diff --git a/.env b/.env new file mode 100644 index 0000000..20ffee2 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +FIREFOX_BINARY="D:\Program Files\Firefox Developer Edition\firefox.exe" +FIREFOX_PROFILE="D:\Programs\Firefox\Addon" diff --git a/.gitignore b/.gitignore index 2b20e3e..1e9cfdb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,13 @@ /*.project /node_modules .vscode +.idea + +.wxt +.output + +/package-lock.json + +/dev/experiments +/releases +/data/session-sync.css diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..d8fc8ea --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 140, + "tabWidth": 4, + "useTabs": true, + "endOfLine": "crlf" +} diff --git a/README.md b/README.md index bda30e4..01177df 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,49 @@ -SessionSync [![Session Sync](/data/images/icon32.png)](https://addons.mozilla.org/en-US/firefox/addon/session-sync/) -=========== +# SessionSync [![Session Sync](/data/images/icon32.png)](https://addons.mozilla.org/en-US/firefox/addon/session-sync/) ##### Firefox Add-on for storing browser sessions into bookmarks and sync them across multiple devices -[![alt tag](https://ffp4g1ylyit3jdyti1hqcvtb-wpengine.netdna-ssl.com/addons/files/2015/11/AMO-button_1.png)](https://addons.mozilla.org/en-US/firefox/addon/session-sync/) +[Install from Add-ons Website](https://addons.mozilla.org/en-US/firefox/addon/session-sync/) #### Description Save sessions as bookmarks and sync them using Firefox Sync (or any other sync engine) Session-Sync will automatically detect updates and display updated sessions across multiple machines. Session-Sync provides a rich interface for sessions management - - edit sessions and bookmarks (save, delete, restore, merge, replace, etc) - - order sessions and book using drag-n-drop actions - - use auto-save feature to inspect history sessions - + +- edit sessions and bookmarks (save, delete, restore, merge, replace, etc) +- order sessions and book using drag-n-drop actions +- use auto-save feature to inspect history sessions + [![Session Sync](/data/images/tutorial/session-menu.png)](https://addons.mozilla.org/en-US/firefox/addon/session-sync/) #### Version 3 - WebExtensions - - improved UI support for widget based interaction - - multiple UI/UX improvements - - added optional Tab/Window Detach mode +- improved UI support for widget based interaction +- multiple UI/UX improvements +- added optional Tab/Window Detach mode #### General Features - Advanced UI and Management Features - - improved bookmark management +- improved bookmark management - added support for pinned tabs and other features such as merge, replace, - session auto-saving - history list - session can be saved later as classic bookmark sessions - private windows are ignored by auto-save - - speed improvement and bookmark cache optimizations +- speed improvement and bookmark cache optimizations ##### UI/UX - - resizable UI - - icon set and tooltip system - - advance UI scaling for individual UI areas (session list, bookmark list, session header and toolbar) - - customization panel - - support for touch input - - session sorting by name/position/date - - session filtering - - advanced drag-n-drop support with fluid animations - +- resizable UI +- icon set and tooltip system +- advance UI scaling for individual UI areas (session list, bookmark list, session header and toolbar) +- customization panel +- support for touch input +- session sorting by name/position/date +- session filtering +- advanced drag-n-drop support with fluid animations + +## Development + +```bash +npm run dev:firefox +``` diff --git a/_locales/en/messages.json b/_locales/en/messages.json deleted file mode 100644 index 1fd8eb3..0000000 --- a/_locales/en/messages.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "test": { - "message": "Restore in new window" - } -} \ No newline at end of file diff --git a/dev/art/logo.psd b/assets/art/logo.psd similarity index 100% rename from dev/art/logo.psd rename to assets/art/logo.psd diff --git a/dev/art/menu-buttons.psd b/assets/art/menu-buttons.psd similarity index 100% rename from dev/art/menu-buttons.psd rename to assets/art/menu-buttons.psd diff --git a/dev/design/design.psd b/assets/design/design.psd similarity index 100% rename from dev/design/design.psd rename to assets/design/design.psd diff --git a/data/images/icons/anchor.png b/assets/images/icons/anchor.png similarity index 100% rename from data/images/icons/anchor.png rename to assets/images/icons/anchor.png diff --git a/data/images/icons/aperture.png b/assets/images/icons/aperture.png similarity index 100% rename from data/images/icons/aperture.png rename to assets/images/icons/aperture.png diff --git a/data/images/icons/arrow-down.png b/assets/images/icons/arrow-down.png similarity index 100% rename from data/images/icons/arrow-down.png rename to assets/images/icons/arrow-down.png diff --git a/data/images/icons/arrow-up.png b/assets/images/icons/arrow-up.png similarity index 100% rename from data/images/icons/arrow-up.png rename to assets/images/icons/arrow-up.png diff --git a/data/images/icons/art.png b/assets/images/icons/art.png similarity index 100% rename from data/images/icons/art.png rename to assets/images/icons/art.png diff --git a/data/images/icons/barchart.png b/assets/images/icons/barchart.png similarity index 100% rename from data/images/icons/barchart.png rename to assets/images/icons/barchart.png diff --git a/data/images/icons/batteryfull.png b/assets/images/icons/batteryfull.png similarity index 100% rename from data/images/icons/batteryfull.png rename to assets/images/icons/batteryfull.png diff --git a/data/images/icons/batterylow.png b/assets/images/icons/batterylow.png similarity index 100% rename from data/images/icons/batterylow.png rename to assets/images/icons/batterylow.png diff --git a/data/images/icons/bike.png b/assets/images/icons/bike.png similarity index 100% rename from data/images/icons/bike.png rename to assets/images/icons/bike.png diff --git a/data/images/icons/biker.png b/assets/images/icons/biker.png similarity index 100% rename from data/images/icons/biker.png rename to assets/images/icons/biker.png diff --git a/data/images/icons/bikewheel.png b/assets/images/icons/bikewheel.png similarity index 100% rename from data/images/icons/bikewheel.png rename to assets/images/icons/bikewheel.png diff --git a/data/images/icons/blimp.png b/assets/images/icons/blimp.png similarity index 100% rename from data/images/icons/blimp.png rename to assets/images/icons/blimp.png diff --git a/data/images/icons/bolt.png b/assets/images/icons/bolt.png similarity index 100% rename from data/images/icons/bolt.png rename to assets/images/icons/bolt.png diff --git a/data/images/icons/bomb.png b/assets/images/icons/bomb.png similarity index 100% rename from data/images/icons/bomb.png rename to assets/images/icons/bomb.png diff --git a/data/images/icons/brush-pencil.png b/assets/images/icons/brush-pencil.png similarity index 100% rename from data/images/icons/brush-pencil.png rename to assets/images/icons/brush-pencil.png diff --git a/data/images/icons/calculator.png b/assets/images/icons/calculator.png similarity index 100% rename from data/images/icons/calculator.png rename to assets/images/icons/calculator.png diff --git a/data/images/icons/calendar.png b/assets/images/icons/calendar.png similarity index 100% rename from data/images/icons/calendar.png rename to assets/images/icons/calendar.png diff --git a/data/images/icons/camera.png b/assets/images/icons/camera.png similarity index 100% rename from data/images/icons/camera.png rename to assets/images/icons/camera.png diff --git a/data/images/icons/car.png b/assets/images/icons/car.png similarity index 100% rename from data/images/icons/car.png rename to assets/images/icons/car.png diff --git a/data/images/icons/cart.png b/assets/images/icons/cart.png similarity index 100% rename from data/images/icons/cart.png rename to assets/images/icons/cart.png diff --git a/data/images/icons/carwheel.png b/assets/images/icons/carwheel.png similarity index 100% rename from data/images/icons/carwheel.png rename to assets/images/icons/carwheel.png diff --git a/data/images/icons/caution.png b/assets/images/icons/caution.png similarity index 100% rename from data/images/icons/caution.png rename to assets/images/icons/caution.png diff --git a/data/images/icons/chat.png b/assets/images/icons/chat.png similarity index 100% rename from data/images/icons/chat.png rename to assets/images/icons/chat.png diff --git a/data/images/icons/check.png b/assets/images/icons/check.png similarity index 100% rename from data/images/icons/check.png rename to assets/images/icons/check.png diff --git a/data/images/icons/circlecompass.png b/assets/images/icons/circlecompass.png similarity index 100% rename from data/images/icons/circlecompass.png rename to assets/images/icons/circlecompass.png diff --git a/data/images/icons/clapboard.png b/assets/images/icons/clapboard.png similarity index 100% rename from data/images/icons/clapboard.png rename to assets/images/icons/clapboard.png diff --git a/data/images/icons/clock.png b/assets/images/icons/clock.png similarity index 100% rename from data/images/icons/clock.png rename to assets/images/icons/clock.png diff --git a/data/images/icons/cloud.png b/assets/images/icons/cloud.png similarity index 100% rename from data/images/icons/cloud.png rename to assets/images/icons/cloud.png diff --git a/data/images/icons/cmyk.png b/assets/images/icons/cmyk.png similarity index 100% rename from data/images/icons/cmyk.png rename to assets/images/icons/cmyk.png diff --git a/data/images/icons/colorwheel.png b/assets/images/icons/colorwheel.png similarity index 100% rename from data/images/icons/colorwheel.png rename to assets/images/icons/colorwheel.png diff --git a/data/images/icons/compass.png b/assets/images/icons/compass.png similarity index 100% rename from data/images/icons/compass.png rename to assets/images/icons/compass.png diff --git a/data/images/icons/compose.png b/assets/images/icons/compose.png similarity index 100% rename from data/images/icons/compose.png rename to assets/images/icons/compose.png diff --git a/data/images/icons/computer.png b/assets/images/icons/computer.png similarity index 100% rename from data/images/icons/computer.png rename to assets/images/icons/computer.png diff --git a/data/images/icons/cone.png b/assets/images/icons/cone.png similarity index 100% rename from data/images/icons/cone.png rename to assets/images/icons/cone.png diff --git a/data/images/icons/contacts.png b/assets/images/icons/contacts.png similarity index 100% rename from data/images/icons/contacts.png rename to assets/images/icons/contacts.png diff --git a/data/images/icons/contrast.png b/assets/images/icons/contrast.png similarity index 100% rename from data/images/icons/contrast.png rename to assets/images/icons/contrast.png diff --git a/data/images/icons/countdown.png b/assets/images/icons/countdown.png similarity index 100% rename from data/images/icons/countdown.png rename to assets/images/icons/countdown.png diff --git a/data/images/icons/creditcard.png b/assets/images/icons/creditcard.png similarity index 100% rename from data/images/icons/creditcard.png rename to assets/images/icons/creditcard.png diff --git a/data/images/icons/crop.png b/assets/images/icons/crop.png similarity index 100% rename from data/images/icons/crop.png rename to assets/images/icons/crop.png diff --git a/data/images/icons/crossroads.png b/assets/images/icons/crossroads.png similarity index 100% rename from data/images/icons/crossroads.png rename to assets/images/icons/crossroads.png diff --git a/data/images/icons/cruise.png b/assets/images/icons/cruise.png similarity index 100% rename from data/images/icons/cruise.png rename to assets/images/icons/cruise.png diff --git a/data/images/icons/cursor.png b/assets/images/icons/cursor.png similarity index 100% rename from data/images/icons/cursor.png rename to assets/images/icons/cursor.png diff --git a/data/images/icons/dev.png b/assets/images/icons/dev.png similarity index 100% rename from data/images/icons/dev.png rename to assets/images/icons/dev.png diff --git a/data/images/icons/die.png b/assets/images/icons/die.png similarity index 100% rename from data/images/icons/die.png rename to assets/images/icons/die.png diff --git a/data/images/icons/dolly.png b/assets/images/icons/dolly.png similarity index 100% rename from data/images/icons/dolly.png rename to assets/images/icons/dolly.png diff --git a/data/images/icons/door.png b/assets/images/icons/door.png similarity index 100% rename from data/images/icons/door.png rename to assets/images/icons/door.png diff --git a/data/images/icons/download.png b/assets/images/icons/download.png similarity index 100% rename from data/images/icons/download.png rename to assets/images/icons/download.png diff --git a/data/images/icons/easel.png b/assets/images/icons/easel.png similarity index 100% rename from data/images/icons/easel.png rename to assets/images/icons/easel.png diff --git a/data/images/icons/email.png b/assets/images/icons/email.png similarity index 100% rename from data/images/icons/email.png rename to assets/images/icons/email.png diff --git a/data/images/icons/eye.png b/assets/images/icons/eye.png similarity index 100% rename from data/images/icons/eye.png rename to assets/images/icons/eye.png diff --git a/data/images/icons/eyedropper.png b/assets/images/icons/eyedropper.png similarity index 100% rename from data/images/icons/eyedropper.png rename to assets/images/icons/eyedropper.png diff --git a/data/images/icons/fashion.png b/assets/images/icons/fashion.png similarity index 100% rename from data/images/icons/fashion.png rename to assets/images/icons/fashion.png diff --git a/data/images/icons/filmreel.png b/assets/images/icons/filmreel.png similarity index 100% rename from data/images/icons/filmreel.png rename to assets/images/icons/filmreel.png diff --git a/data/images/icons/filmroll.png b/assets/images/icons/filmroll.png similarity index 100% rename from data/images/icons/filmroll.png rename to assets/images/icons/filmroll.png diff --git a/data/images/icons/flag.png b/assets/images/icons/flag.png similarity index 100% rename from data/images/icons/flag.png rename to assets/images/icons/flag.png diff --git a/data/images/icons/flame.png b/assets/images/icons/flame.png similarity index 100% rename from data/images/icons/flame.png rename to assets/images/icons/flame.png diff --git a/data/images/icons/flash.png b/assets/images/icons/flash.png similarity index 100% rename from data/images/icons/flash.png rename to assets/images/icons/flash.png diff --git a/data/images/icons/flower.png b/assets/images/icons/flower.png similarity index 100% rename from data/images/icons/flower.png rename to assets/images/icons/flower.png diff --git a/data/images/icons/focus.png b/assets/images/icons/focus.png similarity index 100% rename from data/images/icons/focus.png rename to assets/images/icons/focus.png diff --git a/data/images/icons/folder.png b/assets/images/icons/folder.png similarity index 100% rename from data/images/icons/folder.png rename to assets/images/icons/folder.png diff --git a/data/images/icons/gamecontroller.png b/assets/images/icons/gamecontroller.png similarity index 100% rename from data/images/icons/gamecontroller.png rename to assets/images/icons/gamecontroller.png diff --git a/data/images/icons/gas.png b/assets/images/icons/gas.png similarity index 100% rename from data/images/icons/gas.png rename to assets/images/icons/gas.png diff --git a/data/images/icons/genius.png b/assets/images/icons/genius.png similarity index 100% rename from data/images/icons/genius.png rename to assets/images/icons/genius.png diff --git a/data/images/icons/global.png b/assets/images/icons/global.png similarity index 100% rename from data/images/icons/global.png rename to assets/images/icons/global.png diff --git a/data/images/icons/gps.png b/assets/images/icons/gps.png similarity index 100% rename from data/images/icons/gps.png rename to assets/images/icons/gps.png diff --git a/data/images/icons/half-loading.png b/assets/images/icons/half-loading.png similarity index 100% rename from data/images/icons/half-loading.png rename to assets/images/icons/half-loading.png diff --git a/data/images/icons/hazard.png b/assets/images/icons/hazard.png similarity index 100% rename from data/images/icons/hazard.png rename to assets/images/icons/hazard.png diff --git a/data/images/icons/helicopter.png b/assets/images/icons/helicopter.png similarity index 100% rename from data/images/icons/helicopter.png rename to assets/images/icons/helicopter.png diff --git a/data/images/icons/hotair.png b/assets/images/icons/hotair.png similarity index 100% rename from data/images/icons/hotair.png rename to assets/images/icons/hotair.png diff --git a/data/images/icons/hourglass.png b/assets/images/icons/hourglass.png similarity index 100% rename from data/images/icons/hourglass.png rename to assets/images/icons/hourglass.png diff --git a/data/images/icons/image.png b/assets/images/icons/image.png similarity index 100% rename from data/images/icons/image.png rename to assets/images/icons/image.png diff --git a/data/images/icons/interstate.png b/assets/images/icons/interstate.png similarity index 100% rename from data/images/icons/interstate.png rename to assets/images/icons/interstate.png diff --git a/data/images/icons/key.png b/assets/images/icons/key.png similarity index 100% rename from data/images/icons/key.png rename to assets/images/icons/key.png diff --git a/data/images/icons/keyboard.png b/assets/images/icons/keyboard.png similarity index 100% rename from data/images/icons/keyboard.png rename to assets/images/icons/keyboard.png diff --git a/data/images/icons/lens.png b/assets/images/icons/lens.png similarity index 100% rename from data/images/icons/lens.png rename to assets/images/icons/lens.png diff --git a/data/images/icons/lightbulb.png b/assets/images/icons/lightbulb.png similarity index 100% rename from data/images/icons/lightbulb.png rename to assets/images/icons/lightbulb.png diff --git a/data/images/icons/loading.png b/assets/images/icons/loading.png similarity index 100% rename from data/images/icons/loading.png rename to assets/images/icons/loading.png diff --git a/data/images/icons/location.png b/assets/images/icons/location.png similarity index 100% rename from data/images/icons/location.png rename to assets/images/icons/location.png diff --git a/data/images/icons/locked.png b/assets/images/icons/locked.png similarity index 100% rename from data/images/icons/locked.png rename to assets/images/icons/locked.png diff --git a/data/images/icons/magicwand.png b/assets/images/icons/magicwand.png similarity index 100% rename from data/images/icons/magicwand.png rename to assets/images/icons/magicwand.png diff --git a/data/images/icons/magnifyingglass.png b/assets/images/icons/magnifyingglass.png similarity index 100% rename from data/images/icons/magnifyingglass.png rename to assets/images/icons/magnifyingglass.png diff --git a/data/images/icons/map.png b/assets/images/icons/map.png similarity index 100% rename from data/images/icons/map.png rename to assets/images/icons/map.png diff --git a/data/images/icons/megaphone.png b/assets/images/icons/megaphone.png similarity index 100% rename from data/images/icons/megaphone.png rename to assets/images/icons/megaphone.png diff --git a/data/images/icons/megaphone2.png b/assets/images/icons/megaphone2.png similarity index 100% rename from data/images/icons/megaphone2.png rename to assets/images/icons/megaphone2.png diff --git a/data/images/icons/memorycard.png b/assets/images/icons/memorycard.png similarity index 100% rename from data/images/icons/memorycard.png rename to assets/images/icons/memorycard.png diff --git a/data/images/icons/mic.png b/assets/images/icons/mic.png similarity index 100% rename from data/images/icons/mic.png rename to assets/images/icons/mic.png diff --git a/data/images/icons/microphone.png b/assets/images/icons/microphone.png similarity index 100% rename from data/images/icons/microphone.png rename to assets/images/icons/microphone.png diff --git a/data/images/icons/money.png b/assets/images/icons/money.png similarity index 100% rename from data/images/icons/money.png rename to assets/images/icons/money.png diff --git a/data/images/icons/motorcycle.png b/assets/images/icons/motorcycle.png similarity index 100% rename from data/images/icons/motorcycle.png rename to assets/images/icons/motorcycle.png diff --git a/data/images/icons/music.png b/assets/images/icons/music.png similarity index 100% rename from data/images/icons/music.png rename to assets/images/icons/music.png diff --git a/data/images/icons/news.png b/assets/images/icons/news.png similarity index 100% rename from data/images/icons/news.png rename to assets/images/icons/news.png diff --git a/data/images/icons/paintbrush.png b/assets/images/icons/paintbrush.png similarity index 100% rename from data/images/icons/paintbrush.png rename to assets/images/icons/paintbrush.png diff --git a/data/images/icons/paintbrush2.png b/assets/images/icons/paintbrush2.png similarity index 100% rename from data/images/icons/paintbrush2.png rename to assets/images/icons/paintbrush2.png diff --git a/data/images/icons/paintcan.png b/assets/images/icons/paintcan.png similarity index 100% rename from data/images/icons/paintcan.png rename to assets/images/icons/paintcan.png diff --git a/data/images/icons/paintroller.png b/assets/images/icons/paintroller.png similarity index 100% rename from data/images/icons/paintroller.png rename to assets/images/icons/paintroller.png diff --git a/data/images/icons/parachute.png b/assets/images/icons/parachute.png similarity index 100% rename from data/images/icons/parachute.png rename to assets/images/icons/parachute.png diff --git a/data/images/icons/pencil.png b/assets/images/icons/pencil.png similarity index 100% rename from data/images/icons/pencil.png rename to assets/images/icons/pencil.png diff --git a/data/images/icons/phone.png b/assets/images/icons/phone.png similarity index 100% rename from data/images/icons/phone.png rename to assets/images/icons/phone.png diff --git a/data/images/icons/piechart.png b/assets/images/icons/piechart.png similarity index 100% rename from data/images/icons/piechart.png rename to assets/images/icons/piechart.png diff --git a/data/images/icons/plane.png b/assets/images/icons/plane.png similarity index 100% rename from data/images/icons/plane.png rename to assets/images/icons/plane.png diff --git a/data/images/icons/play.png b/assets/images/icons/play.png similarity index 100% rename from data/images/icons/play.png rename to assets/images/icons/play.png diff --git a/data/images/icons/plugin.png b/assets/images/icons/plugin.png similarity index 100% rename from data/images/icons/plugin.png rename to assets/images/icons/plugin.png diff --git a/data/images/icons/polaroid.png b/assets/images/icons/polaroid.png similarity index 100% rename from data/images/icons/polaroid.png rename to assets/images/icons/polaroid.png diff --git a/data/images/icons/polaroidcamera.png b/assets/images/icons/polaroidcamera.png similarity index 100% rename from data/images/icons/polaroidcamera.png rename to assets/images/icons/polaroidcamera.png diff --git a/data/images/icons/polaroids.png b/assets/images/icons/polaroids.png similarity index 100% rename from data/images/icons/polaroids.png rename to assets/images/icons/polaroids.png diff --git a/data/images/icons/present.png b/assets/images/icons/present.png similarity index 100% rename from data/images/icons/present.png rename to assets/images/icons/present.png diff --git a/data/images/icons/profle.png b/assets/images/icons/profle.png similarity index 100% rename from data/images/icons/profle.png rename to assets/images/icons/profle.png diff --git a/data/images/icons/quote.png b/assets/images/icons/quote.png similarity index 100% rename from data/images/icons/quote.png rename to assets/images/icons/quote.png diff --git a/data/images/icons/racingflags.png b/assets/images/icons/racingflags.png similarity index 100% rename from data/images/icons/racingflags.png rename to assets/images/icons/racingflags.png diff --git a/data/images/icons/radio.png b/assets/images/icons/radio.png similarity index 100% rename from data/images/icons/radio.png rename to assets/images/icons/radio.png diff --git a/data/images/icons/radiotower.png b/assets/images/icons/radiotower.png similarity index 100% rename from data/images/icons/radiotower.png rename to assets/images/icons/radiotower.png diff --git a/data/images/icons/rainbow.png b/assets/images/icons/rainbow.png similarity index 100% rename from data/images/icons/rainbow.png rename to assets/images/icons/rainbow.png diff --git a/data/images/icons/rgb.png b/assets/images/icons/rgb.png similarity index 100% rename from data/images/icons/rgb.png rename to assets/images/icons/rgb.png diff --git a/data/images/icons/roadblock.png b/assets/images/icons/roadblock.png similarity index 100% rename from data/images/icons/roadblock.png rename to assets/images/icons/roadblock.png diff --git a/data/images/icons/rocket.png b/assets/images/icons/rocket.png similarity index 100% rename from data/images/icons/rocket.png rename to assets/images/icons/rocket.png diff --git a/data/images/icons/rulertriangle.png b/assets/images/icons/rulertriangle.png similarity index 100% rename from data/images/icons/rulertriangle.png rename to assets/images/icons/rulertriangle.png diff --git a/data/images/icons/running.png b/assets/images/icons/running.png similarity index 100% rename from data/images/icons/running.png rename to assets/images/icons/running.png diff --git a/data/images/icons/sailboat.png b/assets/images/icons/sailboat.png similarity index 100% rename from data/images/icons/sailboat.png rename to assets/images/icons/sailboat.png diff --git a/data/images/icons/schooolbus.png b/assets/images/icons/schooolbus.png similarity index 100% rename from data/images/icons/schooolbus.png rename to assets/images/icons/schooolbus.png diff --git a/data/images/icons/scissors.png b/assets/images/icons/scissors.png similarity index 100% rename from data/images/icons/scissors.png rename to assets/images/icons/scissors.png diff --git a/data/images/icons/scooter.png b/assets/images/icons/scooter.png similarity index 100% rename from data/images/icons/scooter.png rename to assets/images/icons/scooter.png diff --git a/data/images/icons/security.png b/assets/images/icons/security.png similarity index 100% rename from data/images/icons/security.png rename to assets/images/icons/security.png diff --git a/data/images/icons/selftimer.png b/assets/images/icons/selftimer.png similarity index 100% rename from data/images/icons/selftimer.png rename to assets/images/icons/selftimer.png diff --git a/data/images/icons/settings.png b/assets/images/icons/settings.png similarity index 100% rename from data/images/icons/settings.png rename to assets/images/icons/settings.png diff --git a/data/images/icons/shipwheel.png b/assets/images/icons/shipwheel.png similarity index 100% rename from data/images/icons/shipwheel.png rename to assets/images/icons/shipwheel.png diff --git a/data/images/icons/shoeprints.png b/assets/images/icons/shoeprints.png similarity index 100% rename from data/images/icons/shoeprints.png rename to assets/images/icons/shoeprints.png diff --git a/data/images/icons/shop.png b/assets/images/icons/shop.png similarity index 100% rename from data/images/icons/shop.png rename to assets/images/icons/shop.png diff --git a/data/images/icons/skateboard.png b/assets/images/icons/skateboard.png similarity index 100% rename from data/images/icons/skateboard.png rename to assets/images/icons/skateboard.png diff --git a/data/images/icons/slr.png b/assets/images/icons/slr.png similarity index 100% rename from data/images/icons/slr.png rename to assets/images/icons/slr.png diff --git a/data/images/icons/smartphone.png b/assets/images/icons/smartphone.png similarity index 100% rename from data/images/icons/smartphone.png rename to assets/images/icons/smartphone.png diff --git a/data/images/icons/spaceshuttle.png b/assets/images/icons/spaceshuttle.png similarity index 100% rename from data/images/icons/spaceshuttle.png rename to assets/images/icons/spaceshuttle.png diff --git a/data/images/icons/speaker.png b/assets/images/icons/speaker.png similarity index 100% rename from data/images/icons/speaker.png rename to assets/images/icons/speaker.png diff --git a/data/images/icons/speedometer.png b/assets/images/icons/speedometer.png similarity index 100% rename from data/images/icons/speedometer.png rename to assets/images/icons/speedometer.png diff --git a/data/images/icons/spraypaint.png b/assets/images/icons/spraypaint.png similarity index 100% rename from data/images/icons/spraypaint.png rename to assets/images/icons/spraypaint.png diff --git a/data/images/icons/steeringwheel.png b/assets/images/icons/steeringwheel.png similarity index 100% rename from data/images/icons/steeringwheel.png rename to assets/images/icons/steeringwheel.png diff --git a/data/images/icons/stop.png b/assets/images/icons/stop.png similarity index 100% rename from data/images/icons/stop.png rename to assets/images/icons/stop.png diff --git a/data/images/icons/sub.png b/assets/images/icons/sub.png similarity index 100% rename from data/images/icons/sub.png rename to assets/images/icons/sub.png diff --git a/data/images/icons/submarine.png b/assets/images/icons/submarine.png similarity index 100% rename from data/images/icons/submarine.png rename to assets/images/icons/submarine.png diff --git a/data/images/icons/support.png b/assets/images/icons/support.png similarity index 100% rename from data/images/icons/support.png rename to assets/images/icons/support.png diff --git a/data/images/icons/swatches.png b/assets/images/icons/swatches.png similarity index 100% rename from data/images/icons/swatches.png rename to assets/images/icons/swatches.png diff --git a/data/images/icons/tablet.png b/assets/images/icons/tablet.png similarity index 100% rename from data/images/icons/tablet.png rename to assets/images/icons/tablet.png diff --git a/data/images/icons/takeoff.png b/assets/images/icons/takeoff.png similarity index 100% rename from data/images/icons/takeoff.png rename to assets/images/icons/takeoff.png diff --git a/data/images/icons/target.png b/assets/images/icons/target.png similarity index 100% rename from data/images/icons/target.png rename to assets/images/icons/target.png diff --git a/data/images/icons/taxi.png b/assets/images/icons/taxi.png similarity index 100% rename from data/images/icons/taxi.png rename to assets/images/icons/taxi.png diff --git a/data/images/icons/toolbox.png b/assets/images/icons/toolbox.png similarity index 100% rename from data/images/icons/toolbox.png rename to assets/images/icons/toolbox.png diff --git a/data/images/icons/tractor.png b/assets/images/icons/tractor.png similarity index 100% rename from data/images/icons/tractor.png rename to assets/images/icons/tractor.png diff --git a/data/images/icons/traffic.png b/assets/images/icons/traffic.png similarity index 100% rename from data/images/icons/traffic.png rename to assets/images/icons/traffic.png diff --git a/data/images/icons/train.png b/assets/images/icons/train.png similarity index 100% rename from data/images/icons/train.png rename to assets/images/icons/train.png diff --git a/data/images/icons/travelerbag.png b/assets/images/icons/travelerbag.png similarity index 100% rename from data/images/icons/travelerbag.png rename to assets/images/icons/travelerbag.png diff --git a/data/images/icons/trends.png b/assets/images/icons/trends.png similarity index 100% rename from data/images/icons/trends.png rename to assets/images/icons/trends.png diff --git a/data/images/icons/tripod.png b/assets/images/icons/tripod.png similarity index 100% rename from data/images/icons/tripod.png rename to assets/images/icons/tripod.png diff --git a/data/images/icons/trophy.png b/assets/images/icons/trophy.png similarity index 100% rename from data/images/icons/trophy.png rename to assets/images/icons/trophy.png diff --git a/data/images/icons/truck.png b/assets/images/icons/truck.png similarity index 100% rename from data/images/icons/truck.png rename to assets/images/icons/truck.png diff --git a/data/images/icons/tv.png b/assets/images/icons/tv.png similarity index 100% rename from data/images/icons/tv.png rename to assets/images/icons/tv.png diff --git a/data/images/icons/typography.png b/assets/images/icons/typography.png similarity index 100% rename from data/images/icons/typography.png rename to assets/images/icons/typography.png diff --git a/data/images/icons/ufo.png b/assets/images/icons/ufo.png similarity index 100% rename from data/images/icons/ufo.png rename to assets/images/icons/ufo.png diff --git a/data/images/icons/umbrella.png b/assets/images/icons/umbrella.png similarity index 100% rename from data/images/icons/umbrella.png rename to assets/images/icons/umbrella.png diff --git a/data/images/icons/unicycle.png b/assets/images/icons/unicycle.png similarity index 100% rename from data/images/icons/unicycle.png rename to assets/images/icons/unicycle.png diff --git a/data/images/icons/unlocked.png b/assets/images/icons/unlocked.png similarity index 100% rename from data/images/icons/unlocked.png rename to assets/images/icons/unlocked.png diff --git a/data/images/icons/upload.png b/assets/images/icons/upload.png similarity index 100% rename from data/images/icons/upload.png rename to assets/images/icons/upload.png diff --git a/data/images/icons/video.png b/assets/images/icons/video.png similarity index 100% rename from data/images/icons/video.png rename to assets/images/icons/video.png diff --git a/data/images/icons/videocameraclassic.png b/assets/images/icons/videocameraclassic.png similarity index 100% rename from data/images/icons/videocameraclassic.png rename to assets/images/icons/videocameraclassic.png diff --git a/data/images/icons/videocameracompact.png b/assets/images/icons/videocameracompact.png similarity index 100% rename from data/images/icons/videocameracompact.png rename to assets/images/icons/videocameracompact.png diff --git a/data/images/icons/volume.png b/assets/images/icons/volume.png similarity index 100% rename from data/images/icons/volume.png rename to assets/images/icons/volume.png diff --git a/data/images/icons/water.png b/assets/images/icons/water.png similarity index 100% rename from data/images/icons/water.png rename to assets/images/icons/water.png diff --git a/data/images/icons/weather.png b/assets/images/icons/weather.png similarity index 100% rename from data/images/icons/weather.png rename to assets/images/icons/weather.png diff --git a/data/images/icons/windsock.png b/assets/images/icons/windsock.png similarity index 100% rename from data/images/icons/windsock.png rename to assets/images/icons/windsock.png diff --git a/data/images/icons/windy.png b/assets/images/icons/windy.png similarity index 100% rename from data/images/icons/windy.png rename to assets/images/icons/windy.png diff --git a/data/images/icons/zoomin.png b/assets/images/icons/zoomin.png similarity index 100% rename from data/images/icons/zoomin.png rename to assets/images/icons/zoomin.png diff --git a/data/images/icons/zoomout.png b/assets/images/icons/zoomout.png similarity index 100% rename from data/images/icons/zoomout.png rename to assets/images/icons/zoomout.png diff --git a/bin/7za.exe b/bin/7za.exe deleted file mode 100644 index 710bda2..0000000 Binary files a/bin/7za.exe and /dev/null differ diff --git a/data/home/home.html b/data/home/home.html deleted file mode 100644 index 3ba3127..0000000 --- a/data/home/home.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - Session Sync - Tutorial - - - - - - - -
- -
- - -
-
- Session Sync - -
-
- -
- - -
-
-
- -
- -
-
Feedback
-
-
- -
-
-
-
- -
-
-
-
- Press CTRL + SHIFT + F to open the panel or use the widget provided to open the panel -
-
- You can find all your recent browsing history in the history tab.
- Restore the session or save it if you want to keep indefinitely
- Configure history settings in the configuration panel -
-
- Use the search bar to easy filter through saved sessions -
- You can focus on the search bar by pressing CTRL + F or F3 -
-
- Press left click to access aditional context menus for - sessions or - bookmarks -
-
-
- Resize the UI panel using the - resize-handle from the - bottom-left corner -
-
- Resize session list width by dragging around the - green resize handle -
-
- The addon offers multiple configuration options. Explore all of them in the configuration panel -
- Configuration panel can be accessed from the addon UI menu -
-
- Drag and drop bookmark items over the trash bin drop area location (bottom-right corner) - to delete them -
- Click on the red button to restore the previous deleted items -
-
- Session sync offers the ability to save (or not) pinned tabs and - also to remember the - pinned state -
-
- You can edit session and bookmarks information to provide more valuable future information -
-
- Sort sessions by name, position, or date using the sort-selection menu from the bottom-left corner of the UI -
-
- The session menu toolbar provides fast access to various commands such as restore, replace, merge or add the current tab as bookmark -
-
- Move your mouse above a bookmark to view the URL. -
- Click on the URL bar to copy the link -
-
- You can detach the UI into a separate window or tab
- Window Mode: CTRL + SHIFT + V   - Tab Mode: CTRL + SHIFT + U
- or simply use the buttons from addon menu
-
-
- Saved session are stored as bookmarks. It's recommended to use Firefox Account to sync your bookmarks.
- Currenly you can only use Firefox Bookmark Manager (CTRL + SHIFT + B) to export/import all your bookmarks (which includes all Session Sync saved data).
- Session Sync detects automatically all updated bookmarks after an import. -
-
-
-
-
- - - - - - - - diff --git a/data/lazy/lazy.css b/data/lazy/lazy.css deleted file mode 100644 index 426db7c..0000000 --- a/data/lazy/lazy.css +++ /dev/null @@ -1,77 +0,0 @@ -:root { - --main-text: #0C0C0D; - --sub-text: #737373; -} - -body { - font-family: 'Segoe UI', 'San Francisco', 'Ubuntu', 'Fira Sans', 'Roboto', 'Arial', 'Helvetica', sans-serif; - font-size: 15px; - font-weight: 400; - color: var(--main-text); - background-color: var(--main-bg); - line-height: 1.5; - display: flex; - flex-direction: row; -} - -#container { - display: flex; - flex-direction: column; - width: 100%; - padding-left: 20px; -} - -#title { - font-size: 32px; - font-weight: 600; - color: var(--sub-text); - line-height: 2; - text-align: center; -} - -#url { - text-align: center; -} - -.caption { - font-size: 16px; - font-weight: 400; - color: var(--sub-text); -} - -hr { - width: 100%; - background-color: var(--line); - height: 1px; - border: none; - margin-top: 20px; - margin-bottom: 20px; -} - -#warning { - position: absolute; - bottom: 50%; - width: 100%; -} - -#warning .message { - margin: 0 auto; - max-width: 980px; - color: #333; -} - -#warning .separator { - height: 1em; -} - -#warning ul { - margin: 0 auto; -} - -#warning ul li { - text-align: left; -} - -#warning .highlight { - color: #EA1F1F; -} diff --git a/data/lazy/lazy.html b/data/lazy/lazy.html deleted file mode 100644 index 5ea1717..0000000 --- a/data/lazy/lazy.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - -
-
Title
- about:example -
- -
-
This page is used for lazy loading Session Sync restored pages
-
If the page was not loaded automatically please check that lazy loading is enabled in addon configuration panel.
-
-
-
Sadly, Firefox Addon API prevents loading certain URL types. This is not an addon issue but and API limitation.
-
    -
  • chrome: URLs
  • -
  • javascript: URLs
  • -
  • data: URLs
  • -
  • file: URLs (files on the filesystem)
  • -
  • priviledged about: URLs
  • -
-
-
-
- - - - - diff --git a/data/lazy/lazy.js b/data/lazy/lazy.js deleted file mode 100644 index c6dcf5b..0000000 --- a/data/lazy/lazy.js +++ /dev/null @@ -1,32 +0,0 @@ -function getLazyLoadingParameters(url) -{ - let paramater = {}; - let paras = url.split('?')[1].split('&'); - for (let p of paras) { - paramater[p.split('=')[0]] = decodeURIComponent(p.split('=')[1]); - } - return paramater; -} - -(function () -{ - var title = document.getElementById('title'); - var urlInfo = document.getElementById('url'); - - let paramater = getLazyLoadingParameters(location.href); - - document.title = paramater.title; - title.textContent = paramater.title; - - urlInfo.textContent = paramater.url; - urlInfo.href = paramater.url; - - if (paramater.favIconUrl) { - var favicon = document.createElement('link'); - favicon.rel = 'shortcut icon'; - favicon.href = paramater.favIconUrl; - document.head.appendChild(favicon); - } - -})(); - diff --git a/data/scripts/3rd/RequireJS/require.js b/data/scripts/3rd/RequireJS/require.js deleted file mode 100644 index a3ca583..0000000 --- a/data/scripts/3rd/RequireJS/require.js +++ /dev/null @@ -1,5 +0,0 @@ -/** vim: et:ts=4:sw=4:sts=4 - * @license RequireJS 2.3.5 Copyright jQuery Foundation and other contributors. - * Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE - */ -var requirejs,require,define;!function(global,setTimeout){function commentReplace(e,t){return t||""}function isFunction(e){return"[object Function]"===ostring.call(e)}function isArray(e){return"[object Array]"===ostring.call(e)}function each(e,t){if(e){var i;for(i=0;i-1&&(!e[i]||!t(e[i],i,e));i-=1);}}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(e,t,i,r){return t&&eachProp(t,function(t,n){!i&&hasProp(e,n)||(!r||"object"!=typeof t||!t||isArray(t)||isFunction(t)||t instanceof RegExp?e[n]=t:(e[n]||(e[n]={}),mixin(e[n],t,i,r)))}),e}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName("script")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split("."),function(e){t=t[e]}),t}function makeError(e,t,i,r){var n=new Error(t+"\nhttp://requirejs.org/docs/errors.html#"+e);return n.requireType=e,n.requireModules=r,i&&(n.originalError=i),n}function newContext(e){function t(e){var t,i;for(t=0;t0&&(e.splice(t-1,2),t-=2)}}function i(e,i,r){var n,o,a,s,u,c,d,p,f,l,h=i&&i.split("/"),m=y.map,g=m&&m["*"];if(e&&(c=(e=e.split("/")).length-1,y.nodeIdCompat&&jsSuffixRegExp.test(e[c])&&(e[c]=e[c].replace(jsSuffixRegExp,"")),"."===e[0].charAt(0)&&h&&(e=h.slice(0,h.length-1).concat(e)),t(e),e=e.join("/")),r&&m&&(h||g)){e:for(a=(o=e.split("/")).length;a>0;a-=1){if(u=o.slice(0,a).join("/"),h)for(s=h.length;s>0;s-=1)if((n=getOwn(m,h.slice(0,s).join("/")))&&(n=getOwn(n,u))){d=n,p=a;break e}!f&&g&&getOwn(g,u)&&(f=getOwn(g,u),l=a)}!d&&f&&(d=f,p=l),d&&(o.splice(0,p,d),e=o.join("/"))}return getOwn(y.pkgs,e)||e}function r(e){isBrowser&&each(scripts(),function(t){if(t.getAttribute("data-requiremodule")===e&&t.getAttribute("data-requirecontext")===q.contextName)return t.parentNode.removeChild(t),!0})}function n(e){var t=getOwn(y.paths,e);if(t&&isArray(t)&&t.length>1)return t.shift(),q.require.undef(e),q.makeRequire(null,{skipMap:!0})([e]),!0}function o(e){var t,i=e?e.indexOf("!"):-1;return i>-1&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function a(e,t,r,n){var a,s,u,c,d=null,p=t?t.name:null,f=e,l=!0,h="";return e||(l=!1,e="_@r"+(T+=1)),c=o(e),d=c[0],e=c[1],d&&(d=i(d,p,n),s=getOwn(j,d)),e&&(d?h=r?e:s&&s.normalize?s.normalize(e,function(e){return i(e,p,n)}):-1===e.indexOf("!")?i(e,p,n):e:(d=(c=o(h=i(e,p,n)))[0],h=c[1],r=!0,a=q.nameToUrl(h))),u=!d||s||r?"":"_unnormalized"+(A+=1),{prefix:d,name:h,parentMap:t,unnormalized:!!u,url:a,originalName:f,isDefine:l,id:(d?d+"!"+h:h)+u}}function s(e){var t=e.id,i=getOwn(S,t);return i||(i=S[t]=new q.Module(e)),i}function u(e,t,i){var r=e.id,n=getOwn(S,r);!hasProp(j,r)||n&&!n.defineEmitComplete?(n=s(e)).error&&"error"===t?i(n.error):n.on(t,i):"defined"===t&&i(j[r])}function c(e,t){var i=e.requireModules,r=!1;t?t(e):(each(i,function(t){var i=getOwn(S,t);i&&(i.error=e,i.events.error&&(r=!0,i.emit("error",e)))}),r||req.onError(e))}function d(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];"string"==typeof t&&(q.defQueueMap[t]=!0),O.push(e)}),globalDefQueue=[])}function p(e){delete S[e],delete k[e]}function f(e,t,i){var r=e.map.id;e.error?e.emit("error",e.error):(t[r]=!0,each(e.depMaps,function(r,n){var o=r.id,a=getOwn(S,o);!a||e.depMatched[n]||i[o]||(getOwn(t,o)?(e.defineDep(n,j[o]),e.check()):f(a,t,i))}),i[r]=!0)}function l(){var e,t,i=1e3*y.waitSeconds,o=i&&q.startTime+i<(new Date).getTime(),a=[],s=[],u=!1,d=!0;if(!x){if(x=!0,eachProp(k,function(e){var i=e.map,c=i.id;if(e.enabled&&(i.isDefine||s.push(e),!e.error))if(!e.inited&&o)n(c)?(t=!0,u=!0):(a.push(c),r(c));else if(!e.inited&&e.fetched&&i.isDefine&&(u=!0,!i.prefix))return d=!1}),o&&a.length)return e=makeError("timeout","Load timeout for modules: "+a,null,a),e.contextName=q.contextName,c(e);d&&each(s,function(e){f(e,{},{})}),o&&!t||!u||!isBrowser&&!isWebWorker||w||(w=setTimeout(function(){w=0,l()},50)),x=!1}}function h(e){hasProp(j,e[0])||s(a(e[0],null,!0)).init(e[1],e[2])}function m(e,t,i,r){e.detachEvent&&!isOpera?r&&e.detachEvent(r,t):e.removeEventListener(i,t,!1)}function g(e){var t=e.currentTarget||e.srcElement;return m(t,q.onScriptLoad,"load","onreadystatechange"),m(t,q.onScriptError,"error"),{node:t,id:t&&t.getAttribute("data-requiremodule")}}function v(){var e;for(d();O.length;){if(null===(e=O.shift())[0])return c(makeError("mismatch","Mismatched anonymous define() module: "+e[e.length-1]));h(e)}q.defQueueMap={}}var x,b,q,E,w,y={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},S={},k={},M={},O=[],j={},P={},R={},T=1,A=1;return E={require:function(e){return e.require?e.require:e.require=q.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?j[e.map.id]=e.exports:e.exports=j[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(y.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},b=function(e){this.events=getOwn(M,e.id)||{},this.map=e,this.shim=getOwn(y.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0},b.prototype={init:function(e,t,i,r){r=r||{},this.inited||(this.factory=t,i?this.on("error",i):this.events.error&&(i=bind(this,function(e){this.emit("error",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=r.ignore,r.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,q.startTime=(new Date).getTime();var e=this.map;if(!this.shim)return e.prefix?this.callPlugin():this.load();q.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()}))}},load:function(){var e=this.map.url;P[e]||(P[e]=!0,q.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var e,t,i=this.map.id,r=this.depExports,n=this.exports,o=this.factory;if(this.inited){if(this.error)this.emit("error",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(o)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{n=q.execCb(i,o,r,n)}catch(t){e=t}else n=q.execCb(i,o,r,n);if(this.map.isDefine&&void 0===n&&((t=this.module)?n=t.exports:this.usingExports&&(n=this.exports)),e)return e.requireMap=this.map,e.requireModules=this.map.isDefine?[this.map.id]:null,e.requireType=this.map.isDefine?"define":"require",c(this.error=e)}else n=o;if(this.exports=n,this.map.isDefine&&!this.ignore&&(j[i]=n,req.onResourceLoad)){var a=[];each(this.depMaps,function(e){a.push(e.normalizedMap||e)}),req.onResourceLoad(q,this.map,a)}p(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else hasProp(q.defQueueMap,i)||this.fetch()}},callPlugin:function(){var e=this.map,t=e.id,r=a(e.prefix);this.depMaps.push(r),u(r,"defined",bind(this,function(r){var n,o,d,f=getOwn(R,this.map.id),l=this.map.name,h=this.map.parentMap?this.map.parentMap.name:null,m=q.makeRequire(e.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(r.normalize&&(l=r.normalize(l,function(e){return i(e,h,!0)})||""),o=a(e.prefix+"!"+l,this.map.parentMap,!0),u(o,"defined",bind(this,function(e){this.map.normalizedMap=o,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),void((d=getOwn(S,o.id))&&(this.depMaps.push(o),this.events.error&&d.on("error",bind(this,function(e){this.emit("error",e)})),d.enable()))):f?(this.map.url=q.nameToUrl(f),void this.load()):((n=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})})).error=bind(this,function(e){this.inited=!0,this.error=e,e.requireModules=[t],eachProp(S,function(e){0===e.map.id.indexOf(t+"_unnormalized")&&p(e.map.id)}),c(e)}),n.fromText=bind(this,function(i,r){var o=e.name,u=a(o),d=useInteractive;r&&(i=r),d&&(useInteractive=!1),s(u),hasProp(y.config,t)&&(y.config[o]=y.config[t]);try{req.exec(i)}catch(e){return c(makeError("fromtexteval","fromText eval for "+t+" failed: "+e,e,[t]))}d&&(useInteractive=!0),this.depMaps.push(u),q.completeLoad(o),m([o],n)}),void r.load(e.name,m,n,y))})),q.enable(r,this),this.pluginMaps[r.id]=r},enable:function(){k[this.map.id]=this,this.enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,r,n;if("string"==typeof e){if(e=a(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,n=getOwn(E,e.id))return void(this.depExports[t]=n(this));this.depCount+=1,u(e,"defined",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?u(e,"error",bind(this,this.errback)):this.events.error&&u(e,"error",bind(this,function(e){this.emit("error",e)}))}i=e.id,r=S[i],hasProp(E,i)||!r||r.enabled||q.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(S,e.id);t&&!t.enabled&&q.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),"error"===e&&delete this.events[e]}},q={config:y,contextName:e,registry:S,defined:j,urlFetched:P,defQueue:O,defQueueMap:{},Module:b,makeModuleMap:a,nextTick:req.nextTick,onError:c,configure:function(e){if(e.baseUrl&&"/"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+="/"),"string"==typeof e.urlArgs){var t=e.urlArgs;e.urlArgs=function(e,i){return(-1===i.indexOf("?")?"?":"&")+t}}var i=y.shim,r={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){r[t]?(y[t]||(y[t]={}),mixin(y[t],e,!0,!0)):y[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(R[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=q.makeShimExports(e)),i[t]=e}),y.shim=i),e.packages&&each(e.packages,function(e){var t;t=(e="string"==typeof e?{name:e}:e).name,e.location&&(y.paths[t]=e.location),y.pkgs[t]=e.name+"/"+(e.main||"main").replace(currDirRegExp,"").replace(jsSuffixRegExp,"")}),eachProp(S,function(e,t){e.inited||e.map.unnormalized||(e.map=a(t,null,!0))}),(e.deps||e.callback)&&q.require(e.deps||[],e.callback)},makeShimExports:function(e){return function(){var t;return e.init&&(t=e.init.apply(global,arguments)),t||e.exports&&getGlobal(e.exports)}},makeRequire:function(t,n){function o(i,r,u){var d,p,f;return n.enableBuildCallback&&r&&isFunction(r)&&(r.__requireJsBuild=!0),"string"==typeof i?isFunction(r)?c(makeError("requireargs","Invalid require call"),u):t&&hasProp(E,i)?E[i](S[t.id]):req.get?req.get(q,i,t,o):(p=a(i,t,!1,!0),d=p.id,hasProp(j,d)?j[d]:c(makeError("notloaded",'Module name "'+d+'" has not been loaded yet for context: '+e+(t?"":". Use require([])")))):(v(),q.nextTick(function(){v(),(f=s(a(null,t))).skipMap=n.skipMap,f.init(i,r,u,{enabled:!0}),l()}),o)}return n=n||{},mixin(o,{isBrowser:isBrowser,toUrl:function(e){var r,n=e.lastIndexOf("."),o=e.split("/")[0],a="."===o||".."===o;return-1!==n&&(!a||n>1)&&(r=e.substring(n,e.length),e=e.substring(0,n)),q.nameToUrl(i(e,t&&t.id,!0),r,!0)},defined:function(e){return hasProp(j,a(e,t,!1,!0).id)},specified:function(e){return e=a(e,t,!1,!0).id,hasProp(j,e)||hasProp(S,e)}}),t||(o.undef=function(e){d();var i=a(e,t,!0),n=getOwn(S,e);n.undefed=!0,r(e),delete j[e],delete P[i.url],delete M[e],eachReverse(O,function(t,i){t[0]===e&&O.splice(i,1)}),delete q.defQueueMap[e],n&&(n.events.defined&&(M[e]=n.events),p(e))}),o},enable:function(e){getOwn(S,e.id)&&s(e).enable()},completeLoad:function(e){var t,i,r,o=getOwn(y.shim,e)||{},a=o.exports;for(d();O.length;){if(null===(i=O.shift())[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);h(i)}if(q.defQueueMap={},r=getOwn(S,e),!t&&!hasProp(j,e)&&r&&!r.inited){if(!(!y.enforceDefine||a&&getGlobal(a)))return n(e)?void 0:c(makeError("nodefine","No define call for "+e,null,[e]));h([e,o.deps||[],o.exportsFn])}l()},nameToUrl:function(e,t,i){var r,n,o,a,s,u,c,d=getOwn(y.pkgs,e);if(d&&(e=d),c=getOwn(R,e))return q.nameToUrl(c,t,i);if(req.jsExtRegExp.test(e))s=e+(t||"");else{for(r=y.paths,o=(n=e.split("/")).length;o>0;o-=1)if(a=n.slice(0,o).join("/"),u=getOwn(r,a)){isArray(u)&&(u=u[0]),n.splice(0,o,u);break}s=n.join("/"),s=("/"===(s+=t||(/^data\:|^blob\:|\?/.test(s)||i?"":".js")).charAt(0)||s.match(/^[\w\+\.\-]+:/)?"":y.baseUrl)+s}return y.urlArgs&&!/^blob\:/.test(s)?s+y.urlArgs(e,s):s},load:function(e,t){req.load(q,e,t)},execCb:function(e,t,i,r){return t.apply(r,i)},onScriptLoad:function(e){if("load"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=g(e);q.completeLoad(t.id)}},onScriptError:function(e){var t=g(e);if(!n(t.id)){var i=[];return eachProp(S,function(e,r){0!==r.indexOf("_@r")&&each(e.depMaps,function(e){if(e.id===t.id)return i.push(r),!0})}),c(makeError("scripterror",'Script error for "'+t.id+(i.length?'", needed by: '+i.join(", "):'"'),e,[t.id]))}}},q.require=q.makeRequire(),q}function getInteractiveScript(){return interactiveScript&&"interactive"===interactiveScript.readyState?interactiveScript:(eachReverse(scripts(),function(e){if("interactive"===e.readyState)return interactiveScript=e}),interactiveScript)}var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version="2.3.5",commentRegExp=/\/\*[\s\S]*?\*\/|([^:"'=]|^)\/\/.*$/gm,cjsRequireRegExp=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,jsSuffixRegExp=/\.js$/,currDirRegExp=/^\.\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!("undefined"==typeof window||"undefined"==typeof navigator||!window.document),isWebWorker=!isBrowser&&"undefined"!=typeof importScripts,readyRegExp=isBrowser&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName="_",isOpera="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;if(void 0===define){if(void 0!==requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}void 0===require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,r){var n,o,a=defContextName;return isArray(e)||"string"==typeof e||(o=e,isArray(t)?(e=t,t=i,i=r):e=[]),o&&o.context&&(a=o.context),(n=getOwn(contexts,a))||(n=contexts[a]=req.s.newContext(a)),o&&n.configure(o),n.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick=void 0!==setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\/|:|\?|\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each(["toUrl","undef","defined","specified"],function(e){req[e]=function(){var t=contexts[defContextName];return t.require[e].apply(t,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName("head")[0],(baseElement=document.getElementsByTagName("base")[0])&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var r=e.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");return r.type=e.scriptType||"text/javascript",r.charset="utf-8",r.async=!0,r},req.load=function(e,t,i){var r,n=e&&e.config||{};if(isBrowser)return(r=req.createNode(n,t,i)).setAttribute("data-requirecontext",e.contextName),r.setAttribute("data-requiremodule",t),!r.attachEvent||r.attachEvent.toString&&r.attachEvent.toString().indexOf("[native code")<0||isOpera?(r.addEventListener("load",e.onScriptLoad,!1),r.addEventListener("error",e.onScriptError,!1)):(useInteractive=!0,r.attachEvent("onreadystatechange",e.onScriptLoad)),r.src=i,n.onNodeCreated&&n.onNodeCreated(r,n,t,i),currentlyAddingScript=r,baseElement?head.insertBefore(r,baseElement):head.appendChild(r),currentlyAddingScript=null,r;if(isWebWorker)try{setTimeout(function(){},0),importScripts(i),e.completeLoad(t)}catch(r){e.onError(makeError("importscripts","importScripts failed for "+t+" at "+i,r,[t]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute("data-main"))return mainScript=dataMain,cfg.baseUrl||-1!==mainScript.indexOf("!")||(src=mainScript.split("/"),mainScript=src.pop(),subPath=src.length?src.join("/")+"/":"./",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,""),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,t,i){var r,n;"string"!=typeof e&&(i=t,t=e,e=null),isArray(t)||(i=t,t=null),!t&&isFunction(i)&&(t=[],i.length&&(i.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,i){t.push(i)}),t=(1===i.length?["require"]:["require","exports","module"]).concat(t))),useInteractive&&(r=currentlyAddingScript||getInteractiveScript())&&(e||(e=r.getAttribute("data-requiremodule")),n=contexts[r.getAttribute("data-requirecontext")]),n?(n.defQueue.push([e,t,i]),n.defQueueMap[e]=!0):globalDefQueue.push([e,t,i])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}}(this,"undefined"==typeof setTimeout?void 0:setTimeout); \ No newline at end of file diff --git a/data/scripts/3rd/pubsub.js b/data/scripts/3rd/pubsub.js deleted file mode 100644 index 0126059..0000000 --- a/data/scripts/3rd/pubsub.js +++ /dev/null @@ -1,219 +0,0 @@ -define(function(require, exports, module) { - - // Modified version of https://github.com/mroderick/PubSubJS to return different PubSub objects - - 'use strict'; - - function PubSub() { - - var messages = {}, - lastUid = -1; - - function hasKeys(obj){ - var key; - - for (key in obj){ - if ( obj.hasOwnProperty(key) ){ - return true; - } - } - return false; - } - - /** - * Returns a function that throws the passed exception, for use as argument for setTimeout - * @param { Object } ex An Error object - */ - function throwException( ex ){ - return function reThrowException(){ - throw ex; - }; - } - - function callSubscriberWithDelayedExceptions( subscriber, message, data ){ - try { - // subscriber( message, data ); - subscriber( data ); - } catch( ex ){ - setTimeout( throwException( ex ), 0); - } - } - - function callSubscriberWithImmediateExceptions( subscriber, message, data ){ - // subscriber( message, data ); - subscriber( data ); - } - - function deliverMessage( originalMessage, matchedMessage, data, immediateExceptions ){ - var subscribers = messages[matchedMessage], - callSubscriber = immediateExceptions ? callSubscriberWithImmediateExceptions : callSubscriberWithDelayedExceptions, - s; - - if ( !messages.hasOwnProperty( matchedMessage ) ) { - return; - } - - for (s in subscribers){ - if ( subscribers.hasOwnProperty(s)){ - callSubscriber( subscribers[s], originalMessage, data ); - } - } - } - - function createDeliveryFunction( message, data, immediateExceptions ){ - return function deliverNamespaced(){ - var topic = String( message ), - position = topic.lastIndexOf( '.' ); - - // deliver the message as it is now - deliverMessage(message, message, data, immediateExceptions); - - // trim the hierarchy and deliver message to each level - while( position !== -1 ){ - topic = topic.substr( 0, position ); - position = topic.lastIndexOf('.'); - deliverMessage( message, topic, data ); - } - }; - } - - function messageHasSubscribers( message ){ - var topic = String( message ), - found = Boolean(messages.hasOwnProperty( topic ) && hasKeys(messages[topic])), - position = topic.lastIndexOf( '.' ); - - while ( !found && position !== -1 ){ - topic = topic.substr( 0, position ); - position = topic.lastIndexOf( '.' ); - found = Boolean(messages.hasOwnProperty( topic ) && hasKeys(messages[topic])); - } - - return found; - } - - function publish( message, data, sync, immediateExceptions ){ - var deliver = createDeliveryFunction( message, data, immediateExceptions ), - hasSubscribers = messageHasSubscribers( message ); - - if ( !hasSubscribers ){ - return false; - } - - if ( sync === true ){ - deliver(); - } else { - setTimeout( deliver, 0 ); - } - return true; - } - - /** - * PubSub.publish( message[, data] ) -> Boolean - * - message (String): The message to publish - * - data: The data to pass to subscribers - * Publishes the the message, passing the data to it's subscribers - **/ - this.publish = function( message, data ){ - return publish( message, data, false, this.immediateExceptions ); - }; - - /** - * PubSub.publishSync( message[, data] ) -> Boolean - * - message (String): The message to publish - * - data: The data to pass to subscribers - * Publishes the the message synchronously, passing the data to it's subscribers - **/ - this.publishSync = function( message, data ){ - return publish( message, data, true, this.immediateExceptions ); - }; - - /** - * PubSub.subscribe( message, func ) -> String - * - message (String): The message to subscribe to - * - func (Function): The function to call when a new message is published - * Subscribes the passed function to the passed message. Every returned token is unique and should be stored if - * you need to unsubscribe - **/ - this.subscribe = function( message, func ){ - if ( typeof func !== 'function'){ - return false; - } - - // message is not registered yet - if ( !messages.hasOwnProperty( message ) ){ - messages[message] = {}; - } - - // forcing token as String, to allow for future expansions without breaking usage - // and allow for easy use as key names for the 'messages' object - var token = 'uid_' + String(++lastUid); - messages[message][token] = func; - - // return token for unsubscribing - return token; - }; - - /* Public: Clears all subscriptions - */ - this.clearAllSubscriptions = function clearSubscriptions(){ - messages = {}; - }; - - /* Public: removes subscriptions. - * When passed a token, removes a specific subscription. - * When passed a function, removes all subscriptions for that function - * When passed a topic, removes all subscriptions for that topic (hierarchy) - * - * value - A token, function or topic to unsubscribe. - * - * Examples - * - * // Example 1 - unsubscribing with a token - * var token = PubSub.subscribe('mytopic', myFunc); - * PubSub.unsubscribe(token); - * - * // Example 2 - unsubscribing with a function - * PubSub.unsubscribe(myFunc); - * - * // Example 3 - unsubscribing a topic - * PubSub.unsubscribe('mytopic'); - */ - this.unsubscribe = function(value){ - var isTopic = typeof value === 'string' && messages.hasOwnProperty(value), - isToken = !isTopic && typeof value === 'string', - isFunction = typeof value === 'function', - result = false, - m, message, t; - - if (isTopic){ - delete messages[value]; - return; - } - - for ( m in messages ){ - if ( messages.hasOwnProperty( m ) ){ - message = messages[m]; - - if ( isToken && message[value] ){ - delete message[value]; - result = value; - // tokens are unique, so we can just stop here - break; - } else if (isFunction) { - for ( t in message ){ - if (message.hasOwnProperty(t) && message[t] === value){ - delete message[t]; - result = true; - } - } - } - } - } - - return result; - }; - this.messages = messages; - } - - exports.PubSub = PubSub; -}); \ No newline at end of file diff --git a/data/scripts/app.js b/data/scripts/app.js deleted file mode 100644 index 8555ead..0000000 --- a/data/scripts/app.js +++ /dev/null @@ -1,77 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ------------------------------------------------------------------------ - // Modules - - const { AppConfig } = require('./config'); - const { WindowEvents, GlobalEvents } = require('./utils/global-events'); - - const { SessionSyncUI } = require('./session-sync-ui'); - const { SessionSyncModel } = require ('./components/session-sync-model'); - - const { BookmarkManager } = require('./session/bookmarks'); - - // ------------------------------------------------------------------------ - // API - - var findSessionSync = function findSessionSync(callback) - { - BookmarkManager.searchBookmarks({title: 'SessionSync'}) - .then( - function success(bookmarks) { - if (bookmarks.length == 0) - { - BookmarkManager.createBookmark({ - title: 'SessionSync', - parentId: 'menu________' - }).then(callback, function error() { - BookmarkManager.createBookmark({ - title: 'SessionSync', - }).then(callback); - }); - } - else - { - callback(bookmarks[0]); - } - }, - function fail() { - } - ); - }; - - var listSessions = function listSessions() - { - BookmarkManager.getFolderBookmarks(AppConfig.get('storageID'), function(sessions) { - SessionSyncModel.sessions = sessions; - WindowEvents.emit(document, 'ListSessions'); - }); - }; - - var init = function init() - { - SessionSyncUI(); - - findSessionSync(function(bookmark) { - AppConfig.set('storageID', bookmark.id, function() { - listSessions(true); - }); - }); - - AppConfig.init(); - - GlobalEvents.on('update-sessions', listSessions.bind(null, false, false)); - }; - - // ------------------------------------------------------------------------ - // Events - - // ------------------------------------------------------------------------ - // Init - - // ------------------------------------------------------------------------ - // Module exports - - exports.init = init; -}); diff --git a/data/scripts/components/clipboard.js b/data/scripts/components/clipboard.js deleted file mode 100644 index da39d9b..0000000 --- a/data/scripts/components/clipboard.js +++ /dev/null @@ -1,42 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ------------------------------------------------------------------------ - // Modules - - const { HTMLCreator } = require('../utils/dom'); - const { WindowEvents } = require('../utils/global-events'); - - // ------------------------------------------------------------------------ - // API - - function Clipboard() - { - var DomElem = HTMLCreator(); - - var input = DomElem('input', {id: 'clipboard'}); - document.body.appendChild(input); - - // ------------------------------------------------------------------------ - // Events - - function copyText(text) - { - input.value = text; - input.select(); - document.execCommand('copy'); - } - - WindowEvents.on(document, 'CopyToClipboard', copyText); - - // ------------------------------------------------------------------------ - // Public data - - this.DOMRoot = input; - } - - // ------------------------------------------------------------------------ - // Module exports - - exports.Clipboard = Clipboard; -}); diff --git a/data/scripts/components/config-panel.js b/data/scripts/components/config-panel.js deleted file mode 100644 index 772af30..0000000 --- a/data/scripts/components/config-panel.js +++ /dev/null @@ -1,366 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - // App - const { AppConfig } = require('../config'); - const { SessionHistory } = require('./session-history'); - - // Utils - const { WindowEvents, GlobalEvents } = require('../utils/global-events'); - const { HTMLCreator } = require('../utils/dom'); - const DOMComponent = require('../utils/components'); - - // ***************************************************************************** - // Custom Modules - - /* - * Config panel - */ - - function ConfigPanel() - { - // Create DomHealper - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var panel = DomElem('div', {class: 'config-panel'}); - - // Close button - var closeBtn = DomElem('div', {class: 'css-close-button'}); - panel.appendChild(closeBtn); - // ------------------------------------------------------------------------ - // Methods - - function RangeOptionConfig(options) - { - var optionRG = new DOMComponent.RangeControl({ - value: AppConfig.get(options.key), - description: options.name, - step: options.step ? options.step : 1, - minValue: options.minValue ? options.minValue : 1, - maxValue: options.maxValue ? options.maxValue : 1, - onChange: function(value) { - AppConfig.set(options.key, value); - if (options.callback) { - options.callback(value); - } - }, - }); - options.parent.addItem(optionRG.DOMRoot); - AppConfig.onChange(options.key, function(value) { - optionRG.setValue(value, false); - }); - } - - function ToggleOptionConfig(options) - { - var isSwitch = (options.type === 'switch'); - var Toggle = DOMComponent.ToggleButton; - if (isSwitch) { - Toggle = DOMComponent.ToggleSwitch; - options.onState = 'Enabled'; - options.offState = 'Disabled'; - } - - var toggleBtn = new Toggle({ - state: AppConfig.get(options.key), - description: options.name, - onState: options.onState, - offState: options.offState, - callback: function(value) { - AppConfig.set(options.key, value); - if (options.callback) { - options.callback(value); - } - }, - }); - - options.parent.addItem(toggleBtn.DOMRoot); - AppConfig.onChange(options.key, function(value) { - if (isSwitch) - { - toggleBtn.setState(value); - } - else - { - toggleBtn.setValue(value, false); - } - }); - } - - function ConfigSection(options) - { - var sectionContainer = DomElem('div', {class: 'section-container'}); - var sectionTitle = DomElem('div', {class: 'section-title'}); - var title = DomElem('div', {class: 'title'}); - var dropIcon = DomElem('div', {class: 'drop-icon'}); - var sectionBody = DomElem('div', {class: 'section-body'}); - - if (options.id) { - sectionContainer.setAttribute('id', options.id); - } - - title.textContent = options.title; - sectionTitle.appendChild(dropIcon); - sectionTitle.appendChild(title); - - sectionContainer.appendChild(sectionTitle); - sectionContainer.appendChild(sectionBody); - - sectionTitle.addEventListener('click', function() { - - // manual mode - var isCollapsed = sectionContainer.hasAttribute('collapsed'); - if (isCollapsed) { - sectionContainer.removeAttribute('collapsed'); - } else { - sectionBody.style.height = sectionBody.clientHeight + 'px'; - sectionContainer.setAttribute('collapsed', ''); - } - }); - - this.container = sectionContainer; - this.addItem = function(item) { - sectionBody.appendChild(item); - }; - } - - // ------------------------ - // Section: UI scaling - - (function() { - - var section = new ConfigSection({ title: 'UI Scaling' }); - panel.appendChild(section.container); - - if (AppConfig.isPanel()) - { - RangeOptionConfig({ parent: section, name: 'Panel width (px)', key: 'style.panel.width', minValue: 500, maxValue: 800, step: 5}); - RangeOptionConfig({ parent: section, name: 'Panel height (px)', key: 'style.panel.height', minValue: 400, maxValue: 600, step: 5}); - } - RangeOptionConfig({ parent: section, name: 'Header bar', key: 'style.scale.header', minValue: 10, maxValue: 50}); - RangeOptionConfig({ parent: section, name: 'Toolbar', key: 'style.scale.toolbar', minValue: 10, maxValue: 50}); - RangeOptionConfig({ parent: section, name: 'Session list', key: 'style.scale.sessions', minValue: 10, maxValue: 50}); - RangeOptionConfig({ parent: section, name: 'Bookmarks list', key: 'style.scale.bookmarks', minValue: 10, maxValue: 50}); - - })(); - - // ------------------------ - // Section: General settings - - (function() { - - var section = new ConfigSection({ title: 'Session Management' }); - panel.appendChild(section.container); - - ToggleOptionConfig({ - parent: section, - name: 'Lazy loading restore', - key: 'restore.lazy.loading', - type: 'switch', - }); - - ToggleOptionConfig({ - parent: section, - name: 'Reverse restore order', - key: 'restore.reverse.order', - type: 'switch', - }); - - })(); - - // ------------------------ - // Section: General settings - - (function() { - - var section = new ConfigSection({ title: 'General' }); - panel.appendChild(section.container); - - ToggleOptionConfig({ - parent: section, - name: 'Left click', - key: 'bookmark.click.newTab', - onState: 'New tab', - offState: 'Same tab', - }); - - ToggleOptionConfig({ - parent: section, - name: 'Middle click', - key: 'bookmark.middleClick.newTab', - onState: 'New tab', - offState: 'Same tab', - }); - - ToggleOptionConfig({ - parent: section, - type: 'switch', - name: 'Context menu icons', - key: 'context.menu.icons', - onState: 'Show', - offState: 'Disabled', - }); - - ToggleOptionConfig({ - parent: section, - name: 'Favicon service', - key: 'services.favicon.enabled', - type: 'switch', - }); - - ToggleOptionConfig({ - parent: section, - name: 'Troubleshooting', - key: 'troubleshooting', - type: 'switch', - }); - - })(); - - // ------------------------ - // Section: Hotkeys - - (function() { - - if (typeof browser === 'object' && browser.commands.update == undefined) - return; - - var section = new ConfigSection({ title: 'Hotkeys' }); - panel.appendChild(section.container); - - function HotkeyOptionConfig(command) - { - var button = new DOMComponent.ActionButton({ - value: command.shortcut, - description: command.description, - callback: function() { - WindowEvents.emit(document, 'ChangeHotkey', { - title: command.description, - shortcut: button.getValue(), - callback: function (value) { - button.setValue(value); - command.shortcut = value; - GlobalEvents.emit('hotkey.update', command); - } - }); - }, - }); - - return button.DOMRoot; - } - - AppConfig.onChange('config.hotkey.init', function(commands) { - commands.forEach(function (command) { - var action = HotkeyOptionConfig(command); - section.addItem(action); - }); - }); - })(); - - // ------------------------ - // Section: History settings - - (function() { - - var section = new ConfigSection({ title: 'Auto-save History', id: 'history-config' }); - panel.appendChild(section.container); - - SessionHistory.getConfig(function (historyConfig) { - - // Context menu icons - var contextMenuIconsTB = new DOMComponent.ToggleSwitch({ - state: historyConfig.enabled, - description: 'Auto-save sessions', - onState: 'Enabled', - offState: 'Disabled', - callback: function(value) { - historyConfig.enabled = value; - SessionHistory.updateConfig(historyConfig); - if (value == false) { - WindowEvents.emit(document, 'ShowSyncList', { update: true }); - } - WindowEvents.emit(document, 'SetUIState', {'history': value}); - } - }); - section.addItem(contextMenuIconsTB.DOMRoot); - - // Context menu icons - var autoSavePrivateTB = new DOMComponent.ToggleSwitch({ - state: historyConfig.savePrivate, - description: 'Save private windows', - onState: 'Enabled', - offState: 'Disabled', - callback: function(value) { - historyConfig.savePrivate = value; - SessionHistory.updateConfig(historyConfig); - } - }); - section.addItem(autoSavePrivateTB.DOMRoot); - - // Auto-save interval - var saveIntervalRG = new DOMComponent.RangeControl({ - value: historyConfig.interval, - description: 'Save interval (seconds)', - minValue: 10, - onChange: function(value) { - historyConfig.interval = value; - SessionHistory.updateConfig(historyConfig); - }, - }); - section.addItem(saveIntervalRG.DOMRoot); - - // Auto-save slots - var saveSlotsRG = new DOMComponent.RangeControl({ - value: historyConfig.savingSlots, - description: 'Saving entries (max)', - minValue: 2, - onChange: function(value) { - historyConfig.savingSlots = value; - SessionHistory.updateConfig(historyConfig); - }, - }); - section.addItem(saveSlotsRG.DOMRoot); - - // Auto-save expire time - var expireAfterRG = new DOMComponent.RangeControl({ - value: historyConfig.expireTimeHours, - description: 'Delete older than [...] hours', - minValue: 1, - onChange: function(value) { - historyConfig.expireTimeHours = value; - SessionHistory.updateConfig(historyConfig); - }, - }); - section.addItem(expireAfterRG.DOMRoot); - - // Events - WindowEvents.emit(document, 'SetUIState', {'history': historyConfig.enabled}); - - }); - })(); - - // ------------------------------------------------------------------------ - // Events - - closeBtn.addEventListener('click', function() { - WindowEvents.emit(document, 'SetUIState', { config : undefined}); - }); - - // ------------------------------------------------------------------------ - // Public properties - - this.document = document; - this.DOMRoot = panel; - } - - // ***************************************************************************** - // Public API - - exports.ConfigPanel = ConfigPanel; -}); \ No newline at end of file diff --git a/data/scripts/components/notification-system.js b/data/scripts/components/notification-system.js deleted file mode 100644 index 76e67ab..0000000 --- a/data/scripts/components/notification-system.js +++ /dev/null @@ -1,54 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { HTMLCreator } = require('../utils/dom'); - const { WindowEvents } = require('../utils/global-events'); - - // ***************************************************************************** - // NotificationSystem - - function NotificationSystem() - { - var DomElem = HTMLCreator(); - - var panel = DomElem('div', {class : 'notification-system'}); - var info = DomElem('div', {class : 'info'}); - var warning = DomElem('div', {class : 'warning'}); - - panel.appendChild(info); - panel.appendChild(warning); - - // ------------------------------------------------------------------------ - // Events - - var infoTimeout = 0; - - var closeInfoBox = function closeInfoBox() - { - info.removeAttribute('active'); - }; - - var showInfo = function showInfo(options) - { - info.textContent = options.message; - info.setAttribute('active', ''); - clearTimeout(infoTimeout); - infoTimeout = setTimeout(closeInfoBox, options.timeout ? options.timeout : 1500); - }; - - WindowEvents.on(document, 'Notification', showInfo); - - // ------------------------------------------------------------------------ - // Public data - - this.DOMRoot = panel; - } - - // ***************************************************************************** - // Public API - - exports.NotificationSystem = NotificationSystem; -}); \ No newline at end of file diff --git a/data/scripts/components/overlay-system.js b/data/scripts/components/overlay-system.js deleted file mode 100644 index b38ab6d..0000000 --- a/data/scripts/components/overlay-system.js +++ /dev/null @@ -1,66 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { AppConfig } = require('../config'); - const { HTMLCreator } = require('../utils/dom'); - - // ***************************************************************************** - // API - - /* - * Session Hotkeys - */ - - function OverlaySystem() - { - // Create DomHealper - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var overlay = DomElem('div', { id : 'overlay' }); - - // ------------------------------------------------------------------------ - // Events - - var state = false; - - function setState(options) { - if (AppConfig.isPanel()) { - state = options.state; - overlay.setAttribute('state', state); - - if (options.zIndex) { - overlay.style.zIndex = options.zIndex; - } - - if (state == false) { - overlay.removeAttribute('style'); - } - } - } - - function autoHide() { - if (state == true) - setState(false); - } - - // document.addEventListener('click', autoHide); - - // WindowEvents.on(document, 'OverlaySystem', setState); - - // ------------------------------------------------------------------------ - // Public properties - - this.DOMRoot = overlay; - } - - // ***************************************************************************** - // Public API - - exports.OverlaySystem = OverlaySystem; -}); \ No newline at end of file diff --git a/data/scripts/components/session-bookmark-events.js b/data/scripts/components/session-bookmark-events.js deleted file mode 100644 index f6dd6c6..0000000 --- a/data/scripts/components/session-bookmark-events.js +++ /dev/null @@ -1,442 +0,0 @@ -define(function(require, exports) { - - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { AppConfig } = require('../config'); - const { WindowEvents } = require('../utils/global-events'); - const { AutoScroll } = require('../utils/auto-scroll'); - const { DragContext } = require('../utils/drag-context'); - const { SessionSyncModel } = require('./session-sync-model'); - - const { SessionManager } = require('../session/management'); - const { BookmarkManager } = require('../session/bookmarks'); - - // ***************************************************************************** - // API - - // ---------------------------------------------------------------------------- - // Session Events - delegated - - var SessionBookmarkEvents = function SessionBookmarkEvents(container) - { - var startDragEvent = null; - var trackingTelemetry = new Date(); - var offsetRect = {}; - var bookmarkContext = null; - var bookmarkHoverContextID = 0; - var SyncModel = SessionSyncModel.getModel(document); - - function getAttribute(target, attribute) { - try { - return target.getAttribute(attribute); - } - catch (err) { - console.log(err); - return null; - } - } - - function getTargetType(e) { - return getAttribute(e.target, 'type'); - } - - function getSessionID(e) { - return getAttribute(e.target, 'sessionID'); - } - - function getBookmarkID(e) { - return getAttribute(e.target, 'bookmarkID'); - } - - function getTabURL(e) { - return getAttribute(e.target, 'url'); - } - - function getTabID(e) { - var ID = getAttribute(e.target, 'tabID'); - return ID ? ID | 0 : undefined; - } - - function getWindowID(e) { - var ID = getAttribute(e.target, 'windowID'); - return ID ? ID | 0 : undefined; - } - - // -------------------------------------------------------------- - // Session level - - var bookmarkDblClick = function sessionDblClick(e) - { - if (SyncModel.state.session == 'restore') - { - var bookmarkID = getBookmarkID(e); - if (bookmarkID != null) { - WindowEvents.emit(document, 'BookmarkCtxMenu-EditBookmark', bookmarkID); - } - } - }; - - var mouseOver = function mouseOver(e) - { - if (!e.target.hasAttribute('over')) { - return; - } - - switch(SyncModel.state.session) - { - // Bookmark entry - case 'restore': { - var bookmarkID = getBookmarkID(e); - if (bookmarkHoverContextID == bookmarkID) - return; - - bookmarkHoverContextID = bookmarkID; - WindowEvents.emit(document, 'ShowUrlBar', SessionSyncModel.bookmarks[bookmarkID].url); - return; - } - - // Window tab - case 'current': { - var tabID = getTabID(e); - if (tabID != undefined) { - let url = getTabURL(e); - WindowEvents.emit(document, 'ShowUrlBar', url); - } - return; - } - - // History tab - case 'history': { - let url = getTabURL(e); - WindowEvents.emit(document, 'ShowUrlBar', url); - return; - } - } - }; - - var mouseLeave = function mouseLeave() - { - WindowEvents.emit(document, 'HideUrlBar'); - }; - - // -------------------------------------------------------------- - // Mouse Events - - var trackDragging = function _mouseMove(e) - { - var delta = Math.abs(startDragEvent.clientX - e.clientX) > 5 || Math.abs(startDragEvent.clientY - e.clientY) > 5; - if (delta) - { - DragContext.setContext(bookmarkContext); - trackingTelemetry = new Date(); - - offsetRect = container.parentElement.getBoundingClientRect(); - offsetRect.relativeY = offsetRect.y - container.parentElement.offsetTop; - offsetRect.relativeX = offsetRect.x - container.parentElement.offsetLeft; - - bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.relativeY + 'px'; - bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.relativeX + 'px'; - bookmarkContext.DOMRoot.setAttribute('dragging', ''); - - // Events - window.addEventListener('mousemove', mouseDragging); - window.removeEventListener('mousemove', trackDragging); - } - }; - - var cancelDragging = function cancelDragging(e) - { - AutoScroll.stop(); - - bookmarkContext.DOMRoot.setAttribute('dragging', 'drop'); - bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.y + container.scrollTop + 'px'; - bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.x + 'px'; - - setTimeout(function() { - bookmarkContext.DOMRoot.removeAttribute('dragging'); - bookmarkContext.restorePosition(); - }, 50); - window.removeEventListener('mousemove', mouseDragging); - - DragContext.clearContext(); - }; - - var mouseDragging = function mouseMove(e) - { - if (this != window) - return; - - var deltaTime = new Date() - trackingTelemetry; - if (deltaTime < 16) { - return; - } - - var tryScroll = e.clientX > offsetRect.left && e.clientX < offsetRect.right; - - if (tryScroll) - { - if (e.clientY < offsetRect.top) { - AutoScroll.scroll(container, (e.clientY - offsetRect.top)); - } - else if (e.clientY > offsetRect.bottom) { - AutoScroll.scroll(container, (e.clientY - offsetRect.bottom)); - } else { - AutoScroll.stop(); - } - } else { - AutoScroll.stop(); - } - - bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.relativeY + 'px'; - bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.relativeX + 'px'; - trackingTelemetry = new Date(); - }; - - var mouseUp = function _mouseUp(e) - { - switch(SyncModel.state.session) - { - // if restore session preview - case 'restore': { - var bookmarkID = getBookmarkID(e); - - // Test if changing - if (bookmarkID != null) - { - if (bookmarkContext.bookmarkID == bookmarkID) { - // Preview the content if a click event was registered (mouse-up on the same DOM node) - if (!DragContext.hasContext()) { - BookmarkManager.openBookmark({ - url: SessionSyncModel.bookmarks[bookmarkID].url, - mode: BookmarkManager.getOpenMode(0), - favicon: true, - }); - } - } - else - { - if (SessionSyncModel.bookmarks[bookmarkID] != undefined) - { - SessionSyncModel.moveBookmark(bookmarkContext.bookmarkID, bookmarkID); - } - } - } - else - { - if (getTargetType(e) === 'folder') - { - var sessionID = getSessionID(e); - if (sessionID != null && SessionSyncModel.bookmarks[sessionID] != undefined) { - SessionSyncModel.moveBookmarkTo(bookmarkContext.bookmarkID, sessionID); - } - } - } - break; - } - - case 'current': { - let tabID = getTabID(e); - let tabContext = SyncModel.tabs[tabID]; - - if (tabContext) - { - if (tabContext != bookmarkContext) - { - SessionManager.moveTab(bookmarkContext.tab.id, tabContext.tab.index, tabContext.tab.windowId); - } - else - { - if (tabID) { - SessionManager.activateTab(tabID); - var windowID = getWindowID(e); - if (windowID) { - SessionManager.activateWindow(windowID); - } - } - } - } - else - { - if (getTargetType(e) === 'folder') - { - var savingSessionID = getSessionID(e); - if (savingSessionID != null) { - BookmarkManager.createBookmarkFromTab(bookmarkContext.tab, savingSessionID) - .then(function() { - WindowEvents.emit(document, 'Notification', { - message: 'Saved', - }); - }); - } - } - } - } - } - - if (DragContext.hasContext()) - { - cancelDragging(e); - } - else - { - window.removeEventListener('mousemove', trackDragging); - } - - window.removeEventListener('mouseup', mouseUp); - }; - - var bookmarkMouseDown = function _bookmarkMouseDown(e) - { - var target = e.target; - if (DragContext.hasContext()) { - cancelDragging(e); - return; - } - - switch(SyncModel.state.session) - { - // if restore session preview - case 'restore': { - var bookmarkID = getBookmarkID(e); - if (bookmarkID == null) { - return; - } - - bookmarkContext = SyncModel.getBookmark(bookmarkID); - - if (e.button == 0) - { - startDragEvent = e; - window.addEventListener('mousemove', trackDragging); - window.addEventListener('mouseup', mouseUp); - return; - } - - // Middle Click - if (e.button == 1) - { - BookmarkManager.openBookmark({ - url: SessionSyncModel.bookmarks[bookmarkID].url, - mode: BookmarkManager.getOpenMode(1), - favicon: true, - }); - return; - } - - return; - } - - // If active session preview - case 'current': { - - if (e.button == 0) - { - let tabID = getTabID(e); - if (tabID != undefined) { - bookmarkContext = SyncModel.tabs[tabID]; - if (bookmarkContext != undefined) { - startDragEvent = e; - window.addEventListener('mousemove', trackDragging); - window.addEventListener('mouseup', mouseUp); - return; - } - } - else { - let windowID = getWindowID(e); - if (windowID) { - SessionManager.activateWindow(windowID); - } - } - } - return; - } - - // if history session preview - case 'history': { - if (target.hasAttribute('url')) - { - let url = getTabURL(e); - - switch (e.button) - { - case 0: - case 1: { - BookmarkManager.openBookmark({ - url: url, - mode: BookmarkManager.getOpenMode(e.button) - }); - - break; - } - } - } - else - { - if (e.button == 0) - { - let windowID = getWindowID(e); - if (windowID != undefined) { - WindowEvents.emit(document, 'HistorySessionRestoreWindow', windowID); - } - } - } - return; - } - } - }; - - var bookmarkContextMenu = function bookmarkContextMenu(e) - { - switch (SyncModel.state.session) - { - case 'restore': { - var bookmarkID = getBookmarkID(e); - if (bookmarkID == null) - return; - - WindowEvents.emit(document, 'BookmarkCtxMenu-Open', { - context: bookmarkID, - event: e - }); - break; - } - - case 'history': { - let url = getTabURL(e); - if (url) { - WindowEvents.emit(document, 'HistoryMarksCtxMenu-Open', { - context: url, - event: e - }); - } - } - } - }; - - // -------------------------------------------------------------- - // App Events - - - // ------------------------------------------------------------------------ - // Init Code Events - - container.addEventListener('scroll', function () { - AppConfig.set('state.scrollTop.' + SyncModel.state.session, container.scrollTop); - }); - container.addEventListener('contextmenu', bookmarkContextMenu); - container.addEventListener('mousedown', bookmarkMouseDown); - - container.addEventListener('mouseover', mouseOver); - container.addEventListener('mouseleave', mouseLeave); - - container.addEventListener('dblclick', bookmarkDblClick); - }; - - // ***************************************************************************** - // Public API - - exports.SessionBookmarkEvents = SessionBookmarkEvents; -}); \ No newline at end of file diff --git a/data/scripts/components/session-bookmark.js b/data/scripts/components/session-bookmark.js deleted file mode 100644 index 52e9b7d..0000000 --- a/data/scripts/components/session-bookmark.js +++ /dev/null @@ -1,262 +0,0 @@ -define(function(require, exports) { - - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { HTMLCreator } = require('../utils/dom'); - const { AutoScroll } = require('../utils/auto-scroll'); - - const { BookmarkManager } = require('../session/bookmarks'); - - // ***************************************************************************** - // API - - const bookmarkOffset = 2.2; // height + 2 in em - - function setTabFavicon(faviconNode, faviconURL) - { - faviconNode.style.background = 'url("' + faviconURL + '")'; - faviconNode.style.backgroundRepeat = 'no-repeat'; - faviconNode.style.backgroundPosition = 'center center'; - faviconNode.style.backgroundSize = 'contain'; - } - - /* - * Session Bookmark - * Used for interacting with a Firefox bookmark item - */ - - function SessionBookmark(bookmark) - { - var DomElem = HTMLCreator(); - - var box = DomElem('div', {class : 'bookmark'}); - box.setAttribute('pinned', bookmark.pinned); - - var favicon = DomElem('div', {class : 'favicon'}); - favicon.setAttribute('bookmarkID', bookmark.id); - favicon.setAttribute('over', ''); - - var title = DomElem('div', {class : 'text'}); - title.setAttribute('bookmarkID', bookmark.id); - title.setAttribute('over', ''); - title.textContent = bookmark.title; - - box.appendChild(favicon); - box.appendChild(title); - - BookmarkManager.getFaviconUrl(bookmark.url, function (url) { - setTabFavicon(favicon, url); - }); - - // ------------------------------------------------------------------------ - // Public data - - this.bookmark = bookmark; - this.DOMtitle = title; - this.DOMfavicon = favicon; - this.bookmarkID = bookmark.id; - this.DOMRoot = box; - } - - SessionBookmark.prototype.setTitle = function setTitle(title) - { - this.DOMtitle.textContent = title; - }; - - SessionBookmark.prototype.restorePosition = function restorePosition() - { - this.DOMRoot.style.left = '0px'; - this.DOMRoot.style.top = this.position * bookmarkOffset + 'em'; - }; - - SessionBookmark.prototype.setVirtualPosition = function setVirtualPosition(position) - { - this.position = position; - // this.DOMtitle.textContent = '[' + position + '] ' + this.bookmark.title, - this.DOMRoot.style.top = position * bookmarkOffset + 'em'; - }; - - SessionBookmark.prototype.remove = function remove() - { - var box = this.DOMRoot; - if (box.parentElement) - box.parentElement.removeChild(box); - }; - - SessionBookmark.prototype.highlight = function highlight() - { - this.DOMRoot.setAttribute('highlight', 'true'); - setTimeout(function() { - this.DOMRoot.removeAttribute('highlight'); - }.bind(this), 2000); - - var offsetStep = this.DOMRoot.clientHeight; - var parentNode = this.DOMRoot.parentElement; - var offset = this.DOMRoot.offsetTop - parentNode.clientHeight / 2 + offsetStep; - AutoScroll.scrollTo(parentNode, offset, 0.25); - }; - - /* - * SessionWindow - * Used as separator for different windows - */ - - function SessionWindow(windowID, windowIndex, index) - { - var DomElem = HTMLCreator(); - - var box = DomElem('div', {class : 'window-separator'}); - box.style.top = index * bookmarkOffset + 'em'; - - var text = DomElem('div', {class : 'text'}); - text.textContent = 'Window ' + (windowIndex + 1); - text.setAttribute('windowID', windowID); - - box.appendChild(text); - - this.DOMRoot = box; - } - - /* - * SessionTab - * Used for displaying tabs from the current window - */ - - function SessionTab(tab, indexOffset) - { - var DomElem = HTMLCreator(); - - var box = DomElem('div', {class : 'bookmark'}); - box.style.top = (tab.index + indexOffset) * bookmarkOffset + 'em'; - box.setAttribute('pinned', tab.pinned); - box.setAttribute('url', tab.url); - - var favicon = DomElem('div', {class : 'favicon'}); - favicon.setAttribute('tabID', tab.id); - favicon.setAttribute('windowID', tab.windowId); - favicon.setAttribute('url', tab.url); - favicon.setAttribute('over', ''); - - var text = DomElem('div', {class : 'text'}); - text.setAttribute('tabID', tab.id); - text.setAttribute('windowID', tab.windowId); - text.setAttribute('url', tab.url); - text.setAttribute('over', ''); - - if (tab.favIconUrl) { - setTabFavicon(favicon, tab.favIconUrl); - } - - text.textContent = tab.title; - box.appendChild(favicon); - box.appendChild(text); - - this.tab = tab; - this.position = tab.index + indexOffset; - this.DOMRoot = box; - } - - SessionTab.prototype.restorePosition = function restorePosition() - { - this.DOMRoot.style.left = '0px'; - this.DOMRoot.style.top = this.position * bookmarkOffset + 'em'; - }; - - /* - * SessionSeparator - * Used as separator for different windows - */ - - function HistoryWindow(sessionWindowInfo, windowID, index) - { - var DomElem = HTMLCreator(); - - var box = DomElem('div', {class : 'history-window-separator'}); - box.style.top = index * bookmarkOffset + 'em'; - box.setAttribute('windowID', windowID); - - var text = DomElem('div', {class : 'text'}); - text.setAttribute('windowID', windowID); - text.textContent = 'Window ' + windowID; - - if (!sessionWindowInfo.active) { - text.textContent += ' (closed)'; - } - - box.appendChild(text); - - this.DOMRoot = box; - } - - /* - * HistoryTab - * Used for displaying tabs from the history windows - */ - - function HistoryTab(tab, indexOffset) - { - var DomElem = HTMLCreator(); - - var box = DomElem('div', {class : 'bookmark'}); - box.style.top = (tab.index + indexOffset) * bookmarkOffset + 'em'; - box.setAttribute('url', tab.url); - box.setAttribute('pinned', tab.pinned); - - var favicon = DomElem('div', {class : 'favicon'}); - favicon.setAttribute('url', tab.url); - favicon.setAttribute('over', ''); - - var text = DomElem('div', {class : 'text'}); - text.setAttribute('url', tab.url); - text.setAttribute('over', ''); - text.textContent = tab.title; - - if (tab.favIconUrl) { - setTabFavicon(favicon, tab.favIconUrl); - } - - box.appendChild(favicon); - box.appendChild(text); - - this.DOMRoot = box; - } - - // var SortMethods = { - - // 'name-asc' : function sortByNameAscending(a, b) { - // return a.title_lowercase > b.title_lowercase; - // }, - - // 'name-desc' : function sortByNameDescending(a, b) { - // return a.title_lowercase < b.title_lowercase; - // }, - - // 'position-asc' : function sortByPositionAscending(a, b) { - // return a.index > b.index; - // }, - - // 'position-desc' : function sortByPositionDescending(a, b) { - // return a.index < b.index; - // }, - - // 'date-asc' : function sortByDateAscending(a, b) { - // return a.date > b.date; - // }, - - // 'date-desc' : function sortByDateDescending(a, b) { - // return a.date < b.date; - // } - // }; - - // ***************************************************************************** - // Public API - - exports.SessionBookmark = SessionBookmark; - exports.SessionTab = SessionTab; - exports.SessionWindow = SessionWindow; - exports.HistoryWindow = HistoryWindow; - exports.HistoryTab = HistoryTab; -}); \ No newline at end of file diff --git a/data/scripts/components/session-container.js b/data/scripts/components/session-container.js deleted file mode 100644 index 747ad60..0000000 --- a/data/scripts/components/session-container.js +++ /dev/null @@ -1,440 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - // App - const { AppConfig } = require('../config'); - const { SessionBookmark, SessionTab, SessionWindow, HistoryWindow, HistoryTab } = require('./session-bookmark'); - const { SessionBookmarkEvents } = require('./session-bookmark-events'); - const { SessionSyncModel } = require('./session-sync-model'); - - // Utils - const { HTMLCreator } = require('../utils/dom'); - const { WindowEvents, GlobalEvents } = require('../utils/global-events'); - - // Components - const { BookmarkManager } = require('../session/bookmarks'); - const { SessionManager } = require('../session/management'); - const { SessionHistory } = require('./session-history'); - - // ***************************************************************************** - // APIconst { WindowEvents, GlobalEvents } = require('../utils/global-events'); - - /* - * SessionContainer UI - * Panel for listing session information (Firefox Bookmark Folder) and children bookmarks - */ - - function SessionContainer() - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Init UI - - var container = DomElem('div', {class: 'session-container'}); - container.style.fontSize = AppConfig.get('style.scale.bookmarks') + 'px'; - - var bookmarks = DomElem('div', {class: 'bookmarks'}); - container.appendChild(bookmarks); - - // ------------------------------------------------------------------------ - // User events - - function isValidSessionID(ID) - { - return (SessionSyncModel.bookmarks[ID] !== undefined && - SessionSyncModel.bookmarks[ID].type === 'folder' && - SessionSyncModel.bookmarks[ID].parentId == AppConfig.get('storageID')); - } - - function restoreHistoryWindow(sessionInfo) - { - var list = []; - sessionInfo.windows.forEach(historyWindow => { - list.push(historyWindow.tabs); - }); - SessionManager.loadBookmarksNewWindow(list); - } - - WindowEvents.on(document, 'HistorySessionSave', this.saveHistorySession.bind(this)); - - WindowEvents.on(document, 'HistorySessionRestore', function (index) { - SessionHistory.getHistorySession(index, function (sessionInfo) { - restoreHistoryWindow(sessionInfo); - }); - }); - - WindowEvents.on(document, 'HistorySessionRestoreWindow', function (windowID) { - if (this.SyncModel.state.session === 'history') { - if (this.selectedHistorySession.active == true) - { - var activeSessionWindows = Object.values(this.selectedHistorySession.windows); - if (activeSessionWindows[windowID].active) { - SessionManager.activateWindow(activeSessionWindows[windowID].id); - } - else { - SessionManager.loadBookmarksNewWindow([activeSessionWindows[windowID].tabs]); - } - } - else { - SessionManager.loadBookmarksNewWindow([this.selectedHistorySession.windows[windowID].tabs]); - } - } - }.bind(this)); - - WindowEvents.on(document, 'MenuRestoreClick', function() { - if (isValidSessionID(this.activeSessionID)) { - SessionManager.restoreSession(this.activeSessionID); - } - }.bind(this)); - - WindowEvents.on(document, 'MenuRestoreNewWindow', function() { - - if (isValidSessionID(this.activeSessionID)) { - SessionManager.restoreNewWindow(this.activeSessionID); - } - - // history session - if (this.SyncModel.state.session === 'history') { - restoreHistoryWindow(this.selectedHistorySession); - } - }.bind(this)); - - WindowEvents.on(document, 'ReplaceSessionAll', function() { - if (isValidSessionID(this.activeSessionID)) { - SessionManager.replaceSessionAll(this.activeSessionID); - } - }.bind(this)); - - WindowEvents.on(document, 'ReplaceSession', function() { - if (isValidSessionID(this.activeSessionID)) { - SessionManager.replaceSession(this.activeSessionID); - } - }.bind(this)); - - WindowEvents.on(document, 'MenuMergeSessions', function(e) { - if (isValidSessionID(this.activeSessionID)) { - WindowEvents.emit(document, 'ConfirmBox-Open', { - event: e, - message: 'Merge selected session and active window session?', - callback: function() { - SessionManager.mergeSessions(this.activeSessionID); - }.bind(this) - }); - } - }.bind(this)); - - WindowEvents.on(document, 'MenuReplaceSession', function(e) { - if (isValidSessionID(this.activeSessionID)) { - WindowEvents.emit(document, 'ConfirmBox-Open', { - event: e, - message: 'Overwrite the selected session with active window session?', - callback: function() { - SessionManager.overwriteSession(this.activeSessionID); - }.bind(this) - }); - } - }.bind(this)); - - WindowEvents.on(document, 'MenuAddCurrentTab', function() { - if (isValidSessionID(this.activeSessionID)) { - this.bookmarkCurrentTab(); - } - }.bind(this)); - - WindowEvents.on(document, 'MenuSaveSession', function() { - // do not save if the session is already saved - if (isValidSessionID(this.activeSessionID)) { - return; - } - - switch(this.SyncModel.state.session) - { - case 'current': - SessionManager.saveActiveSession(); - break; - - case 'history': - this.saveHistorySession(); - break; - } - - }.bind(this)); - - WindowEvents.on(document, 'MenuFixLazySession', function() { - if (isValidSessionID(this.activeSessionID)) { - SessionManager.fixLazySession(this.activeSessionID); - } - }.bind(this)); - - GlobalEvents.on('UpdateCurrentSession', function() { - if (this.SyncModel.state.session == 'current') { - this.showCurrentSession(); - } - }.bind(this)); - - WindowEvents.on(document, 'EditSelectedSession', function() { - WindowEvents.emit(document, 'SessionContextMenu-EditSession', this.activeSessionID); - }.bind(this)); - - WindowEvents.on(document, 'ShowCurrentSession', this.showCurrentSession.bind(this)); - - WindowEvents.on(document, 'ShowHistorySession', this.showHistorySession.bind(this)); - - WindowEvents.on(document, 'ViewSession', this.previewSession.bind(this)); - - AppConfig.onChange('style.scale.bookmarks', function(size) { - container.style.fontSize = size + 'px'; - }); - - AppConfig.onChange('session.save', function(obj) { - if (this.SyncModel.state.session == 'current') { - this.showCurrentSession(); - } - }.bind(this)); - - WindowEvents.on(document, 'SessionDescriptionChanged', function(sessionID) { - if (this.activeSessionID == sessionID) - { - this.updateSessionInfo(); - } - }.bind(this)); - - // Tracked events - - SessionBookmarkEvents(bookmarks); - - var updateTimeout = null; - function updateOnTabEvent() - { - if (SessionManager.tabTracking()) - { - if (updateTimeout == null) { - updateTimeout = setTimeout(function () { - updateTimeout = null; - GlobalEvents.emit('UpdateCurrentSession'); - }, 1000); - } - } - } - - // Tab updates tracking - var trackedEvents = ['onUpdated', 'onCreated', 'onDetached', 'onMoved', 'onAttached', 'onRemoved']; - trackedEvents.forEach(function (eventType) { - browser.tabs[eventType].addListener(updateOnTabEvent); - }.bind(this)); - - // ------------------------------------------------------------------------ - // Public data - - this.DOMBookmarks = bookmarks; - this.DOMRoot = container; - - this.SyncModel = SessionSyncModel.getModel(document); - } - - SessionContainer.prototype.setUIState = function setUIState(state, headerTitle, sessionID) - { - WindowEvents.emit(document, 'SetUIState', {'session': state}); - WindowEvents.emit(document, 'SetSessionDescription', headerTitle); - - this.DOMBookmarks.textContent = ''; - this.activeSessionID = sessionID; - this.selectedHistorySession = undefined; - }; - - SessionContainer.prototype.updateSessionInfo = function updateSessionInfo() - { - var session = SessionSyncModel.bookmarks[this.activeSessionID]; - WindowEvents.emit(document, 'SetSessionDescription', session.title); - WindowEvents.emit(document, 'SetSessionDate', session.dateAdded); - }; - - SessionContainer.prototype.showCurrentSession = function showCurrentSession() - { - var DOMBookmarks = this.DOMBookmarks; - this.setUIState('current', 'Active session', 0); - - SessionManager.getCurrentWindow(function(mozWindow) { - - // Get non-private windows - if (mozWindow.incognito == true || AppConfig.get('session.save').allWindows == false) - { - mozWindow.tabs.forEach(function (tab) { - let sessionTab = new SessionTab(tab, 0, -1); - this.SyncModel.tabs[sessionTab.tab.id] = sessionTab; - DOMBookmarks.appendChild(sessionTab.DOMRoot); - }.bind(this)); - - this.restoreScrollTop('current'); - } - else - { - // Get non-private windows - var tabIndex = 0; - var windowIndex = 0; // display index in UI - ignores private windows - - SessionManager.getAllWindows(function(windows) { - for (let mozWindow of windows) - { - if (!mozWindow.incognito) - { - // Add window separator - var sessionWindow = new SessionWindow(mozWindow.id, windowIndex, tabIndex); - DOMBookmarks.appendChild(sessionWindow.DOMRoot); - tabIndex++; - - var globalOffset = tabIndex; - - // Add window tabs - for (let i in mozWindow.tabs) - { - let tab = mozWindow.tabs[i]; - let sessionTab = new SessionTab(tab, globalOffset); - this.SyncModel.tabs[sessionTab.tab.id] = sessionTab; - DOMBookmarks.appendChild(sessionTab.DOMRoot); - tabIndex++; - } - - windowIndex++; - } - } - - this.restoreScrollTop('current'); - }.bind(this)); - } - }.bind(this)); - }; - - SessionContainer.prototype.showHistorySession = function showHistorySession(sessionInfo) - { - var newSession = (this.selectedHistorySession != undefined) && (this.selectedHistorySession != sessionInfo); - - this.setUIState('history', (new Date(sessionInfo.lastSave)).toLocaleString(), -1); - this.selectedHistorySession = sessionInfo; - - var tabIndex = 0; - var windowIndex = 0; - - for (let key in sessionInfo.windows) - { - // Add window separator - var sessionWindow = new HistoryWindow(sessionInfo.windows[key], windowIndex, tabIndex); - this.DOMBookmarks.appendChild(sessionWindow.DOMRoot); - tabIndex++; - - var globalOffset = tabIndex; - - // Add window tabs - var windowTabs = sessionInfo.windows[key].tabs; - for (var i in windowTabs) - { - var tab = windowTabs[i]; - tab.index = i | 0; - var sessionTab = new HistoryTab(tab, globalOffset); - this.DOMBookmarks.appendChild(sessionTab.DOMRoot); - tabIndex++; - } - - windowIndex++; - } - - this.restoreScrollTop('history', newSession ? 0 : undefined); - }; - - SessionContainer.prototype.previewSession = function previewSession(sessionID) - { - var session = SessionSyncModel.bookmarks[sessionID]; - this.setUIState('restore', '', sessionID); - - BookmarkManager.getFolderBookmarks(session.id, function (marks) { - - WindowEvents.emit(document, 'SetPromiseSession', { sessionID: session.id, update: false } ); - - var len = marks.length; - for (var i = 0; i < len; i++) - { - var bookmark = new SessionBookmark(marks[i]); - bookmark.setVirtualPosition(marks[i].index); - - SessionSyncModel.bookmarks[marks[i].id] = marks[i]; - this.SyncModel.setBookmark(marks[i].id, bookmark); - this.DOMBookmarks.appendChild(bookmark.DOMRoot); - } - - this.updateSessionInfo(); - this.restoreScrollTop('restore'); - - }.bind(this)); - }; - - SessionContainer.prototype.restoreScrollTop = function restoreScrollTop(state, forcedValue) - { - var scrollTop = AppConfig.get('state.scrollTop.' + state); - this.DOMBookmarks.scrollTop = (forcedValue !== undefined) ? forcedValue : scrollTop; - }; - - SessionContainer.prototype.bookmarkCurrentTab = function bookmarkCurrentTab() - { - // Test if already saved in this session - SessionManager.getCurrentTab(function(activeTab) { - - var sessionID = this.activeSessionID; - var bookmarks = SessionSyncModel.bookmarks; - - for (var i in bookmarks) - { - if (bookmarks[i].parentId == sessionID && bookmarks[i].url == activeTab.url) - { - WindowEvents.emit(document, 'Notification', { - message: 'Already Saved', - }); - - var bookmarkElem = this.SyncModel.getBookmark(bookmarks[i].id); - bookmarkElem.highlight(); - return; - } - } - - // Add bookmark into the session - - BookmarkManager.createBookmarkFromTab(activeTab, sessionID) - .then(function (mark) { - var bookmark = new SessionBookmark(mark); - - SessionSyncModel.bookmarks[mark.id] = mark; - this.SyncModel.setBookmark(mark.id, bookmark); - this.DOMBookmarks.appendChild(bookmark.DOMRoot); - - bookmark.setVirtualPosition(mark.index); - bookmark.highlight(); - - }.bind(this)); - - }.bind(this)); - }; - - SessionContainer.prototype.saveHistorySession = function saveHistorySession(sessionIndex) - { - if (sessionIndex == undefined) - { - // When Save button from toolbar is pressed - SessionManager.saveHistorySession(this.selectedHistorySession); - } - else - { - // Save from context menu - SessionHistory.getHistorySession(sessionIndex, function (sessionInfo) { - SessionManager.saveHistorySession(sessionInfo); - }); - } - }; - - // ***************************************************************************** - // Public API - - exports.SessionContainer = SessionContainer; -}); \ No newline at end of file diff --git a/data/scripts/components/session-filtering.js b/data/scripts/components/session-filtering.js deleted file mode 100644 index 706709a..0000000 --- a/data/scripts/components/session-filtering.js +++ /dev/null @@ -1,139 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { AppConfig } = require('../config'); - const { WindowEvents, GlobalEvents } = require('../utils/global-events'); - const { HTMLCreator } = require('../utils/dom'); - - // ***************************************************************************** - // API - - /* - * SessionFiltering - */ - - function SessionFiltering() - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var panel = DomElem('div', {class: 'search-bar', content: 'false', match: 'true'}); - - var createSession = DomElem('div', {class: 'create-session' }); - panel.appendChild(createSession); - - var close = DomElem('div', {class: 'close' }); - panel.appendChild(close); - - var searchInput = DomElem('input', {type: 'text', class: 'search', placeholder:'Search'}); - panel.appendChild(searchInput); - - // ------------------------------------------------------------------------ - // Methods - - var timeOut = null; - var prevValue = ''; - - var dismiss = function dismiss() - { - clearTimeout(timeOut); - if (searchInput.value.length) { - prevValue = ''; - searchInput.value = prevValue; - WindowEvents.emit(document, 'FilterSessions', searchInput.value); - } - - searchInput.blur(); - onBlur(); - - panel.setAttribute('content', searchInput.value != ''); - WindowEvents.emit(document, 'SetUIState', {'search-box-content': searchInput.value != ''}); - }; - - var onKeyDown = function() - { - if (prevValue == searchInput.value) - return; - - prevValue = searchInput.value; - panel.setAttribute('match', 'false'); - panel.setAttribute('content', searchInput.value != ''); - WindowEvents.emit(document, 'SetUIState', {'search-box-content': searchInput.value != ''}); - clearTimeout(timeOut); - timeOut = setTimeout(function () { - WindowEvents.emit(document, 'FilterSessions', searchInput.value); - }, 200); - }; - - var onFocus = function onFocus() { - panel.setAttribute('active', true); - document.addEventListener('keyup', onKeyDown); - }; - - var onBlur = function onBlur() { - if (searchInput.value == '') { - panel.setAttribute('active', false); - } - document.removeEventListener('keyup', onKeyDown); - }; - - // ------------------------------------------------------------------------ - // Events - - // Tooltip events - createSession.addEventListener('mouseover', function(e) { - WindowEvents.emit(document, 'ShowTooltip', { - node: e.target, - message: 'Create session', - }); - }); - - createSession.addEventListener('mouseleave', function() { - WindowEvents.emit(document, 'HideTooltip'); - }); - - createSession.addEventListener('click', function() { - GlobalEvents.emit('CreateNewSession', searchInput.value); - WindowEvents.emit(document, 'HideTooltip'); - }); - - close.addEventListener('click', dismiss); - - searchInput.addEventListener('focus', onFocus); - searchInput.addEventListener('blur', onBlur); - - WindowEvents.on(document, 'FilterSessionsExactMatch', function(isMatch) { - panel.setAttribute('match', isMatch); - }); - - WindowEvents.on(document, 'FocusSessionFilter', function () { - searchInput.focus(); - }); - - // ------------------------------------------------------------------------ - // Init state - - var settingsKey = 'session.active.filter'; - - AppConfig.onChange(settingsKey, function(value) { - if (value && value.length > 0) { - searchInput.value = value; - panel.setAttribute('active', true); - onKeyDown(); - } - }); - - // ------------------------------------------------------------------------ - // Public properties - - this.DOMRoot = panel; - } - - // Public API - exports.SessionFiltering = SessionFiltering; -}); \ No newline at end of file diff --git a/data/scripts/components/session-folder-events.js b/data/scripts/components/session-folder-events.js deleted file mode 100644 index 5f2ca8a..0000000 --- a/data/scripts/components/session-folder-events.js +++ /dev/null @@ -1,290 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - // App - const { AppConfig } = require('../config'); - const { SessionSyncModel } = require('./session-sync-model'); - - // Utils - const { WindowEvents } = require('../utils/global-events'); - const { AutoScroll } = require('../utils/auto-scroll'); - const { DragContext } = require('../utils/drag-context'); - - // ***************************************************************************** - // API - - // ---------------------------------------------------------------------------- - // Session Folder Events - delegated - - var SessionFolderEvents = function SessionFolderEvents(container) - { - var startDragEvent = null; - var trackingTelemetry; - var offsetRect = {}; - var bookmarkContext = null; - var SyncModel = SessionSyncModel.getModel(document); - - function getAttribute(target, attribute) { - try { - return target.getAttribute(attribute); - } - catch (err) { - console.log(err); - return null; - } - } - - function getBookmarkID(e) { - return getAttribute(e.target, 'sessionID'); - } - - // -------------------------------------------------------------- - // Drag Events - - var trackDragging = function _mouseMove(e) - { - var delta = Math.abs(startDragEvent.clientX - e.clientX) > 5 || Math.abs(startDragEvent.clientY - e.clientY) > 5; - if (delta) { - DragContext.setContext(bookmarkContext); - trackingTelemetry = new Date(); - - offsetRect = container.parentElement.parentElement.getBoundingClientRect(); - offsetRect.relativeY = offsetRect.y - container.parentElement.offsetTop; - offsetRect.relativeX = offsetRect.x - container.parentElement.offsetLeft; - - bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.relativeY + 'px'; - bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.relativeX + 'px'; - bookmarkContext.DOMRoot.setAttribute('dragging', ''); - - // Events - window.addEventListener('mousemove', mouseDragging); - window.removeEventListener('mousemove', trackDragging); - } - }; - - var cancelDragging = function cancelDragging(e) - { - AutoScroll.stop(); - - bookmarkContext.DOMRoot.setAttribute('dragging', 'drop'); - bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.y + container.scrollTop + 'px'; - bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.x + 'px'; - - setTimeout(function() { - bookmarkContext.DOMRoot.removeAttribute('dragging'); - bookmarkContext.restorePosition(); - }, 50); - window.removeEventListener('mousemove', mouseDragging); - - DragContext.clearContext(); - }; - - var mouseDragging = function mouseMove(e) - { - if (this != window) - return; - var deltaTime = new Date() - trackingTelemetry; - if (deltaTime < 16) { - return; - } - - var tryScroll = e.clientX > offsetRect.left && e.clientX < offsetRect.right; - - if (tryScroll) - { - if (e.clientY < offsetRect.top) { - AutoScroll.scroll(container, (e.clientY - offsetRect.top)); - } - else if (e.clientY > offsetRect.bottom) { - AutoScroll.scroll(container, (e.clientY - offsetRect.bottom)); - } else { - AutoScroll.stop(); - } - } else { - AutoScroll.stop(); - } - - bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.relativeY + 'px'; - bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.relativeX + 'px'; - trackingTelemetry = new Date(); - }; - - var mouseUp = function _mouseUp(e) - { - // Test if changing - if (e.target.className == 'folder') - { - var sessionID = getBookmarkID(e); - - // Preview the content if a click event was registered (mouse-up on the same DOM node) - if (bookmarkContext.bookmarkID == sessionID) - { - if (!DragContext.hasContext()) { - WindowEvents.emit(document, 'SelectSyncSession', bookmarkContext); - } - } - else - { - if (AppConfig.get('session.sorting') === 'position-asc') - { - SessionSyncModel.moveBookmark(bookmarkContext.bookmarkID, sessionID); - } - else - { - WindowEvents.emit(document, 'Notification', { - message: 'Rearage function available only when items are sorted ascending by position', - timeout: 3000, - }); - } - } - } - - if (DragContext.hasContext()) - { - cancelDragging(e); - } - else - { - window.removeEventListener('mousemove', trackDragging); - } - - window.removeEventListener('mouseup', mouseUp); - }; - - var folderMouseDown = function _folderMouseDown(e) - { - if (DragContext.hasContext()) { - cancelDragging(e); - return; - } - - var sessionID = getBookmarkID(e); - bookmarkContext = SyncModel.getBookmark(sessionID); - - if (e.button == 0) { - startDragEvent = e; - window.addEventListener('mousemove', trackDragging); - window.addEventListener('mouseup', mouseUp); - return; - } - }; - - var folderContextMenu = function folderContextMenu(e) - { - if (bookmarkContext) - { - var contextMenu = undefined; - switch (bookmarkContext.bookmark.type) - { - case 'folder': - contextMenu = 'SessionContextMenu'; - break; - - case 'bookmark': - contextMenu = 'SessionContextBookmarkMenu'; - break; - - case 'separator': - contextMenu = 'SessionContextSeparatorMenu'; - break; - - default: - return; - } - - WindowEvents.emit(document, contextMenu + '-Open', { - context: bookmarkContext.bookmark.id, - event: e - }); - } - else { - WindowEvents.emit(document, 'SessionListMenu-Open', { event: e }); - } - }; - - var sessionDblClick = function sessionDblClick(e) - { - if (bookmarkContext) - { - switch (bookmarkContext.bookmark.type) - { - case 'folder': - WindowEvents.emit(document, 'SessionContextMenu-EditSession', bookmarkContext.bookmarkID); - break; - - case 'bookmark': - WindowEvents.emit(document, 'BookmarkCtxMenu-EditBookmark', bookmarkContext.bookmarkID); - break; - } - } - }; - - // ------------------------------------------------------------------------ - // Init Code Events - - container.addEventListener('dblclick', sessionDblClick); - container.addEventListener('mousedown', folderMouseDown); - container.addEventListener('contextmenu', folderContextMenu); - }; - - // ---------------------------------------------------------------------------- - // History Session Folder Events - delegated - - var SessionHistoryEvents = function SessionHistoryEvents(container) - { - var historyContext = null; - - function getAttribute(target, attribute) { - try { - return target.getAttribute(attribute); - } - catch (err) { - console.log(err); - return null; - } - } - - var onMouseDown = function onMouseDown(e) - { - if (e.button != 2) - return; - - var target = e.target; - if (target.className == 'history-node') { - WindowEvents.emit(document, 'HistorySessionCtxMenu-Open', { - context: e.target, - event: e - }); - } - else - { - WindowEvents.emit(document, 'HistoryListCtxMenu-Open', {event: e }); - } - }; - - var onClick = function onClick(e) - { - if (e.target.className === 'history-node') - { - // WindowEvents.emit(document, 'HistoryListCtxMenu-Open', {event: e }); - // this.selectHistorySession(e.target); - } - }; - - // ------------------------------------------------------------------------ - // Init Code Events - - container.addEventListener('click', onClick); - container.addEventListener('mousedown', onMouseDown); - }; - - // ***************************************************************************** - // Public API - - exports.SessionFolderEvents = SessionFolderEvents; - exports.SessionHistoryEvents = SessionHistoryEvents; - -}); \ No newline at end of file diff --git a/data/scripts/components/session-folder.js b/data/scripts/components/session-folder.js deleted file mode 100644 index 53b2fbc..0000000 --- a/data/scripts/components/session-folder.js +++ /dev/null @@ -1,152 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - // Utils - const { HTMLCreator } = require('../utils/dom'); - - // ***************************************************************************** - // API - - /* - * Session Bookmark - * Used for interacting with a Firefox bookmark item - */ - function SessionFolder(bookmark) - { - var DomElem = HTMLCreator(); - var folder = DomElem('div', {class: 'folder', type: bookmark.type}); - folder.setAttribute('sessionID', bookmark.id); - - this.DOMRoot = folder; - this.updateFrom(bookmark); - - // ------------------------------------------------------------------------ - // Public data - - this.visible = true; - this.bookmarkID = bookmark.id; - } - - SessionFolder.prototype.folderOffset = 2.7; // em; - - SessionFolder.prototype.updateFrom = function updateFrom(bookmark) - { - this.bookmark = bookmark; - this.bookmark.title_lowercase = this.bookmark.title.toLowerCase(); - this.setTitle(bookmark.title); - }; - - SessionFolder.prototype.isVisible = function isVisible() - { - return this.visible; - }; - - SessionFolder.prototype.setVisible = function setVisible() - { - this.visible = true; - this.DOMRoot.removeAttribute('filter'); - }; - - SessionFolder.prototype.match = function match(expression) - { - var name = this.bookmark.title_lowercase; - if (name.indexOf(expression) != -1) { - this.visible = true; - this.DOMRoot.removeAttribute('filter'); - return true; - } - - this.visible = false; - this.DOMRoot.setAttribute('filter', ''); - return false; - }; - - SessionFolder.prototype.restorePosition = function restorePosition() - { - this.DOMRoot.style.left = '0px'; - this.DOMRoot.style.top = this.position * this.folderOffset + 'em'; - }; - - SessionFolder.prototype.setVirtualPosition = function setVirtualPosition(position) - { - if (this.position == position) - return; - - this.position = position; - this.DOMRoot.style.top = (position * this.folderOffset).toFixed(2) + 'em'; - }; - - SessionFolder.prototype.setTitle = function setTitle(title) - { - this.DOMRoot.textContent = title; - }; - - SessionFolder.prototype.remove = function remove() - { - var box = this.DOMRoot; - if (box.parentElement) - box.parentElement.removeChild(box); - }; - - var SortMethods = { - - 'name-asc' : function sortByNameAscending(a, b) { - return a.bookmark.title_lowercase > b.bookmark.title_lowercase; - }, - - 'name-desc' : function sortByNameDescending(a, b) { - return a.bookmark.title_lowercase < b.bookmark.title_lowercase; - }, - - 'position-asc' : function sortByPositionAscending(a, b) { - return a.bookmark.index > b.bookmark.index; - }, - - 'position-desc' : function sortByPositionDescending(a, b) { - return a.bookmark.index < b.bookmark.index; - }, - - 'date-asc' : function sortByDateAscending(a, b) { - return a.bookmark.dateAdded > b.bookmark.dateAdded; - }, - - 'date-desc' : function sortByDateDescending(a, b) { - return a.bookmark.dateAdded < b.bookmark.dateAdded; - } - }; - - - /* - * SessionHistoryFolder - * Used as separator for different windows - */ - - function SessionHistoryFolder(sessionInfo, index) - { - var DomElem = HTMLCreator(); - var folder = DomElem('div', { class: 'history-node' }); - - folder.textContent = new Date(sessionInfo.lastSave).toLocaleString(); - folder.setAttribute('index', index); - - // Public data - - this.DOMRoot = folder; - this.sessionInfo = sessionInfo; - } - - SessionHistoryFolder.prototype.setTitle = function setTitle(title) - { - this.DOMRoot.textContent = title; - }; - - // ***************************************************************************** - // Public API - exports.SessionFolder = SessionFolder; - exports.SessionFolderSortBy = SortMethods; - exports.SessionHistoryFolder = SessionHistoryFolder; - -}); \ No newline at end of file diff --git a/data/scripts/components/session-header-bar.js b/data/scripts/components/session-header-bar.js deleted file mode 100644 index cd968bc..0000000 --- a/data/scripts/components/session-header-bar.js +++ /dev/null @@ -1,353 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ------------------------------------------------------------------------ - // Modules - - const { AppConfig } = require('../config'); - const { SessionSyncModel } = require ('./session-sync-model'); - - // Utils - const JSUtils = require('../utils/general'); - const { HTMLCreator } = require('../utils/dom'); - const { WindowEvents } = require('../utils/global-events'); - - // ------------------------------------------------------------------------ - // API - - function SessionHeaderBar() - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var headerBar = DomElem('div', {class: 'header-bar'}); - headerBar.style.fontSize = AppConfig.get('style.scale.header') + 'px'; - - // Display information about the selected session - var description = DomElem('div', {class: 'description'}); - headerBar.appendChild(description); - - // Active session button - var activeSessionBtn = DomElem('div', {class: 'active-session-btn', tooltip: 'Active session'}); - headerBar.appendChild(activeSessionBtn); - - // Header Menu - var headerMenu = CreateHeaderMenu(); - headerBar.appendChild(headerMenu); - - // ------------------------------------------------------------------------ - // Events - - AppConfig.onChange('style.scale.header', function(value) { - headerBar.style.fontSize = value + 'px'; - }); - - activeSessionBtn.addEventListener('click', function() { - WindowEvents.emit(document, 'ShowCurrentSession'); - }); - - WindowEvents.on(document, 'SetSessionDescription', function(value) { - description.textContent = value; - }); - - // Tooltip events - headerBar.addEventListener('mouseover', function(e) { - if (e.target.hasAttribute('tooltip')) { - WindowEvents.emit(document, 'ShowTooltip', { - node: e.target, - message: e.target.getAttribute('tooltip') - }); - } else { - WindowEvents.emit(document, 'HideTooltip'); - } - }); - - headerBar.addEventListener('mouseleave', function() { - WindowEvents.emit(document, 'HideTooltip'); - }); - - // ------------------------------------------------------------------------ - // Public properties - - this.DOMRoot = headerBar; - } - - var CreateHeaderMenu = function CreateHeaderMenu() - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var headerMenu = DomElem('div', {class: 'header-menu'}); - headerMenu.setAttribute('active', 'false'); - - var menuArea = DomElem('div', {class: 'menu-area'}); - headerMenu.appendChild(menuArea); - - var menuRow1 = DomElem('div', {class: 'menu-row'}); - var menuRow2 = DomElem('div', {class: 'menu-row'}); - var menuRow3 = DomElem('div', {class: 'menu-row'}); - menuArea.appendChild(menuRow1); - menuArea.appendChild(menuRow2); - menuArea.appendChild(menuRow3); - - // ------------------------------------------------------------------------ - // Methods - - function MenuButton(options) { - - var button = DomElem('div', {class: 'menu-button'}); - - var icon = DomElem('div', {class: 'icon'}); - var title = DomElem('div', {class: 'title'}); - - title.textContent = options.title; - - var url = 'url("images/' + options.icon + '")'; - icon.style.backgroundImage = url; - - // Events - var callback = JSUtils.getValidFunction(options.callback); - button.addEventListener('click', callback); - - button.appendChild(icon); - button.appendChild(title); - return button; - } - - var openConfig = MenuButton({ - title: 'Options', - icon: 'icons/gear.png', - callback: function () { - var model = SessionSyncModel.getModel(document); - var state = model.state['config'] == undefined ? 'on' : undefined; - WindowEvents.emit(document, 'SetUIState', { config : state}); - } - }); - - var openTutorial = MenuButton({ - title: 'Tutorial', - icon: 'icons/brightness.png', - callback: function () { - browser.runtime.sendMessage({event: 'session-sync-tutorial'}); - } - }); - - var leaveFeedback = MenuButton({ - title: 'Feedback', - icon: 'icons/mail.png', - callback: function () { - browser.runtime.sendMessage({event: 'session-sync-leave-feedback'}); - } - }); - - var exportImport = MenuButton({ - title: 'Export/Import', - icon: 'icons/booklet.png', - callback: function () { - browser.tabs.create({ - url: 'home/home.html#export-import', - active: true, - }); - } - }); - - var buttonDetachInTab = MenuButton({ - title: 'Tab View', - icon: 'icons/document.png', - callback: function () { - browser.runtime.sendMessage({event: 'session-sync-detach-tab'}); - } - }); - - var buttonDetachInWindow = MenuButton({ - title: 'Window View', - icon: 'icons/browser.png', - callback: function () { - browser.runtime.sendMessage({event: 'session-sync-detach-window'}); - } - }); - - var githubPage = MenuButton({ - title: 'Dev page', - icon: 'github-logo.png', - callback: function () { - browser.runtime.sendMessage({event: 'session-sync-open-github'}); - } - }); - - menuRow1.appendChild(openConfig); - menuRow2.appendChild(openTutorial); - menuRow2.appendChild(leaveFeedback); - menuRow2.appendChild(githubPage); - menuRow3.appendChild(exportImport); - menuRow3.appendChild(buttonDetachInTab); - menuRow3.appendChild(buttonDetachInWindow); - - // ------------------------------------------------------------------------ - // Dev actions - - var DebugMode = { - init: AppConfig.devMode(), - manualTrigger: 0, - triggerDate: new Date(), - action : function () { - if (this.init == false) - { - if (new Date() - this.triggerDate < 5000) { - this.manualTrigger++; - if (this.manualTrigger > 10) { - this.init = true; - initDevMode(); - } - } - else { - this.manualTrigger = 0; - } - } - } - }; - - function initDevMode() - { - var devRow1 = DomElem('div', {class: 'menu-row'}); - - var activeTabInfo = MenuButton({ - title: 'Tab Info', - icon: 'icons/document.png', - callback: function () { - browser.tabs.query({active: true, windowId: browser.windows.WINDOW_ID_CURRENT}) - .then(tabs => browser.tabs.get(tabs[0].id)) - .then(tab => { - console.log(tab); - }); - } - }); - - var localStorage = MenuButton({ - title: 'Local Storage', - icon: 'icons/bookshelf.png', - callback: function () { - browser.storage.local.get().then(function (data) { - console.log(data); - }); - } - }); - - var clearFaviconCache = MenuButton({ - title: 'Clear Favicons', - icon: 'icons/x.png', - callback: function () { - browser.storage.local.get().then(function (data) { - for (var key in data) { - if (key.startsWith('@favIconUrl')) { - browser.storage.local.remove(key); - } - } - }); - } - }); - - devRow1.appendChild(activeTabInfo); - devRow1.appendChild(localStorage); - devRow1.appendChild(clearFaviconCache); - menuArea.appendChild(devRow1); - } - - if (AppConfig.devMode()) - { - initDevMode(); - } - - var Troubleshooting = (function Troubleshooting() - { - var isInit = false; - - var fixLazySession = MenuButton({ - title: 'Fix session', - icon: 'icons/tools.png', - callback: function() { - WindowEvents.emit(document, 'MenuFixLazySession'); - } - }); - - function init() - { - if (isInit == true) - { - return; - } - - isInit = true; - var devRow = DomElem('div', {class: 'menu-row'}); - devRow.appendChild(fixLazySession); - menuArea.appendChild(devRow); - } - - return { - init: init - }; - - })(); - - // ------------------------------------------------------------------------ - // Events - - var closeMenu = function closeMenu() { - headerMenu.setAttribute('active', '0'); - }; - - var closeOnMiss = function closeOnMiss(e) - { - if (e && (e.target == headerMenu || e.target == menuArea)) { - return; - } - closeMenu(); - document.removeEventListener('click', closeOnMiss); - }; - - headerMenu.addEventListener('click', function(e) { - if (e && e.target == headerMenu) - { - var state = headerMenu.getAttribute('active') | 0; - var newState = 1 - state; - headerMenu.setAttribute('active', newState); - if (newState == 1) { - document.addEventListener('click', closeOnMiss); - } - - DebugMode.action(); - } - }); - - WindowEvents.on(document, 'CloseUI', function() { - closeMenu(); - }); - - AppConfig.onChange('troubleshooting', function(value) { - if (value == true) { - Troubleshooting.init(); - } - }); - - // ------------------------------------------------------------------------ - // Public - - return headerMenu; - }; - - // ------------------------------------------------------------------------ - // Events - - // ------------------------------------------------------------------------ - // Init - - // ------------------------------------------------------------------------ - // Module exports - - exports.SessionHeaderBar = SessionHeaderBar; - -}); diff --git a/data/scripts/components/session-history.js b/data/scripts/components/session-history.js deleted file mode 100644 index dccb759..0000000 --- a/data/scripts/components/session-history.js +++ /dev/null @@ -1,117 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { WindowEvents, GlobalEvents } = require('../utils/global-events'); - - // ***************************************************************************** - // API - - var SessionHistory = (function SessionHistory() - { - var storageKey = { - config: 'history.config', - active: 'history.active', - list: 'history.sessions' - }; - - var sessions = []; - - var init = function init() - { - browser.storage.local.get(storageKey.list).then(function (obj) { - if(obj[storageKey.list]) { - sessions = obj[storageKey.list]; - } - }); - }; - - var getFullHistory = function getFullHistory(callback) - { - browser.storage.local.get(storageKey.active).then(function (obj) { - callback(sessions, obj[storageKey.active]); - }); - }; - - var getHistorySession = function getHistorySession(index, callback) { - if (index >=0 && index < sessions.length) { - callback(sessions[index]); - } - else { - browser.storage.local.get(storageKey.active).then(function (obj) { - callback(obj[storageKey.active]); - }); - } - }; - - var getConfig = function getConfig(callback) - { - browser.storage.local.get(storageKey.config).then(function (obj) { - callback(obj[storageKey.config]); - }); - }; - - var updateConfig = function updateConfig(info) - { - browser.storage.local.set({ [storageKey.config] : info}); - }; - - var updateSessions = function updateSessions(sessions) - { - browser.storage.local.set({ [storageKey.list] : sessions}) - .then(function () { - WindowEvents.emit(document, 'ShowHistoryList'); - }); - }; - - var onStorageChanged = function onStorageChanged(object) - { - if (object[storageKey.list]) { - init(); - } - }; - - GlobalEvents.on('HistorySessionDelete', function(index) - { - if (index == -1) - { - browser.runtime.sendMessage({event: 'history.deleteActive'}); - } - else - { - getFullHistory(function (sessions) { - if (index >= 0 && index < sessions.length) { - sessions.splice(index, 1); - updateSessions(sessions); - } - }); - } - }); - - GlobalEvents.on('HistorySessionDeleteAll', function() { - sessions = []; - updateSessions(sessions); - }); - - browser.storage.onChanged.addListener(onStorageChanged); - - // ------------------------------------------------------------------------ - // Public API - - return { - init: init, - getConfig: getConfig, - getFullHistory: getFullHistory, - getHistorySession: getHistorySession, - updateConfig: updateConfig - }; - - })(); - - // ***************************************************************************** - // Public API - - exports.SessionHistory = SessionHistory; -}); \ No newline at end of file diff --git a/data/scripts/components/session-hotkey-manager.js b/data/scripts/components/session-hotkey-manager.js deleted file mode 100644 index 70fe27a..0000000 --- a/data/scripts/components/session-hotkey-manager.js +++ /dev/null @@ -1,229 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { BookmarkManager } = require('../session/bookmarks'); - - // Utils - const { DropDown } = require('../utils/components'); - const { HTMLCreator } = require('../utils/dom'); - const { WindowEvents } = require('../utils/global-events'); - - // ***************************************************************************** - // API - - /* - * Session Hotkeys - */ - - function SessionHotkeyManager() - { - // Create DomHealper - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var overlay = DomElem('div', { class : 'hotkey-overlay' }); - var panel = DomElem('div', { class : 'hotkey-manager' }); - - var header = DomElem('div', { class : 'header' }); - header.textContent = 'Change hotkey'; - - var infoBox = DomElem('div', { class : 'info-box' }); - var message = DomElem('div', { class : 'message' }); - var url = DomElem('div', { class: 'link' }); - var supportLink = 'https://support.mozilla.org/en-US/kb/keyboard-shortcuts-perform-firefox-tasks-quickly'; - - url.textContent = 'Click to see Firefox default shortcuts'; - message.textContent = 'Default Firefox shortcuts cannot be overrided'; - infoBox.appendChild(message); - infoBox.appendChild(url); - - function addPlusSeparator(parent) { - var separator = DomElem('div', { class : 'plus' }); - separator.textContent = '+'; - parent.appendChild(separator); - } - - function HotkeyInfo(title) - { - var box = DomElem('div', { class : 'hotkey-info' }); - var header = DomElem('div', { class : 'title' }); - var value = DomElem('div', { class : 'value' }); - - header.textContent = title; - box.appendChild(header); - box.appendChild(value); - - function setValue(newValue) { - value.textContent = newValue; - } - - return { - DOMRoot: box, - setValue: setValue - }; - } - - var currentHotKey = HotkeyInfo('Current'); - var newHotKey = HotkeyInfo('New'); - - panel.appendChild(header); - panel.appendChild(infoBox); - panel.appendChild(currentHotKey.DOMRoot); - panel.appendChild(newHotKey.DOMRoot); - - var shortcutConfig = DomElem('div', {class : 'shortcut-config'}); - panel.appendChild(shortcutConfig); - - // -------------------------------------------------------------------- - // Modifier key - - var modifierKey = new DropDown({ - id: 'modifier-key', - description: 'Special keys', - onChange: updateNewHotkey - }); - shortcutConfig.appendChild(modifierKey.container); - addPlusSeparator(shortcutConfig); - - modifierKey.addOption('Ctrl'); - modifierKey.addOption('Alt'); - modifierKey.addOption('Command'); - modifierKey.addOption('MacCtrl'); - modifierKey.setValue('Ctrl'); - - // -------------------------------------------------------------------- - // Optional Modifier - - var optionalModifier = new DropDown({ - id: 'optional-modifier', - description: 'Optional modifier', - onChange: updateNewHotkey - }); - shortcutConfig.appendChild(optionalModifier.container); - addPlusSeparator(shortcutConfig); - - optionalModifier.addOption('None', { value: '' }); - optionalModifier.addOption('Shift'); - optionalModifier.setValue('Shift'); - - // -------------------------------------------------------------------- - // Shortcut key - - var shortcutKey = new DropDown({ - id: 'shortcut-key', - description: 'Key', - onChange: updateNewHotkey - }); - - // Add A-Z literals - for (let i = 65; i < 91; i++) { - shortcutKey.addOption(String.fromCharCode(i)); - } - - // Add 0-9 literals - for (let i = 0; i < 10; i++) { - shortcutKey.addOption('' + i); - } - - // Add F1-F12 literals - for (let i = 1; i <= 12; i++) { - shortcutKey.addOption('F' + i); - } - - // Add special characters - shortcutKey.addOption(',', { value: 'Comma'}); - shortcutKey.addOption('.', { value: 'Period'}); - var values = ['Home', 'End', 'PageUp', 'PageDown', 'Space', 'Insert', 'Delete', 'Up', 'Down', 'Left', 'Right']; - for (let i in values) { - shortcutKey.addOption(values[i], { property: 'wide' }); - } - shortcutConfig.appendChild(shortcutKey.container); - - // -------------------------------------------------------------------- - // Action bar - - var actionBar = DomElem('div', {class : 'action-bar'}); - - var ok = DomElem('div', {class : 'button ok'}); - ok.textContent = 'Confirm'; - - var cancel = DomElem('div', {class : 'button cancel'}); - cancel.textContent = 'Cancel'; - - var flexArea1 = DomElem('div', {class : 'flex'}); - var flexArea2 = DomElem('div', {class : 'flex'}); - - actionBar.appendChild(flexArea1); - actionBar.appendChild(ok); - actionBar.appendChild(cancel); - actionBar.appendChild(flexArea2); - - panel.appendChild(actionBar); - overlay.appendChild(panel); - - // ------------------------------------------------------------------------ - // Events - - var changeCallback; - - function updateNewHotkey() { - var optional = ' + ' + (optionalModifier.getValue() ? optionalModifier.getValue() + ' + ' : ''); - newHotKey.setValue(modifierKey.getValue() + optional + shortcutKey.getValue()); - } - - function setState(state) { - overlay.setAttribute('state', state); - } - - cancel.addEventListener('click', function () { - setState(false); - }); - - ok.addEventListener('click', function () { - setState(false); - - var optional = '+' + (optionalModifier.getValue() ? optionalModifier.getValue() + '+' : ''); - changeCallback(modifierKey.getValue() + optional + shortcutKey.getValue()); - }); - - url.addEventListener('click', function () { - BookmarkManager.openBookmark({ - url: supportLink, - mode: 'newTab', - active: true - }); - }); - - function changeHotKey(options) { - - changeCallback = options.callback; - var tokens = options.shortcut.split('+'); - - header.textContent = options.title, - currentHotKey.setValue(tokens.join(' + ')); - newHotKey.setValue(''); - modifierKey.setValue(tokens[0]); - optionalModifier.setValue(tokens.length == 3 ? tokens[1] : ''); - shortcutKey.setValue(tokens.length == 3 ? tokens[2] : tokens[1]); - - setState(true); - } - - WindowEvents.on(document, 'ChangeHotkey', changeHotKey); - - // ------------------------------------------------------------------------ - // Public properties - - this.DOMRoot = overlay; - } - - // ***************************************************************************** - // Public API - - exports.SessionHotkeyManager = SessionHotkeyManager; -}); \ No newline at end of file diff --git a/data/scripts/components/session-hotkeys.js b/data/scripts/components/session-hotkeys.js deleted file mode 100644 index e05913d..0000000 --- a/data/scripts/components/session-hotkeys.js +++ /dev/null @@ -1,71 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - // Utils - const { WindowEvents } = require('../utils/global-events'); - - // ***************************************************************************** - // API - - /* - * Session Hotkeys - */ - - function SessionHotkeys() - { - // ------------------------------------------------------------------------ - // Events - - var onKeyDown = function(e) - { - switch(e.keyCode) - { - // Key: F1 - case 112: { - WindowEvents.emit(document, 'SessionScrollToSelected'); - break; - } - - // Key: F2 - case 113: { - WindowEvents.emit(document, 'EditSelectedSession'); - break; - } - - // Key: F3 - case 114: { - e.preventDefault(); - WindowEvents.emit(document, 'FocusSessionFilter'); - break; - } - - // Key: CTRL + F - case 70: { - if (e.ctrlKey == true && e.shiftKey == false && e.altKey == false) - { - e.preventDefault(); - WindowEvents.emit(document, 'FocusSessionFilter'); - } - break; - } - } - }; - - // ------------------------------------------------------------------------ - // Events - - document.addEventListener('keydown', onKeyDown); - - // ------------------------------------------------------------------------ - // Public properties - - } - - // ***************************************************************************** - // Public API - - exports.SessionHotkeys = SessionHotkeys; -}); \ No newline at end of file diff --git a/data/scripts/components/session-list.js b/data/scripts/components/session-list.js deleted file mode 100644 index 3b0a237..0000000 --- a/data/scripts/components/session-list.js +++ /dev/null @@ -1,430 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ************************************************************************ - // Modules - - const { AppConfig } = require('../config'); - - const { HTMLCreator } = require('../utils/dom'); - const { WindowEvents, GlobalEvents } = require('../utils/global-events'); - const { AutoScroll } = require('../utils/auto-scroll'); - - const { SessionFolder, SessionHistoryFolder, SessionFolderSortBy } = require('./session-folder'); - const { SessionFolderEvents, SessionHistoryEvents } = require('./session-folder-events'); - const { SessionSyncModel } = require('./session-sync-model'); - const { SessionHistory } = require('./session-history'); - - // ************************************************************************ - // API - - /* - * Session List - * UI Module for interacting with all the session - */ - - function SessionList() - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Init UI - - // Session Area - var root = DomElem('div', {class: 'sessions'}); - - var resizeHandle = DomElem('div', {class: 'resize-handle'}); - root.appendChild(resizeHandle); - - // History list - var historyList = this.createHistoryList(); - root.appendChild(historyList); - - // Session list - var sessionList = DomElem('div', {class: 'session-list'}); - root.appendChild(sessionList); - - // ------------------------------------------------------------------------ - // Events - - AppConfig.onChange('style.sessions.list.width', function(width) { - root.style.width = width + 'px'; - }); - - AppConfig.onChange('style.scale.sessions', function(value) { - root.style.fontSize = value + 'px'; - }); - - var startResize = function startResize(e) - { - var startPos = e.clientX; - var initWidth = root.clientWidth; - resizeHandle.setAttribute('resizing', 'true'); - - function eventMove(e) { - var deltaX = e.clientX - startPos; - var newWidth = initWidth + deltaX; - AppConfig.set('style.sessions.list.width', newWidth); - } - - function evenUp() { - resizeHandle.removeAttribute('resizing'); - document.removeEventListener('mouseup', evenUp); - document.removeEventListener('mousemove', eventMove); - } - - document.addEventListener('mouseup', evenUp); - document.addEventListener('mousemove', eventMove); - }; - - resizeHandle.addEventListener('mousedown', startResize); - - WindowEvents.on(document, 'ListSessions', this.setSessions.bind(this)); - - WindowEvents.on(document, 'SetPromiseSession', function(sessionInfo) { - this.promiseInfo = sessionInfo; - WindowEvents.emit(document, 'ShowSyncList', { update: sessionInfo.update }); - }.bind(this)); - - WindowEvents.on(document, 'SelectSyncSession', function(context) { - this.selectSyncSession(context, false); - }.bind(this)); - - WindowEvents.on(document, 'FilterSessions', this.filterSessions.bind(this)); - WindowEvents.on(document, 'SortSessionsBy', this.sortSessionsBy.bind(this)); - - WindowEvents.on(document, 'SessionsPositionChanged', function(indices) { - if (AppConfig.get('session.sorting') == 'position-asc' && AppConfig.get('session.active.filter').length == 0) - { - var len = indices.length; - for (var i = 0; i < len; i++) { - var node = this.SyncModel.getBookmark(indices[i]); - node.setVirtualPosition(SessionSyncModel.bookmarks[node.bookmarkID].index); - } - } - }.bind(this)); - - WindowEvents.on(document, 'ShowCurrentSession', function() { - if (AppConfig.get('session.selected') != null) { - AppConfig.set('session.selected', null); - AppConfig.set('state.scrollTop.current', 0); - } - this.setState('active'); - this.setSelectedNode(undefined); - }.bind(this)); - - WindowEvents.on(document, 'ShowSyncList', function(options) { - // Select first visible session - if (this.promiseInfo.sessionID == null) - { - for (var i=0; i < this.sessions.length; i++) - { - if (this.sessions[i].isVisible()) - { - this.selectSyncSession(this.sessions[i], true, false); - break; - } - } - } - - this.setState('sync'); - if (options.update == true) { - GlobalEvents.emit('update-sessions'); - } - }.bind(this)); - - WindowEvents.on(document, 'SessionDescriptionChanged', function() { - this.sortSessionsBy(AppConfig.get('session.sorting')); - this.scrollToSelected(); - }.bind(this)); - - WindowEvents.on(document, 'SessionScrollToSelected', function() { - this.scrollToSelected(); - }.bind(this)); - - GlobalEvents.on('BookmarkDeleted', function (itemID) { - if (AppConfig.get('session.selected') == itemID) { - WindowEvents.emit(this.document, 'ShowCurrentSession'); - } - }.bind(this)); - - WindowEvents.on(document, 'SelectHistorySession', function(context) { - this.selectHistorySession(context); - }.bind(this)); - - AppConfig.onChange('session.selected', function(value) { - if (value) { - WindowEvents.emit(document, 'SetPromiseSession', { sessionID: value, update: false } ); - } - }); - - // ------------------------------------------------------------------------ - // Event Initialization - - SessionFolderEvents(sessionList); - SessionHistoryEvents(historyList); - SessionHistory.init(); - - // ------------------------------------------------------------------------ - // Public data - - this.sessions = []; - this.promiseInfo = {}; - this.DOMList = sessionList; - this.DOMRoot = root; - this.document = document; - - this.SyncModel = SessionSyncModel.getModel(document); - } - - SessionList.prototype.setState = function setState(state) - { - AppConfig.set('session.view', state); - WindowEvents.emit(document, 'SetUIState', { list: state }); - }; - - SessionList.prototype.createHistoryList = function createHistoryList() - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var historyList = DomElem('div', {class: 'history-list'}); - - // ------------------------------------------------------------------------ - // Events - - function refreshHistoryList() - { - var snap = AppConfig.isInitState(); - var selectedIndex = AppConfig.get('session.history.selected'); - - historyList.textContent = ''; - SessionHistory.getFullHistory(function (historySessions, activeSession) { - - if (activeSession) { - let activeSnapshot = new SessionHistoryFolder(activeSession, -1); - activeSnapshot.setTitle('Active snapshot'); - historyList.appendChild(activeSnapshot.DOMRoot); - } - - for (let i = historySessions.length - 1; i >= 0; i--) { - if (historySessions[i] && historySessions[i].tabCount) - { - let historySession = new SessionHistoryFolder(historySessions[i], i); - historyList.appendChild(historySession.DOMRoot); - - if (i == selectedIndex) { - var node = historySession.DOMRoot; - AutoScroll.scrollTo(historyList, node.offsetTop - historyList.parentNode.clientHeight / 2 + node.clientHeight, snap === true ? 0 : 0.25); - } - } - } - }.bind(this)); - } - - GlobalEvents.on('UpdateHistoryList', function() { - refreshHistoryList(); - }); - - WindowEvents.on(document, 'ShowHistoryList', function() { - this.setState('history'); - refreshHistoryList(); - }.bind(this)); - - historyList.addEventListener('click', function(e) { - if (e.target.className === 'history-node') - { - this.selectHistorySession(e.target); - } - }.bind(this)); - - // ------------------------------------------------------------------------ - // Public data - - return historyList; - }; - - SessionList.prototype.selectHistorySession = function selectHistorySession(node) - { - this.setSelectedNode(node); - var index = node.getAttribute('index') | 0; - - SessionHistory.getHistorySession(index, function (sessionInfo) { - WindowEvents.emit(document, 'ShowHistorySession', sessionInfo); - - if (AppConfig.get('session.history.selected') != index) { - AppConfig.set('session.history.selected', index); - AppConfig.set('state.scrollTop.history', 0); - } - }); - }; - - SessionList.prototype.setSessions = function setSessions() - { - this.sessions = []; - this.DOMList.textContent = ''; - - // Position for active filtering - var position = 0; - var selectedSession; - var filterExpression = AppConfig.get('session.active.filter'); - - SessionSyncModel.sessions.forEach(function(session) { - - SessionSyncModel.bookmarks[session.id] = session; - - // find if a cache instance is already created and update it - var sessionFolder = this.SyncModel.getBookmark(session.id); - if (sessionFolder instanceof SessionFolder) - { - sessionFolder.updateFrom(session); - } - else - { - sessionFolder = new SessionFolder(session); - sessionFolder.setVirtualPosition(session.index); - this.SyncModel.setBookmark(session.id, sessionFolder); - } - - if (session.id === this.promiseInfo.sessionID) - { - selectedSession = sessionFolder; - } - - if (filterExpression.length) { - if (sessionFolder.match(this.filter)) { - sessionFolder.setVirtualPosition(position); - position++; - } - } - - this.sessions.push(sessionFolder); - this.DOMList.appendChild(sessionFolder.DOMRoot); - - }.bind(this)); - - if (filterExpression.length > 0) { - this.filterSessions(filterExpression); - } - - this.sortSessionsBy(AppConfig.get('session.sorting')); - - // Switch to History View if this was the state before closing the UI - if (AppConfig.get('session.view') == 'history') - { - WindowEvents.emit(document, 'ShowHistoryList'); - return; - } - - // Preview the selected session if available - if (selectedSession) - { - this.selectSyncSession(selectedSession, true, AppConfig.isInitState()); - return; - } - - // Otherwise, preview the current session - AppConfig.isInitState(); - WindowEvents.emit(this.document, 'ShowCurrentSession'); - }; - - SessionList.prototype.scrollToSelected = function scrollToSelected(snap) - { - if (this.selectedNode == undefined) - return; - - var offset = this.selectedNode.offsetTop - this.DOMList.clientHeight / 2 + this.selectedNode.clientHeight; - - AutoScroll.scrollTo(this.DOMList, offset, snap === true ? 0 : 0.25); - }; - - SessionList.prototype.setSelectedNode = function setSelectedNode(node) - { - if (this.selectedNode != undefined) { - this.selectedNode.removeAttribute('active'); - } - - this.selectedNode = node; - - if (node) { - node.setAttribute('active', 'true'); - } - }; - - SessionList.prototype.selectSyncSession = function selectSyncSession(selectedSession, scrollTo, snap) - { - if (AppConfig.get('session.selected') != selectedSession.bookmarkID) - { - AppConfig.set('session.selected', selectedSession.bookmarkID); - AppConfig.set('state.scrollTop.restore', 0); - } - - this.setSelectedNode(selectedSession.DOMRoot); - - WindowEvents.emit(this.document, 'ViewSession', selectedSession.bookmarkID); - if (this.promiseInfo.edit == true) { - WindowEvents.emit(this.document, 'SessionContextMenu-EditSession', selectedSession.bookmarkID); - } - - // auto-scroll to the selected node - if (scrollTo) { - this.scrollToSelected(snap); - } - }; - - SessionList.prototype.filterSessions = function filterSessions(expression) - { - AppConfig.set('session.active.filter', expression); - - if (expression.length) - { - expression = expression.toLowerCase(); - var exactMatch = false; - var expLength = expression.length; - var document = this.document; - - var position = 0; - this.sessions.forEach(function(session) { - if (session.match(expression)) { - session.setVirtualPosition(position); - position++; - - if (!exactMatch) { - // test same length - exactMatch = session.bookmark.title_lowercase.length == expLength; - } - } - }); - - this.DOMList.parentNode.scrollTop = 0; - WindowEvents.emit(document, 'FilterSessionsExactMatch', exactMatch); - } - else { - this.sessions.forEach(function(session) { - session.setVisible(); - }); - this.sortSessionsBy(AppConfig.get('session.sorting')); - } - }; - - SessionList.prototype.sortSessionsBy = function sortSessionsBy(method) - { - AppConfig.set('session.sorting', method); - this.sessions.sort(SessionFolderSortBy[method]); - var position = 0; - this.sessions.forEach(function(session) { - if (session.isVisible()) { - session.setVirtualPosition(position); - position++; - } - }); - }; - - // ------------------------------------------------------------------------ - // Module exports - - exports.SessionList = SessionList; - -}); \ No newline at end of file diff --git a/data/scripts/components/session-sorting.js b/data/scripts/components/session-sorting.js deleted file mode 100644 index 435b988..0000000 --- a/data/scripts/components/session-sorting.js +++ /dev/null @@ -1,118 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - // Utils - const { AppConfig } = require('../config'); - const { HTMLCreator } = require('../utils/dom'); - const { WindowEvents, GlobalEvents } = require('../utils/global-events'); - - // ***************************************************************************** - // API - - function SessionSorting() - { - // Create DomHealper - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // UI - - // Session ordering - var sortControl = DomElem('div', {class: 'sorting-method', tooltip: 'Sort sessions'}); - - // Sorting dropdown - var optionList = DomElem('div', {class: 'option-list'}); - - var options = { - 'name-asc' : { description: 'name ↑', index: 0 }, - 'name-desc' : { description: 'name ↓', index: 1 }, - 'position-asc' : { description: 'position ↑', index: 2 }, - 'position-desc' : { description: 'position ↓', index: 3 }, - 'date-asc' : { description: 'date ↑', index: 4 }, - 'date-desc' : { description: 'date ↓', index: 5 } - }; - - for (var key in options) - { - var option = DomElem('div', {class: 'option', value: key}); - option.textContent = options[key].description; - optionList.appendChild(option); - - } - - var dropdown = DomElem('div', {class: 'dropdown'}); - dropdown.appendChild(optionList); - sortControl.appendChild(dropdown); - - var activeButton = optionList.children[0]; - - // ------------------------------------------------------------------------ - // Events - - AppConfig.onChange('session.sorting', function (value) { - if (options[value]) { - activeButton = optionList.children[options[value].index]; - activeButton.setAttribute('active', ''); - } - }); - - optionList.addEventListener('click', function(e) { - if (e.target.className == 'option') { - if (e.target != activeButton) { - activeButton.removeAttribute('active'); - activeButton = e.target; - var value = activeButton.getAttribute('value'); - activeButton.setAttribute('active', ''); - } - WindowEvents.emit(document, 'SortSessionsBy', value); - } - }); - - var state = false; - var toggleState = function toggleState() { - state = !state; - sortControl.setAttribute('active', state); - WindowEvents.emit(document, 'OverlaySystem', { state: state, zIndex: 1 }); - }; - - document.addEventListener('click', function(e) { - if (e.target.className == 'sorting-method') { - toggleState(); - } - else { - if (state == true) - toggleState(); - } - }); - - // Tooltip events - sortControl.addEventListener('mouseover', function(e) { - var tooltipMsg = e.target.getAttribute('tooltip'); - if (tooltipMsg) { - WindowEvents.emit(document, 'ShowTooltip', { - node: e.target, - message: tooltipMsg - }); - } else { - WindowEvents.emit(document, 'HideTooltip'); - } - }); - - sortControl.addEventListener('mouseleave', function() { - WindowEvents.emit(document, 'HideTooltip'); - }); - - // ------------------------------------------------------------------------ - // Data - - this.DOMRoot = sortControl; - } - - // ***************************************************************************** - // Public API - - exports.SessionSorting = SessionSorting; -}); \ No newline at end of file diff --git a/data/scripts/components/session-sync-model.js b/data/scripts/components/session-sync-model.js deleted file mode 100644 index 88080ea..0000000 --- a/data/scripts/components/session-sync-model.js +++ /dev/null @@ -1,457 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { AppConfig } = require('../config'); - - // Components - const { BookmarkManager } = require('../session/bookmarks'); - const { WindowEvents, GlobalEvents } = require('../utils/global-events'); - - // Utils - const { LimitCounter } = require('../utils/general'); - - // ***************************************************************************** - // API - - var SessionSyncDOMModel = function SessionSyncDOMModel(document) - { - this.bookmarks = {}; - this.tabs = {}; - this.state = {}; - - // ------------------------------------------------------------------------ - // Events - - var OrderBookmarks = function OrderBookmarks(indices) - { - var len = indices.length; - for (let i = 0; i < len; i++) { - var node = this.bookmarks[indices[i]]; - if (node) { - node.setVirtualPosition(SessionSyncModel.bookmarks[node.bookmarkID].index); - } - } - }.bind(this); - - WindowEvents.on(document, 'BookmarksPositionChanged', OrderBookmarks); - - WindowEvents.on(document, 'BookmarkTitleChanged', function(bookmarkID) { - var node = this.bookmarks[bookmarkID]; - if (node) { - node.setTitle(SessionSyncModel.bookmarks[bookmarkID].title); - } - }.bind(this)); - - // ------------------------------------------------------------------------ - // Register - - SessionSyncModel.registerModel(document, this); - }; - - SessionSyncDOMModel.prototype.getBookmark = function getBookmark(id) - { - return this.bookmarks[id]; - }; - - SessionSyncDOMModel.prototype.setBookmark = function setBookmark(id, bookmark) - { - this.bookmarks[id] = bookmark; - }; - - SessionSyncDOMModel.prototype.removeBookmark = function removeBookmark(id) - { - if (this.bookmarks[id]) - { - this.bookmarks[id].remove(); - delete this.bookmarks[id]; - } - }; - - var SessionSyncModel = (function SessionSync() { - - // Bookmarks storage - var sessions = {}; - var bookmarks = {}; - - // Per document DOM cache - var domModels = new Map(); - - // ------------------------------------------------------------------------ - // API - - var getModel = function getModel(document) - { - var model = domModels.get(document); - if ((model instanceof SessionSyncDOMModel) === false) { - model = new SessionSyncDOMModel(document); - } - return model; - }; - - var registerModel = function registerModel(document, model) - { - if (model instanceof SessionSyncDOMModel) { - domModels.set(document, model); - } - }; - - var unregisterModel = function unregisterModel(document) - { - var model = domModels.get(document); - if (model instanceof SessionSyncDOMModel) { - domModels.delete(namespace); - } - }; - - function onActionError() - { - WindowEvents.emitLocal('Notification', { - message: 'Error when updating bookmarks' - }); - } - - function isSession(parentID) { - return parentID === AppConfig.get('storageID'); - } - - // Assumes moving in the same parent directory - var moveBookmark = function moveBookmark(idA, idB) - { - var parentID = bookmarks[idA].parentId; - var indexA = bookmarks[idA].index; - var indexB = bookmarks[idB].index; - var direction = indexA > indexB ? 1 : -1; - - BookmarkManager.moveItem(idA, parentID, indexA < indexB ? indexB : indexB, onSuccessMove, onActionError); - - function onSuccessMove() - { - var indices = []; - var min = Math.min(indexA, indexB); - var max = Math.max(indexA, indexB); - - for (var id in bookmarks) - { - var B = bookmarks[id]; - if (B.parentId == parentID && B.index > min && B.index < max) - { - indices.push(B.id); - B.index += direction; - } - } - - indices.push(idA); - indices.push(idB); - bookmarks[idA].index = indexB; - bookmarks[idB].index += direction; - - WindowEvents.broadcast(isSession(parentID) ? 'SessionsPositionChanged' : 'BookmarksPositionChanged', indices); - } - }; - - // Moving a bookmark to a new session directory - var moveBookmarkTo = function moveBookmarkTo(bookmarkID, folderID) - { - var parentID = bookmarks[bookmarkID].parentId; - if (parentID == folderID) - return; - - BookmarkManager.moveItem(bookmarkID, folderID, undefined, onSuccess, onActionError); - - function onSuccess() - { - bookmarks[bookmarkID].parentId = folderID; - - // TODO: set position based on the length of the new parent list - // TODO: trigger update for folderID - - var index = bookmarks[bookmarkID].index; - bookmarks[bookmarkID].index = -1; - - // Remove the bookmark from all UIs - domModels.forEach(function (model) { - model.removeBookmark(bookmarkID); - }); - - // Reorder all bookmarks after the removed one - var indices = []; - for (var id in bookmarks) - { - var B = bookmarks[id]; - if (B.parentId == parentID && B.index > index) - { - indices.push(B.id); - B.index--; - } - } - - // Trigger reordering - WindowEvents.broadcast('BookmarksPositionChanged', indices); - } - }; - - // all items must have the same parent - var deleteBookmarkList = function deleteBookmarkList(bookmarkIDs, parentID, callback) - { - if (bookmarkIDs.length == 0) - { - callback(); - return; - } - - var reorderList = []; - - // Get position ordered list of all bookmarks from the parrentID folder - for (var id in bookmarks) - { - var B = bookmarks[id]; - if (B.parentId == parentID) { - reorderList[B.index] = B; - } - } - - var updateEvent = new LimitCounter(bookmarkIDs.length, onFinish); - - bookmarkIDs.forEach(function(bookmarkID) - { - BookmarkManager.deleteItem(bookmarkID, false, onDeleteSuccess.bind(null, bookmarkID), updateEvent.advance); - }); - - function onFinish() - { - // compute reordering - var indices = []; - var position = 0; - for (var i = 0; i < reorderList.length; i++) { - if (reorderList[i]) { - indices.push(reorderList[i].id); - reorderList[i].index = position; - position++; - } - } - - if (indices.length) - { - // Trigger reordering - WindowEvents.broadcast('BookmarksPositionChanged', indices); - } - - callback(); - } - - function onDeleteSuccess(bookmarkID) - { - var index = bookmarks[bookmarkID].index; - - // Remove the bookmark from all UIs - domModels.forEach(function (model) { - model.removeBookmark(bookmarkID); - }); - - delete reorderList[index]; - delete bookmarks[bookmarkID]; - GlobalEvents.emit('BookmarkRemoved'); - - updateEvent.advance(); - } - }; - - var deleteBookmarkItem = function deleteBookmarkItem(bookmarkID) - { - var isFolder = bookmarks[bookmarkID].type === 'folder'; - BookmarkManager.deleteItem(bookmarkID, isFolder, onDeleteSuccess, onActionError); - - function onDeleteSuccess() - { - var parentID = bookmarks[bookmarkID].parentId; - var index = bookmarks[bookmarkID].index; - - // Remove the bookmark from all UIs - domModels.forEach(function (model) { - model.removeBookmark(bookmarkID); - }); - - // Reorder all bookmarks after the removed one - var indices = []; - for (let id in bookmarks) - { - let B = bookmarks[id]; - if (B.parentId == parentID && B.index > index) { - indices.push(B.id); - B.index--; - } - } - - // Save undo history - var undoEvent = { type: 'delete', bookmark: bookmarks[bookmarkID], items: [] }; - - if (isFolder) - { - var folderID = bookmarks[bookmarkID].id; - for (let id in bookmarks) - { - let B = bookmarks[id]; - if (B.parentId == folderID) { - undoEvent.items.push(Object.assign({}, B)); - } - } - } - - // Remove model references - delete bookmarks[bookmarkID]; - undoEvent.items.forEach(function(B) { - delete bookmarks[B.id]; - }); - - // Save undo event - var undoEvents = AppConfig.get('undo.events'); - undoEvents.push(undoEvent); - - AppConfig.set('undo.events', undoEvents); - GlobalEvents.emit('BookmarkDeleted', bookmarkID); - - // Trigger reordering - WindowEvents.broadcast('BookmarksPositionChanged', indices); - } - }; - - var updateBookmarkItem = function updateBookmarkItem(bookmarkID, options) - { - try { - if (options.title) { - BookmarkManager.setTitle(bookmarks[bookmarkID], options.title, function() { - WindowEvents.broadcast('BookmarkTitleChanged', bookmarkID); - WindowEvents.broadcast('SessionDescriptionChanged', bookmarkID); - }); - } - - if (options.url) { - BookmarkManager.setLocation(bookmarks[bookmarkID], options.url); - } - - WindowEvents.emitLocal('Notification', { - message: 'Success' - }); - - } catch(err) { - console.log(err); - WindowEvents.emitLocal('Notification', { - message: 'Error while saving bookmark' - }); - } - }; - - // ------------------------------------------------------------------------ - // Events - - var recreateBookmark = function recreateBookmark(item) - { - delete item.id; - delete item.dateAdded; - delete item.dateGroupModified; - delete item.title_lowercase; - return BookmarkManager.createBookmark(item); - }; - - var undoHistoryEvent = function undoHistoryEvent() - { - var sessionID = undefined; - var undoEvents = AppConfig.get('undo.events'); - - function onSuccess() - { - AppConfig.set('undo.events', undoEvents); - WindowEvents.emit(document, 'SetPromiseSession', { sessionID: sessionID, update: false }); - GlobalEvents.emit('update-sessions'); - } - - if (undoEvents.length) - { - var historyEvent = undoEvents.pop(); - - if (historyEvent.type == 'delete') - { - switch (historyEvent.bookmark.type) - { - case 'bookmark': - case 'separator': { - recreateBookmark(historyEvent.bookmark) - .then(function (bookmark) { - sessionID = (bookmark.parentId == AppConfig.get('storageID')) ? bookmark.id : bookmark.parentId; - onSuccess(); - }); - break; - } - - case 'folder': { - // Restore deleted folder - var oldID = historyEvent.bookmark.id; - - recreateBookmark(historyEvent.bookmark) - .then(function (node) { - sessionID = node.id; - var newID = node.id; - var updateEvent = new LimitCounter(historyEvent.items.length, onSuccess); - - // correct parent for each bookmark - if (oldID != newID) - { - // correct restore event - historyEvent.items.forEach(function (bookmark) { - bookmark.parentId = newID; - recreateBookmark(bookmark) - .then(updateEvent.advance, updateEvent.advance); - }); - - // correct all undo events - undoEvents.forEach(function (event) { - event.items.forEach(function (bookmark) { - if (bookmark.parentId == oldID) { - bookmark.parentId = newID; - } - }); - }); - } - - if (historyEvent.items.length == 0) { - onSuccess(); - } - }); - break; - } - } - } - } - }; - - // Subscribe to management events - GlobalEvents.on('DeleteBookmarkItem', deleteBookmarkItem); - GlobalEvents.on('HistoryUndo', undoHistoryEvent); - - // ------------------------------------------------------------------------ - // Public API - - return { - getModel: getModel, - unregisterModel: unregisterModel, - registerModel: registerModel, - - domModels: domModels, - bookmarks: bookmarks, - moveBookmark: moveBookmark, - moveBookmarkTo: moveBookmarkTo, - - deleteBookmarkList: deleteBookmarkList, - updateBookmarkItem: updateBookmarkItem, - }; - - })(); - - // ***************************************************************************** - // Public API - - exports.SessionSyncModel = SessionSyncModel; -}); \ No newline at end of file diff --git a/data/scripts/components/session-toolbar.js b/data/scripts/components/session-toolbar.js deleted file mode 100644 index 7a9c9f9..0000000 --- a/data/scripts/components/session-toolbar.js +++ /dev/null @@ -1,277 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - // App - const { AppConfig } = require('../config'); - - // Utils - const { HTMLCreator } = require('../utils/dom'); - const { WindowEvents, GlobalEvents } = require('../utils/global-events'); - const DOMComponent = require('../utils/components'); - - // ***************************************************************************** - // API - - /* - * Command bar - */ - - function SessionToolbar() - { - // Create DomHealper - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var toolbar = DomElem('div', {class: 'session-toolbar'}); - - // ------------------------------------------------------------------------ - // List settings - - var container = DomElem('div', {class: 'session-selector'}); - container.style.width = AppConfig.get('style.scale.bookmarks') + 'px'; - - var activeBtn = DomElem('div', {class: 'button active-session'}); - activeBtn.textContent = ''; - - var syncBtn = DomElem('div', {class: 'button sync'}); - syncBtn.textContent = 'Sessions'; - - var historyBtn = DomElem('div', {class: 'button history'}); - historyBtn.textContent = 'History'; - - container.appendChild(activeBtn); - container.appendChild(syncBtn); - container.appendChild(historyBtn); - toolbar.appendChild(container); - - // ------------------------------------------------------------------------ - // Session toolbar menu - - var sessionMenu = this.createMenu(); - toolbar.appendChild(sessionMenu); - - var sessionDate = DomElem('div', {class: 'session-date'}); - toolbar.appendChild(sessionDate); - - // ------------------------------------------------------------------------ - // Events - - WindowEvents.on(document, 'SetSessionDate', function(date) { - sessionDate.textContent = (new Date(date)).toLocaleString(); - }); - - activeBtn.addEventListener('click', function() { - WindowEvents.emit(document, 'ShowCurrentSession'); - }); - - syncBtn.addEventListener('click', function() { - WindowEvents.emit(document, 'ShowSyncList', { update: true }); - }); - - historyBtn.addEventListener('click', function() { - WindowEvents.emit(document, 'ShowHistoryList'); - }); - - AppConfig.onChange('style.sessions.list.width', function(width) { - container.style.width = width + 'px'; - }); - - AppConfig.onChange('style.scale.toolbar', function(value) { - toolbar.style.fontSize = value + 'px'; - }); - - // ------------------------------------------------------------------------ - // Public properties - - this.document = document; - this.DOMRoot = toolbar; - } - - SessionToolbar.prototype.createMenu = function createMenu() - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // UI - SessionContainer menu - - var menu = DomElem('div', {class: 'menu-bar'}); - - var ActionButton = function actionButton(options) - { - var button = DomElem('div', { - class: 'button ' + options.className, - 'state-current': options.state.current, - 'state-restore': options.state.restore, - 'state-history': options.state.history, - }); - button.setAttribute('tooltip', options.tooltip); - - button.addEventListener('click', function (e) { - WindowEvents.emit(document, options.event, e); - }); - - return button; - }; - - var save = ActionButton({ - tooltip: 'Save session', - className: 'save', - event: 'MenuSaveSession', - state: { - current : true, - history: true - } - }); - - var add = ActionButton({ - tooltip: 'Add current tab', - className: 'add', - event: 'MenuAddCurrentTab', - state: { - restore: true - } - }); - - var restore = ActionButton({ - tooltip: 'Restore', - className: 'restore', - event: 'MenuRestoreClick', - state: { - restore: true - } - }); - - var restoreW = ActionButton({ - tooltip: 'Restore (new window)', - className: 'restore-new-win', - event: 'MenuRestoreNewWindow', - state: { - restore : true, - history: true - } - }); - - var replaceSession = ActionButton({ - tooltip: 'Restore (close active)', - className: 'repalce-session', - event: 'ReplaceSession', - state: { - restore: true - } - }); - - var replaceSessionAll = ActionButton({ - tooltip: 'Restore (close all)', - className: 'repalce-session-all', - event: 'ReplaceSessionAll', - state: { - restore: true - } - }); - - var mergeSession = ActionButton({ - tooltip: 'Merge sessions', - className: 'merge-sessions', - event: 'MenuMergeSessions', - state: { - restore: true - } - }); - - var overwriteSession = ActionButton({ - tooltip: 'Overwrite session', - className: 'replace-session', - event: 'MenuReplaceSession', - state: { - restore: true - } - }); - - var separator1 = DomElem('div', {class: 'separator'}); - var separator2 = DomElem('div', {class: 'separator'}); - var saveConfig = DomElem('div', {class: 'save-config'}); - - // Pin tabs action - - var saveCfgKey = 'session.save'; - var saveCfg = AppConfig.get(saveCfgKey); - var cfgSavePinned = new DOMComponent.ToggleSwitch({ - state: saveCfg.pinned, - tooltip: 'Save pinned tabs', - attribute: 'pin', - onState: '', - offState: '', - callback: function(value) { - saveCfg.pinned = value; - AppConfig.set(saveCfgKey, saveCfg); - } - }); - saveConfig.appendChild(cfgSavePinned.DOMRoot); - - var cfgAllWindows = new DOMComponent.ToggleSwitch({ - state: saveCfg.allWindows, - tooltip: 'Show all windows', - attribute: 'windows', - onState: '', - offState: '', - callback: function(value) { - saveCfg.allWindows = value; - AppConfig.set(saveCfgKey, saveCfg); - } - }); - saveConfig.appendChild(cfgAllWindows.DOMRoot); - - menu.appendChild(restore); - menu.appendChild(restoreW); - menu.appendChild(replaceSession); - menu.appendChild(replaceSessionAll); - menu.appendChild(separator1); - menu.appendChild(mergeSession); - menu.appendChild(overwriteSession); - menu.appendChild(separator2); - menu.appendChild(add); - menu.appendChild(save); - menu.appendChild(saveConfig); - - - // ------------------------------------------------------------------------ - // Events - - AppConfig.onChange(saveCfgKey, function(value) { - cfgSavePinned.setState(value.pinned); - cfgAllWindows.setState(value.allWindows); - }); - - // Tooltip events - menu.addEventListener('mouseover', function(e) { - var tooltipMsg = e.target.getAttribute('tooltip'); - if (tooltipMsg) { - WindowEvents.emit(document, 'ShowTooltip', { - node: e.target, - message: tooltipMsg - }); - } else { - WindowEvents.emit(document, 'HideTooltip'); - } - }); - - menu.addEventListener('mouseleave', function() { - WindowEvents.emit(document, 'HideTooltip'); - }); - - // ------------------------------------------------------------------------ - // Data - - return menu; - }; - - // ***************************************************************************** - // Public API - - exports.SessionToolbar = SessionToolbar; -}); \ No newline at end of file diff --git a/data/scripts/components/tooltip-system.js b/data/scripts/components/tooltip-system.js deleted file mode 100644 index 7fcad39..0000000 --- a/data/scripts/components/tooltip-system.js +++ /dev/null @@ -1,52 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Modules - - const { HTMLCreator } = require('../utils/dom'); - const { WindowEvents } = require('../utils/global-events'); - - // ***************************************************************************** - // TooltipSystem - - function TooltipSystem() - { - var DomElem = HTMLCreator(); - - var tooltip = DomElem('div', {class : 'tooltip-system'}); - - // ------------------------------------------------------------------------ - // Events - - var hideTooltip = function hideTooltip() - { - tooltip.removeAttribute('active'); - }; - - var showTooltip = function showTooltip(options) - { - tooltip.textContent = options.message; - tooltip.setAttribute('active', ''); - var position = options.node.getBoundingClientRect(); - - tooltip.setAttribute('active', ''); - tooltip.style.left = position.x - Math.floor((tooltip.clientWidth - options.node.clientWidth) / 2) + 'px'; - tooltip.style.top = position.y - tooltip.clientHeight + 'px'; - }; - - WindowEvents.on(document, 'ShowTooltip', showTooltip); - WindowEvents.on(document, 'HideTooltip', hideTooltip); - - // ------------------------------------------------------------------------ - // Public data - - this.DOMRoot = tooltip; - } - - // ***************************************************************************** - // Public API - - exports.TooltipSystem = TooltipSystem; - -}); \ No newline at end of file diff --git a/data/scripts/components/trash-can.js b/data/scripts/components/trash-can.js deleted file mode 100644 index e235bc5..0000000 --- a/data/scripts/components/trash-can.js +++ /dev/null @@ -1,126 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - // App - const { AppConfig } = require('../config'); - - const { WindowEvents, GlobalEvents } = require('../utils/global-events'); - const { HTMLCreator } = require('../utils/dom'); - const { DragContext } = require('../utils/drag-context'); - const { SessionFolder } = require('./session-folder'); - const { SessionBookmark, SessionTab } = require('./session-bookmark'); - - // ***************************************************************************** - // API - - function TrashCan() { - - // Create DomHealper - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - var block = DomElem('div', { - class: 'trash-can', - items: 0, - dismiss: true, - tooltip: 'Drop item to delete' - }); - - var count = DomElem('div', {class: 'count', tooltip: 'Click to restore'}); - count.textContent = '0'; - block.appendChild(count); - - var dismiss = DomElem('div', { - class: 'dismiss css-close-button', - tooltip: 'Hide' - }); - - block.appendChild(dismiss); - - var configKey = { - hide: 'trashcan.hide', - hideCount: 'trashcan.hide-count', - undoEvents: 'undo.events', - }; - - // ------------------------------------------------------------------------ - // Events - - // History Undo/Redo events - count.addEventListener('click', function() { - GlobalEvents.emit('HistoryUndo'); - }); - - function updateItemCount(value) { - count.textContent = value; - block.setAttribute('items', value); - AppConfig.set(configKey.hide, value == 0 || value == AppConfig.get(configKey.hideCount)); - } - - block.addEventListener('mouseup', function (e) { - var item = DragContext.getContext(); - if (item instanceof SessionFolder || item instanceof SessionBookmark) { - block.setAttribute('action', 'drop'); - GlobalEvents.emit('DeleteBookmarkItem', item.bookmarkID); - WindowEvents.emit(document, 'ShowTooltip', { - node: e.target, - message: 'Click to restore' - }); - return; - } - - if (item instanceof SessionTab) { - GlobalEvents.emit('management.close-tab', item.tab.id); - } - }); - - dismiss.addEventListener('click', function () { - AppConfig.set(configKey.hide, true); - AppConfig.set(configKey.hideCount, AppConfig.get(configKey.undoEvents).length); - }); - - // Tooltip events - block.addEventListener('mouseover', function(e) { - WindowEvents.emit(document, 'ShowTooltip', { - node: e.target, - message: e.target.getAttribute('tooltip') - }); - }); - - block.addEventListener('mouseleave', function() { - WindowEvents.emit(document, 'HideTooltip'); - }); - - WindowEvents.on(document, 'TrashCan-Droppable', function(value) { - block.setAttribute('droppable', value); - }); - - // ------------------------------------------------------------------------ - // Init code - - - // ------------------------------------------------------------------------ - // Events - - AppConfig.onChange(configKey.hide, function (value) { - block.setAttribute('dismiss', value); - }); - - AppConfig.onChange(configKey.undoEvents, function (value) { - updateItemCount(value.length); - }); - - // ------------------------------------------------------------------------ - // Public properties - - this.DOMRoot = block; - } - - // ***************************************************************************** - // Public API - exports.TrashCan = TrashCan; - -}); \ No newline at end of file diff --git a/data/scripts/components/url-bar.js b/data/scripts/components/url-bar.js deleted file mode 100644 index 15b10b1..0000000 --- a/data/scripts/components/url-bar.js +++ /dev/null @@ -1,91 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ------------------------------------------------------------------------ - // Modules - - const { HTMLCreator } = require('../utils/dom'); - const { WindowEvents } = require('../utils/global-events'); - - // ------------------------------------------------------------------------ - // API - - function UrlBar() - { - var DomElem = HTMLCreator(); - - var url_bar = DomElem('div', {class: 'url-bar'}); - var url_arrow = DomElem('div', {class: 'arrow'}); - var url_value = DomElem('div', {class: 'url'}); - var copy_feedback = DomElem('div', {class: 'copy-feedback'}); - var input = DomElem('input', {class: 'copy-input'}); - - copy_feedback.textContent = 'Copied'; - url_arrow.textContent = 'url'; - url_bar.appendChild(input); - url_bar.appendChild(url_arrow); - url_bar.appendChild(url_value); - url_bar.appendChild(copy_feedback); - - // ------------------------------------------------------------------------ - // Events - - var timeoutID = 0; - var feedbackTimeoutID = 0; - - function clearTimeID() { - clearTimeout(timeoutID); - } - - function showURLInfoBar(url) - { - clearTimeout(timeoutID); - url_value.textContent = url; - url_bar.setAttribute('data-active', 'true'); - url_bar.addEventListener('mouseenter', clearTimeID); - url_bar.addEventListener('mouseleave', hideURLInfoBar); - } - - function hideURLInfoBar() - { - timeoutID = setTimeout(function() { - url_bar.removeAttribute('data-active'); - url_bar.removeEventListener('mouseleave', clearTimeID); - url_bar.removeEventListener('mouseenter', hideURLInfoBar); - }, 1000); - } - - function copyURLToClipboard() - { - input.value = url_value.textContent; - input.select(); - document.execCommand('copy'); - - clearTimeout(feedbackTimeoutID); - copy_feedback.setAttribute('active', ''); - feedbackTimeoutID = setTimeout(function() { - copy_feedback.removeAttribute('active'); - }, 1500); - } - - WindowEvents.on(document, 'ShowUrlBar', showURLInfoBar); - WindowEvents.on(document, 'HideUrlBar', hideURLInfoBar); - WindowEvents.on(document, 'UrlBar-CopyURL', copyURLToClipboard); - - url_bar.addEventListener('click', copyURLToClipboard); - - // ------------------------------------------------------------------------ - // Public data - - this.DOMRoot = url_bar; - } - - UrlBar.prototype.setAutoHideDelay = function setAutoHideDelay(miliseconds) { - this.hide_delay = miliseconds; - }; - - // ------------------------------------------------------------------------ - // Module exports - - exports.UrlBar = UrlBar; -}); diff --git a/data/scripts/config.js b/data/scripts/config.js deleted file mode 100644 index 2bf5552..0000000 --- a/data/scripts/config.js +++ /dev/null @@ -1,220 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { PubSub } = require('./3rd/pubsub'); - - // Utils - const { GlobalEvents } = require('./utils/global-events'); - - // ***************************************************************************** - // API - - var Config = (function() { - - var initState = true; - var initConfig = true; - var localSettings = {}; - var configEvents = new PubSub(); - var isPanelUI = (document.body.clientWidth == 0); - - // ------------------------------------------------------------------------ - // API - - function getConfigValue(key, defaultValue) - { - if (initConfig && localSettings[key] == undefined) { - localSettings[key] = defaultValue; - return; - } - - browser.storage.local.get(key).then(function (obj) { - if (obj.hasOwnProperty(key)) - { - // console.log('[LocalStorage][Read]', key, obj[key]); - localSettings[key] = obj[key]; - configEvents.publish(key, obj[key]); - } - else - { - // console.log('Not found [LocalStorage][Set]', key, defaultValue); - browser.storage.local.set({ [key] : defaultValue }) - .then(function success() { - localSettings[key] = defaultValue; - configEvents.publish(key, defaultValue); - }); - } - }); - } - - var setupConfig = function setupConfig() - { - // Styling - getConfigValue('style.panel.width', 800); - getConfigValue('style.panel.height', 600); - getConfigValue('style.sessions.list.width', 220); - - getConfigValue('style.scale.toolbar', 12); - getConfigValue('style.scale.header', 18); - getConfigValue('style.scale.sessions', 12); - getConfigValue('style.scale.bookmarks', 14); - - getConfigValue('state.scrollTop.current', 0); - getConfigValue('state.scrollTop.restore', 0); - getConfigValue('state.scrollTop.history', 0); - - // Session saving settings - getConfigValue('session.save', { - pinned: false, - allWindows: true - }); - - // Management configuration - getConfigValue('session.view', undefined); - getConfigValue('session.sorting', 'position-asc'); - getConfigValue('session.active.filter', ''); - getConfigValue('session.selected', null); - getConfigValue('session.history.selected', null); - getConfigValue('services.favicon.enabled', true); - - // Bookmarks configuration - getConfigValue('bookmark.click.newTab', false); - getConfigValue('bookmark.middleClick.newTab', true); - getConfigValue('restore.lazy.loading', true); - getConfigValue('restore.reverse.order', false); - - // General settings - getConfigValue('context.menu.icons', true); - - getConfigValue('trashcan.hide', true); - getConfigValue('trashcan.hide-count', 0); - getConfigValue('undo.events', []); - - getConfigValue('troubleshooting', false); - }; - - var set = function set(key, value, onSuccess) - { - if (typeof value != 'object' && localSettings[key] == value) - { - if (typeof onSuccess === 'function') { - onSuccess(); - } - return; - } - - browser.storage.local.set({ [key] : value }) - .then(function success() { - localSettings[key] = value; - // console.log('[Change config]', key, value); - configEvents.publish(key, value); - if (typeof onSuccess === 'function') { - onSuccess(); - } - }, function onError(error) { - console.log(error); - }); - }; - - var get = function get(key) - { - if (localSettings[key] === undefined) { - console.log('[ERROR] Settings key [' + key + '] was not found!'); - } - // console.log(key, localSettings[key]); - return localSettings[key]; - }; - - var isInitState = function isInitState() - { - if (initState == true) { - initState = false; - return true; - } - return false; - }; - - var isPanel = function isPanel() - { - return isPanelUI; - }; - - var devMode = function devMode() - { - return false; - }; - - var isAddonContext = function isAddonContext() - { - return typeof browser === 'object'; - }; - - var onChange = function onChange(topic, callback) { - configEvents.subscribe(topic, callback) - }; - - // ------------------------------------------------------------------------ - // Startup - - var init = function init() - { - setupConfig(); - // console.log(localSettings); - - if (isAddonContext()) { - browser.commands.getAll(). - then(function (commands) { - configEvents.publish('config.hotkey.init', commands); - }); - } - }; - - if (isAddonContext()) { - - // console.log(''); - // console.log('------------------------------------------'); - - // var manifest = browser.runtime.getManifest(); - // console.log(manifest); - // console.log(manifest.name + ' v' + manifest.version); - // console.log('------------------------------------------'); - - setupConfig(); - initConfig = false; - - GlobalEvents.on('hotkey.update', function (command) { - browser.commands.update({ - name: command.name, - shortcut: command.shortcut - }); - }); - } - - // ------------------------------------------------------------------------ - // Public API - - return { - set: set, - get: get, - init : init, - onChange: onChange, - devMode: devMode, - isPanel: isPanel, - isInitState: isInitState, - isAddonContext: isAddonContext - }; - })(); - - // ------------------------------------------------------------------------ - // Events - - // ------------------------------------------------------------------------ - // Init - - // ------------------------------------------------------------------------ - // Module exports - - exports.AppConfig = Config; -}); \ No newline at end of file diff --git a/data/scripts/require-config.js b/data/scripts/require-config.js deleted file mode 100644 index e3220e5..0000000 --- a/data/scripts/require-config.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -require.config({ - - baseURL: '/scripts', - - nodeIdCompat : true, - -}); - -define(function(require) { - - // Load Modules - var App = require('app'); - - // Init - App.init(); - -}); \ No newline at end of file diff --git a/data/scripts/session-sync-ui.js b/data/scripts/session-sync-ui.js deleted file mode 100644 index 7c8ccaf..0000000 --- a/data/scripts/session-sync-ui.js +++ /dev/null @@ -1,470 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ************************************************************************ - // Modules - - // App - const { AppConfig } = require('./config'); - - // Utils - const { HTMLCreator } = require('./utils/dom'); - const { WindowEvents, GlobalEvents } = require('./utils/global-events'); - - const { ContextMenu } = require('./utils/context-menu'); - const { ConfirmBox } = require('./utils/confirm-box'); - const { FieldEditWdiget } = require('./utils/field-edit-widget'); - - // Components - const { Clipboard } = require('./components/clipboard'); - const { UrlBar } = require('./components/url-bar'); - const { TrashCan } = require('./components/trash-can'); - const { TooltipSystem } = require('./components/tooltip-system'); - const { SessionHotkeys } = require('./components/session-hotkeys'); - const { NotificationSystem } = require('./components/notification-system'); - const { ConfigPanel } = require('./components/config-panel'); - const { OverlaySystem } = require('./components/overlay-system'); - - const { SessionHotkeyManager } = require('./components/session-hotkey-manager'); - const { SessionSorting } = require('./components/session-sorting'); - const { SessionFiltering } = require('./components/session-filtering'); - - const { SessionHeaderBar } = require('./components/session-header-bar'); - const { SessionList } = require('./components/session-list'); - const { SessionContainer } = require('./components/session-container'); - const { SessionSyncModel } = require('./components/session-sync-model'); - const { SessionToolbar } = require('./components/session-toolbar'); - - // Bookmarks - const { SessionManager } = require('./session/management'); - const { BookmarkManager } = require('./session/bookmarks'); - - // ************************************************************************ - // UI initilization - - var SessionSyncUI = function SessionSyncUI() { - - var DomElem = HTMLCreator(); - - document.body.setAttribute('panel', AppConfig.isPanel()); - var content = document.getElementById('body'); - - // ------------------------------------------------------------------------ - // UI Modules - - var configPanel = new ConfigPanel(); - content.appendChild(configPanel.DOMRoot); - - var tooltipSystem = new TooltipSystem(); - content.appendChild(tooltipSystem.DOMRoot); - - var notificationSystem = new NotificationSystem(); - content.appendChild(notificationSystem.DOMRoot); - - var hotekyManager = new SessionHotkeyManager(); - content.appendChild(hotekyManager.DOMRoot); - - var overlaySystem = new OverlaySystem(); - content.appendChild(overlaySystem.DOMRoot); - - // ------------------------------------------------------------- - // Create the UI - var centralArea = DomElem('div', {class: 'sessions-area'}); - - // ------------------------------------------------------------- - // Clipboard - - new Clipboard(); - - // ------------------------------------------------------------- - // GlobalHotkeys - - new SessionHotkeys(); - - // -------------------------------------------------------------------- - // Center area - - var headerBar = new SessionHeaderBar(); - var toolbar = new SessionToolbar(); - var sessionList = new SessionList(); - var searchBar = new SessionFiltering(); - var sessionContainer = new SessionContainer(); - var sessionSorting = new SessionSorting(); - - headerBar.DOMRoot.appendChild(searchBar.DOMRoot); - sessionList.DOMRoot.appendChild(sessionSorting.DOMRoot); - - var body = DomElem('div', {class: 'sessions-area-body'}); - body.appendChild(sessionList.DOMRoot); - body.appendChild(sessionContainer.DOMRoot); - - var resizeHandle = DomElem('div', {class: 'panel-resize-handle'}); - - // ------------------------------------------------------------- - // Trash can - - var trashCan = new TrashCan(); - centralArea.appendChild(trashCan.DOMRoot); - - // -------------------------------------------------------------------- - // URL bar - - var urlBar = new UrlBar(); - headerBar.DOMRoot.appendChild(urlBar.DOMRoot); - - centralArea.appendChild(resizeHandle); - centralArea.appendChild(headerBar.DOMRoot); - centralArea.appendChild(toolbar.DOMRoot); - centralArea.appendChild(body); - content.appendChild(centralArea); - - // ------------------------------------------------------------------------ - // Context Menues - - // Bookmark Item Context Menu - var BM = new ContextMenu({name : 'BookmarkCtxMenu'}); - BM.addMenuEntry({value: 'Open (new tab)', event: 'OpenInNewTab', icon: 'new-tab'}); - if (AppConfig.isPanel()) { - BM.addMenuEntry({value: 'Open (current tab)', event: 'OpenInActiveTab', icon: 'same-tab'}); - } - BM.addMenuEntry({value: 'Open (new window)', event: 'OpenInNewWindow', icon: 'new-window'}); - BM.addMenuEntry({value: 'Copy URL', event: 'CopyURL', icon: 'copy'}); - BM.addMenuEntry({value: 'Copy Title', event: 'CopyTitle', icon: 'copy'}); - BM.addMenuEntry({value: 'Edit', event: 'EditBookmark', icon: 'edit'}); - BM.addMenuEntry({value: 'Delete', globalEvent: 'DeleteBookmarkItem', icon: 'delete', separator: 'top'}); - content.appendChild(BM.DOMRoot); - - // Session Context Menu - var SM = new ContextMenu({name : 'SessionContextMenu'}); - SM.addMenuEntry({value: 'Restore', event: 'RestoreSession', icon: 'same-window'}); - SM.addMenuEntry({value: 'Restore (new window)', event: 'RestoreInNewWindow', icon: 'new-window'}); - SM.addMenuEntry({value: 'Restore (close active)', event: 'ReplaceSession', icon: 'replace-current'}); - SM.addMenuEntry({value: 'Restore (close all)', event: 'ReplaceSessionAll', icon: 'replace-all'}); - SM.addMenuEntry({value: 'Edit', event: 'EditSession', icon: 'edit'}); - SM.addMenuEntry({value: 'Delete', globalEvent: 'DeleteBookmarkItem', icon: 'delete'}); - SM.addMenuEntry({value: 'Create session', callback: SessionManager.createNewSession, icon: 'new-session', separator: 'top'}); - SM.addMenuEntry({value: 'Insert separator', callback: SessionManager.createSeparator, icon: 'new-session'}); - content.appendChild(SM.DOMRoot); - - var SCBM = new ContextMenu({name : 'SessionContextBookmarkMenu'}); - SCBM.addMenuEntry({value: 'Edit', event: 'EditBookmark', icon: 'edit'}); - SCBM.addMenuEntry({value: 'Delete', globalEvent: 'DeleteBookmarkItem', icon: 'delete'}); - SCBM.addMenuEntry({value: 'Create session', callback: SessionManager.createNewSession, icon: 'new-session', separator: 'top'}); - SCBM.addMenuEntry({value: 'Insert separator', callback: SessionManager.createSeparator, icon: 'new-session'}); - content.appendChild(SCBM.DOMRoot); - - var SCSM = new ContextMenu({name : 'SessionContextSeparatorMenu'}); - SCSM.addMenuEntry({value: 'Delete', globalEvent: 'DeleteBookmarkItem', icon: 'delete'}); - SCSM.addMenuEntry({value: 'Create session', callback: SessionManager.createNewSession, icon: 'new-session', separator: 'top'}); - SCSM.addMenuEntry({value: 'Insert separator', callback: SessionManager.createSeparator, icon: 'new-session'}); - content.appendChild(SCSM.DOMRoot); - - var SLM = new ContextMenu({name : 'SessionListMenu'}); - SLM.addMenuEntry({value: 'Create session', callback: SessionManager.createNewSession, icon: 'new-session'}); - content.appendChild(SLM.DOMRoot); - - // History List Context Menu - var confirmDeleteAll = function confirmDeleteAll(context, event) - { - WindowEvents.emit(document, 'ConfirmBox-Open', { - event: event, - message: 'Clear history?', - callback : function() { - GlobalEvents.emit('HistorySessionDeleteAll'); - } - }); - }; - - var HSML = new ContextMenu({name : 'HistoryListCtxMenu'}); - HSML.addMenuEntry({value: 'Delete All', callback: confirmDeleteAll, icon: 'delete'}); - content.appendChild(HSML.DOMRoot); - - // History Items Context Menu - - var confirmDeleteOne = function confirmDeleteOne(context, event) - { - WindowEvents.emit(document, 'ConfirmBox-Open', { - event: event, - message: 'Confirm delete?', - callback : function() { - GlobalEvents.emit('HistorySessionDelete', context.getAttribute('index')); - } - }); - }; - - var HSM = new ContextMenu({name : 'HistorySessionCtxMenu'}); - HSM.addMenuEntry({value: 'Save', event: 'Save', icon: 'heart'}); - HSM.addMenuEntry({value: 'Restore (new window)', event: 'Restore', icon: 'new-window'}); - HSM.addMenuEntry({value: 'Delete', callback: confirmDeleteOne, icon: 'delete', separator: 'top'}); - content.appendChild(HSM.DOMRoot); - - // History Context Menu - var HBM = new ContextMenu({name : 'HistoryMarksCtxMenu'}); - HBM.addMenuEntry({value: 'Open (new tab)', event: 'OpenInNewTab', icon: 'new-tab'}); - if (AppConfig.isPanel()) { - HBM.addMenuEntry({value: 'Open (current tab)', event: 'OpenInActiveTab', icon: 'same-tab'}); - } - HBM.addMenuEntry({value: 'Open (new window)', event: 'OpenInNewWindow', icon: 'new-window'}); - HBM.addMenuEntry({value: 'Copy URL', event: 'CopyURL', icon: 'copy'}); - content.appendChild(HBM.DOMRoot); - - // Field edit widgets - var BEP = new FieldEditWdiget({name: 'BookmarkEditWidget'}); - BEP.addField({label: 'Name', name: 'title'}); - BEP.addField({label: 'Location', name: 'url'}); - // BEP.addField({label: 'Description', name: 'desc'}); - centralArea.appendChild(BEP.DOMRoot); - - // Field edit Info - var SEP = new FieldEditWdiget({name: 'SessionFolderEditWidget'}); - SEP.addField({label: 'Title', name: 'title'}); - // SEP.addField({label: 'Description', name: 'desc'}); - centralArea.appendChild(SEP.DOMRoot); - - // Confirm Box - var CB = new ConfirmBox({name: 'ConfirmBox'}); - content.appendChild(CB.DOMRoot); - - // ------------------------------------------------------------------------ - // Panel States - - var UI = content.parentElement; - function setUIState(props) - { - var model = SessionSyncModel.getModel(document); - for (var key in props) { - model.state[key] = props[key]; - props[key] !== undefined ? UI.setAttribute(key, props[key]) : UI.removeAttribute(key); - } - } - - // ------------------------------------------------------------------------ - // App Events - - WindowEvents.on(document, 'SetUIState', setUIState); - - WindowEvents.on(document, 'CopyURL', function (url) { - WindowEvents.emit(document, 'ShowUrlBar', url); - WindowEvents.emit(document, 'UrlBar-CopyURL'); - }); - - WindowEvents.on(document, 'CopyText', function () { - WindowEvents.emit(document, 'UrlBar-CopyURL'); - }); - - // SessionContextMenu events - - WindowEvents.on(document, 'SessionContextMenu-EditSession', function (sessionID) { - if (SessionSyncModel.bookmarks[sessionID]) - { - var type = SessionSyncModel.bookmarks[sessionID].type; - switch (type) - { - case 'folder': - WindowEvents.emit(document, 'SessionFolderEditWidget-Invoke', { - context : sessionID, - fields : { - title : SessionSyncModel.bookmarks[sessionID].title, - } - }); - break; - - case 'bookmark': - WindowEvents.emit(document, 'BookmarkCtxMenu-EditBookmark', sessionID); - break; - } - } - }); - - WindowEvents.on(document, 'SessionContextMenu-RestoreSession', SessionManager.restoreSession); - WindowEvents.on(document, 'SessionContextMenu-RestoreInNewWindow', SessionManager.restoreNewWindow); - WindowEvents.on(document, 'SessionContextMenu-ReplaceSession', SessionManager.replaceSession); - WindowEvents.on(document, 'SessionContextMenu-ReplaceSessionAll', SessionManager.replaceSessionAll); - - // BookmarksCtxMenu events - - function InvokeBookmarkEdit(bookmarkID) - { - WindowEvents.emit(document, 'BookmarkEditWidget-Invoke', { - context : bookmarkID, - fields : { - title : SessionSyncModel.bookmarks[bookmarkID].title, - url : SessionSyncModel.bookmarks[bookmarkID].url, - } - }); - } - - WindowEvents.on(document, 'SessionContextBookmarkMenu-EditBookmark', InvokeBookmarkEdit); - WindowEvents.on(document, 'BookmarkCtxMenu-EditBookmark', InvokeBookmarkEdit); - - WindowEvents.on(document, 'BookmarkCtxMenu-CopyURL', function (bookmarkID) { - WindowEvents.emit(document, 'CopyURL', SessionSyncModel.bookmarks[bookmarkID].url); - }); - - WindowEvents.on(document, 'BookmarkCtxMenu-CopyTitle', function (bookmarkID) { - WindowEvents.emit(document, 'CopyToClipboard', SessionSyncModel.bookmarks[bookmarkID].title); - }); - - WindowEvents.on(document, 'BookmarkCtxMenu-OpenInNewTab', function (bookmarkID) { - BookmarkManager.openBookmark({ - url: SessionSyncModel.bookmarks[bookmarkID].url, - mode: 'newTab', - favicon: true, - }); - }); - - WindowEvents.on(document, 'BookmarkCtxMenu-OpenInActiveTab', function (bookmarkID) { - BookmarkManager.openBookmark({ - url: SessionSyncModel.bookmarks[bookmarkID].url, - mode: 'activeTab', - favicon: true, - }); - }); - - WindowEvents.on(document, 'BookmarkCtxMenu-OpenInNewWindow', function (bookmarkID) { - BookmarkManager.openBookmark({ - url: SessionSyncModel.bookmarks[bookmarkID].url, - mode: 'newWindow', - favicon: true, - }); - }); - - // HistoryMarksCtxMenu events - - WindowEvents.on(document, 'HistoryMarksCtxMenu-CopyURL', function (url) { - WindowEvents.emit(document, 'CopyURL', url); - }); - - WindowEvents.on(document, 'HistoryMarksCtxMenu-OpenInNewTab', function (url) { - BookmarkManager.openBookmark({ - url: url, - mode: 'newTab' - }); - }); - - WindowEvents.on(document, 'HistoryMarksCtxMenu-OpenInActiveTab', function (url) { - BookmarkManager.openBookmark({ - url: url, - mode: 'activeTab' - }); - }); - - WindowEvents.on(document, 'HistoryMarksCtxMenu-OpenInNewWindow', function (url) { - BookmarkManager.openBookmark({ - url: url, - mode: 'newWindow' - }); - }); - - // HistorySessionCtxMenu events - - WindowEvents.on(document, 'HistorySessionCtxMenu-Save', function (context) { - WindowEvents.emit(document, 'HistorySessionSave', context.getAttribute('index')); - }); - - WindowEvents.on(document, 'HistorySessionCtxMenu-Restore', function (context) { - WindowEvents.emit(document, 'HistorySessionRestore', context.getAttribute('index')); - }); - - WindowEvents.on(document, 'HistorySessionCtxMenu-Delete', function (context) { - GlobalEvents.emit('HistorySessionDelete', context.getAttribute('index')); - }); - - // Edit events - - function updateBookmarkInfo(data) - { - SessionSyncModel.updateBookmarkItem(data.context, data.fields); - } - - WindowEvents.on(document, 'SessionFolderEditWidget-Save', updateBookmarkInfo); - WindowEvents.on(document, 'BookmarkEditWidget-Save', updateBookmarkInfo); - - var startResize = function startResize(e) - { - var startPosX = e.clientX; - var startPosY = e.clientY; - var initWidth = document.body.clientWidth; - var initHeight = document.body.clientHeight; - - resizeHandle.setAttribute('resizing', 'true'); - WindowEvents.emit(document, 'SetUIState', {resize: 'true'}); - - function eventMove(e) - { - var deltaX = e.clientX - startPosX; - var deltaY = e.clientY - startPosY; - - var newWidth = Math.max(Math.min(initWidth - deltaX, 800), 500); - var newHeight = Math.max(Math.min(initHeight + deltaY, 600), 400); - - // This is required only for when document body is changing at the same time - // such is the case with Webextensions panels - initWidth = newWidth; - - AppConfig.set('style.panel.width', newWidth); - AppConfig.set('style.panel.height', newHeight); - } - - function evenUp() - { - WindowEvents.emit(document, 'SetUIState', {resize: undefined}); - resizeHandle.removeAttribute('resizing'); - document.removeEventListener('mouseup', evenUp); - document.removeEventListener('mousemove', eventMove); - } - - document.addEventListener('mouseup', evenUp); - document.addEventListener('mousemove', eventMove); - }; - - if (AppConfig.isPanel()) - { - AppConfig.onChange('style.panel.width', function (size) { - document.body.style.width = size + 'px'; - }); - - AppConfig.onChange('style.panel.height', function (size) { - document.body.style.height = size + 'px'; - }); - - resizeHandle.addEventListener('mousedown', startResize); - } - else - { - window.addEventListener('resize', function () { - var newWidth = Math.max(window.innerWidth, 500); - var newHeight = Math.max(window.innerHeight, 400); - - browser.storage.local.set({'style.window.detach' : { - // left: window.screenX, - // top: window.screenY, - width: newWidth, - height: newHeight - }}); - }); - document.body.style.minWidth = '500px'; - } - - function preventDefault(e) { - e.preventDefault(); - e.stopPropagation(); - } - - document.addEventListener('contextmenu', preventDefault); - document.addEventListener('mousedown', function (e) { - // Middle Click - if (e.button == 1) - preventDefault(e); - }); - - // ------------------------------------------------------------------------ - // App Init - - }; - - // ************************************************************************ - // Module exports - - exports.SessionSyncUI = SessionSyncUI; -}); diff --git a/data/scripts/session/bookmarks.js b/data/scripts/session/bookmarks.js deleted file mode 100644 index 7949ad5..0000000 --- a/data/scripts/session/bookmarks.js +++ /dev/null @@ -1,180 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ************************************************************************ - // Modules - - const { AppConfig } = require('../config'); - - // ************************************************************************ - // API - - var BookmarkManager = (function BookmarkManager() { - - var urlParser = document.createElement('a'); - var lazyLoadingUrl = browser.extension.getURL('data/lazy/lazy.html'); - - function getLazyLoadingParameters(url) - { - let paramater = {}; - let params = url.split('?')[1].split('&'); - for (let p of params) { - paramater[p.split('=')[0]] = decodeURIComponent(p.split('=')[1]); - } - return paramater; - } - - var getHostName = function getHostName(url) - { - urlParser.href = url; - return urlParser.hostname; - }; - - var getFaviconUrl = function getFaviconUrl(favIconUrl, callback) - { - var key = '@favIconUrl:' + getHostName(favIconUrl); - browser.storage.local.get(key) - .then(function (obj) { - if (obj[key]) { - callback(obj[key]); - } - }); - }; - - var getFolderBookmarks = function getFolderBookmarks(folderID, callback) - { - var result = browser.bookmarks.getChildren(folderID); - result.then(callback, function fail() { - console.log('Error getting bookmarks from folder ID: ', folderID); - }); - }; - - var searchBookmarks = function searchBookmarks(keyword) - { - return browser.bookmarks.search(keyword); - }; - - var getOpenMode = function getOpenMode(index) - { - var buttons = ['click', 'middleClick']; - var state = AppConfig.get('bookmark.' + buttons[index] + '.newTab'); - return state ? 'newTab' : 'activeTab'; - }; - - var createBookmarkFromTab = function createBookmarkFromTab(tab, parentId) - { - var url = tab.url; - if (url.startsWith(lazyLoadingUrl, 0)) - { - var info = getLazyLoadingParameters(url); - url = info.url; - } - else - { - browser.runtime.sendMessage({event: 'save-favicon', tab: tab}); - } - - return browser.bookmarks.create({ - url: url, - title: tab.title, - parentId: parentId - }); - - }; - - var createBookmark = function createBookmark(options) - { - return browser.bookmarks.create(options); - }; - - var openBookmark = function openBookmark(options) - { - if (AppConfig.isPanel() == false) - { - if (options.mode == 'activeTab') - { - options.mode = 'newTab'; - options.active = false; - } - } - - browser.runtime.sendMessage({event: 'open-tab', options: options}); - }; - - var setTitle = function setTitle(bookmark, title, callback) - { - browser.bookmarks.update(bookmark.id, { - title: title - }).then(function success() { - bookmark.title = title; - bookmark.title_lowercase = title.toLowerCase(); - if (typeof callback === 'function') - callback(true); - }, function error() { - throw 'error'; - }); - }; - - var setLocation = function setLocation(bookmark, url) - { - browser.bookmarks.update(bookmark.id, { - url: url - }).then(function success() { - bookmark.url = url; - }, function error() { - throw 'error'; - }); - }; - - var moveItem = function moveItem(itemID, newParentID, index, callback, errorCallback) - { - browser.bookmarks.move(itemID, { - parentId: newParentID, - index: index - }) - .then(callback, errorCallback); - }; - - var deleteItem = function deleteItem(itemID, isFolder, callback, errorCallback) - { - if (isFolder) - { - browser.bookmarks.removeTree(itemID) - .then(callback, errorCallback); - } - else - { - browser.bookmarks.remove(itemID) - .then(callback, errorCallback); - } - }; - - return { - getFaviconUrl: getFaviconUrl, - getOpenMode: getOpenMode, - openBookmark: openBookmark, - createBookmark: createBookmark, - createBookmarkFromTab: createBookmarkFromTab, - searchBookmarks: searchBookmarks, - - setTitle: setTitle, - setLocation: setLocation, - moveItem: moveItem, - deleteItem: deleteItem, - - getFolderBookmarks : getFolderBookmarks - }; - - })(); - - // ************************************************************************ - // Events - - // ************************************************************************ - // Init - - // ************************************************************************ - // Module exports - - exports.BookmarkManager = BookmarkManager; -}); \ No newline at end of file diff --git a/data/scripts/session/management.js b/data/scripts/session/management.js deleted file mode 100644 index 6fa938d..0000000 --- a/data/scripts/session/management.js +++ /dev/null @@ -1,425 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { AppConfig } = require('../config'); - const { BookmarkManager } = require('./bookmarks'); - const { WindowEvents, GlobalEvents } = require('../utils/global-events'); - const { LimitCounter } = require('../utils/general'); - const { SessionSyncModel } = require ('../components/session-sync-model'); - - // ***************************************************************************** - - var SessionManager = (function SessionManager() { - - var trackTabs = true; - - var getCurrentTab = function getCurrentTab(callback) - { - browser.tabs.query({active: true, windowId: browser.windows.WINDOW_ID_CURRENT}) - .then(function(tabs) { - callback(tabs[0]); - }); - }; - - var getWindowTab = function getWindowTab(tabIndex, windowId, callback) - { - browser.tabs.query({index: tabIndex, windowId: windowId}) - .then(function(tabs) { - callback(tabs[0]); - }); - }; - - var getCurrentWindow = function getCurrentWindow(callback) { - browser.windows.getCurrent({ populate: true }) - .then(callback); - }; - - var getAllWindows = function getAllWindows(callback) { - browser.windows.getAll({ - populate: true, - windowTypes: ['normal'] - }) - .then(callback); - }; - - var tabTracking = function tabTracking() - { - return trackTabs; - }; - - var moveTab = function moveTab(tabID, newIndex, windowID) - { - trackTabs = false; - browser.tabs.move([tabID], { - index: newIndex, - windowId: windowID - }).then(function() { - GlobalEvents.emit('UpdateCurrentSession'); - trackTabs = true; - }, function onError() { - trackTabs = true; - }); - }; - - var activateTab = function activateTab(tabID) - { - browser.tabs.update(tabID, { - active: true - }); - }; - - var closeTab = function closeTab(tabID) - { - browser.tabs.remove(tabID); - }; - - var activateWindow = function activateWindow(windowID) - { - browser.windows.update(windowID, { - focused: true - }); - }; - - var saveActiveSession = function saveActiveSession() - { - getCurrentWindow(function(mozWindow) { - - var sessionID = 0; - var sessionCount = 0; - - function updateOnSave() - { - WindowEvents.emit(document, 'SetPromiseSession', { - update: true, - sessionID: sessionID, - edit: sessionCount == 1, - }); - } - - // Get non-private windows - if (mozWindow.incognito == true || AppConfig.get('session.save').allWindows == false) - { - var sessionTitle = (new Date()).toLocaleString(); - saveWindowSession({ - title: sessionTitle, - tabs: mozWindow.tabs - }, function onSuccess(folder) { - sessionID = folder.id; - sessionCount++; - updateOnSave(); - }); - } - else - { - // Get all non private windows - getAllWindows(function(windows) { - // Save windows as separate sessions - var date = new Date(); - - windows.forEach(function (mozWindow) { - if (mozWindow.incognito == false) { - sessionCount++; - } - }); - - var windowID = 0; - var updateEvent = new LimitCounter(sessionCount, updateOnSave); - - windows.forEach(function (mozWindow) { - if (mozWindow.incognito == false) - { - windowID++; - let sessionTitle = date.toLocaleString() + ((sessionCount > 1) ? (' #' + windowID) : ''); - saveWindowSession({ - title: sessionTitle, - tabs: mozWindow.tabs, - }, function(folder) { - sessionID = folder.id; - updateEvent.advance(); - }); - } - }); - }); - } - }); - }; - - var mergeSessions = function mergeSessions(sessionID) - { - // Exclude all pages that are already saved - getCurrentWindow(function (mozWindow) { - - var toSave = {}; - - mozWindow.tabs.forEach(tab => { - let url = tab.url; - toSave[url] = tab; - }); - - // Exclude all pages that are already saved - BookmarkManager.getFolderBookmarks(sessionID, function(bookmarks) { - bookmarks.forEach(function (bookmark) { - delete toSave[bookmark.url]; - }); - - var updateEvent = new LimitCounter(Object.keys(toSave).length, function () { - WindowEvents.emit(document, 'ViewSession', sessionID); - }); - - // TODO - reverse saving order - var savingList = []; - for (let key in toSave) - { - let tab = toSave[key]; - savingList.push(tab); - } - - for (let i = savingList.length - 1; i >= 0; i--) - { - var tab = savingList[i]; - BookmarkManager.createBookmarkFromTab(tab, sessionID) - .then(updateEvent.advance, updateEvent.advance); - } - }); - }); - }; - - var overwriteSession = function overwriteSession(sessionID) - { - var toRemove = []; - - BookmarkManager.getFolderBookmarks(sessionID, function(bookmarks) { - - // Remove all bookmarks from the current session - bookmarks.forEach(function (bookmark) { - toRemove.push(bookmark.id); - }); - - SessionSyncModel.deleteBookmarkList(toRemove, sessionID, function () { - getCurrentWindow(function(mozWindow) { - - var updateFinish = new LimitCounter(mozWindow.tabs.length, function () { - WindowEvents.emit(document, 'ViewSession', sessionID); - }); - - // TODO - add option to discard duplicate tabs and or pinned tabs - - for (let i = mozWindow.tabs.length - 1; i >= 0; i--) - { - var tab = mozWindow.tabs[i]; - BookmarkManager.createBookmarkFromTab(tab, sessionID) - .then(updateFinish.advance, updateFinish.advance); - } - }); - }); - }); - }; - - var saveHistorySession = function saveHistorySession(sessionInfo) - { - var windowCount = sessionInfo.windows.length; - - var windowID = 0; - - function onSave(bookmark) { - WindowEvents.emit(document, 'SetPromiseSession', { - sessionID: bookmark.id, - edit: false, - update: true - }); - } - - // save all history windows - for (var key in sessionInfo.windows) - { - var date = new Date(sessionInfo.lastSave); - var sessionTitle = date.toLocaleString() + ((windowCount > 1) ? (' #' + windowID) : ''); - windowID++; - - saveWindowSession({ - title: sessionTitle, - tabs: sessionInfo.windows[key].tabs - }, onSave); - } - }; - - var saveWindowSession = function saveWindowSession(sessionInfo, callback) - { - BookmarkManager.createBookmark({ - title: sessionInfo.title, - parentId: AppConfig.get('storageID') - }) - .then(function (folder) { - - var trackSaving = new LimitCounter(sessionInfo.tabs.length, function () { - callback(folder); - }); - - // TODO - add option to discard duplicate tabs - - var savePinned = AppConfig.get('session.save').pinned; - - for (let i = sessionInfo.tabs.length - 1; i >= 0; i--) - { - let tab = sessionInfo.tabs[i]; - if (tab.pinned && savePinned == false) - { - trackSaving.offsetLimit(-1); - } - else - { - BookmarkManager.createBookmarkFromTab(tab, folder.id) - .then(trackSaving.advance, trackSaving.advance); - } - } - }); - }; - - var restoreSession = function restoreSession(folderID) - { - browser.runtime.sendMessage({event: 'restore-session', bookmarkID: folderID}); - }; - - var restoreNewWindow = function restoreNewWindow(folderID) - { - var options = { - newWindow: true - }; - browser.runtime.sendMessage({event: 'restore-session', bookmarkID: folderID, options: options}); - }; - - var restoreAdvanced = function restoreAdvanced(folderID, options) - { - browser.runtime.sendMessage({event: 'restore-session', bookmarkID: folderID, options: options}); - }; - - var replaceSession = function replaceSession(folderID) - { - getCurrentWindow(function(mozWindow) { - var options = { - newWindow: true, - closeWindow: mozWindow.id, - }; - restoreAdvanced(folderID, options); - }); - }; - - var replaceSessionAll = function replaceSessionAll(folderID) - { - var options = { - newWindow: true, - closeAll: true, - }; - restoreAdvanced(folderID, options); - }; - - var loadBookmarksNewWindow = function loadBookmarksNewWindow(windows) - { - browser.runtime.sendMessage({event: 'restore-sessions', windows: windows }); - }; - - var fixLazySession = function fixLazySession(folderID) - { - browser.runtime.sendMessage({event: 'fix-lazy-session', bookmarkID: folderID }); - }; - - var createSeparator = function createSeparator(folderID) { - browser.bookmarks.get(folderID) - .then(function (marks) { - BookmarkManager.createBookmark({ - index: marks[0].index + 1, - type: 'separator', - parentId: AppConfig.get('storageID') - }) - .then(function success(separator) { - WindowEvents.emit(document, 'SetPromiseSession', { sessionID: separator.id, update: true }); - }); - }, function onError(msg) { - console.log(msg); - }); - }; - - var createSession = function createSession(title, index) - { - BookmarkManager.createBookmark({ - index: index, - title: title, - parentId: AppConfig.get('storageID') - }) - .then(function success(session) { - WindowEvents.emit(document, 'SetPromiseSession', { - sessionID: session.id, - edit: true, - update: true - }); - }, function error() { - }); - }; - - var createNewSession = function createNewSession(folderID) { - var sessionTitle = (new Date()).toLocaleString(); - if (folderID) - { - browser.bookmarks.get(folderID) - .then(function (marks) { - createSession(sessionTitle, marks[0].index + 1); - }); - } - else - { - createSession(sessionTitle); - } - }; - - // Events - GlobalEvents.on('CreateNewSession', function(name) { - var title = name ? name : (new Date()).toLocaleString(); - createSession(title); - }); - - // Events - GlobalEvents.on('management.close-tab', function(tabID) { - trackTabs = false; - browser.tabs.remove(tabID) - .then(function () { - GlobalEvents.emit('UpdateCurrentSession'); - trackTabs = true; - }); - }); - - // Public API - return { - moveTab: moveTab, - closeTab: closeTab, - activateTab: activateTab, - tabTracking: tabTracking, - activateWindow: activateWindow, - getWindowTab: getWindowTab, - getCurrentTab: getCurrentTab, - getCurrentWindow: getCurrentWindow, - getAllWindows: getAllWindows, - restoreSession: restoreSession, - replaceSession: replaceSession, - restoreAdvanced: restoreAdvanced, - replaceSessionAll: replaceSessionAll, - fixLazySession: fixLazySession, - restoreNewWindow: restoreNewWindow, - loadBookmarksNewWindow: loadBookmarksNewWindow, - mergeSessions: mergeSessions, - overwriteSession: overwriteSession, - saveActiveSession: saveActiveSession, - createSeparator: createSeparator, - createNewSession: createNewSession, - saveHistorySession: saveHistorySession, - }; - - })(); - - - // ***************************************************************************** - // Public API - exports.SessionManager = SessionManager; -}); \ No newline at end of file diff --git a/data/scripts/template.js b/data/scripts/template.js deleted file mode 100644 index 39d5c90..0000000 --- a/data/scripts/template.js +++ /dev/null @@ -1,26 +0,0 @@ -define(function(require, exports, module) { - 'use strict'; - - // ************************************************************************ - // Modules - - var ModuleName = require('./path/module-name'); - - // ************************************************************************ - // API - - function init() { - - } - - // ************************************************************************ - // Events - - // ************************************************************************ - // Init - - // ************************************************************************ - // Module exports - - exports.init = init; -}); diff --git a/data/scripts/utils/auto-scroll.js b/data/scripts/utils/auto-scroll.js deleted file mode 100644 index 6687d97..0000000 --- a/data/scripts/utils/auto-scroll.js +++ /dev/null @@ -1,94 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // AutoScroll - - var AutoScroll = (function AutoScroll() { - - var autoScroll = false; - var prevScroll = 0; - var scrollSpeed = 0; - var scrollingNode; - var scrollTarget; - var scrollDir = 0; - - function scrollingAnimation() - { - if (autoScroll == false) - { - return; - } - - prevScroll = scrollingNode.scrollTop; - scrollingNode.scrollTop += scrollSpeed; - - if (scrollingNode.scrollTop != prevScroll) - { - if (scrollTarget != undefined) { - if (scrollDir * scrollingNode.scrollTop > scrollTarget) { - scrollingNode.scrollTop = scrollDir * scrollTarget; - autoScroll = false; - return; - } - } - requestAnimationFrame(scrollingAnimation); - } - else - { - autoScroll = false; - } - } - - // scroll a DOM node with a specified speed - function scroll(node, speed) - { - scrollSpeed = speed / 10; - - if (autoScroll == true) - { - return; - } - - scrollTarget = undefined; - autoScroll = true; - scrollingNode = node; - requestAnimationFrame(scrollingAnimation); - } - - function stop() - { - autoScroll = false; - } - - function scrollTo(node, offset, time) - { - if (time == 0) { - node.scrollTop = offset; - return; - } - - if (autoScroll == true) - { - return; - } - - autoScroll = true; - scrollingNode = node; - scrollSpeed = (offset - node.scrollTop) / (time * 60); - scrollDir = scrollSpeed > 0 ? 1 : -1; - scrollTarget = offset * scrollDir; - requestAnimationFrame(scrollingAnimation); - } - - return { - scroll : scroll, - scrollTo: scrollTo, - stop : stop - }; - })(); - - // ***************************************************************************** - // Public API - exports.AutoScroll = AutoScroll; -}); \ No newline at end of file diff --git a/data/scripts/utils/components.js b/data/scripts/utils/components.js deleted file mode 100644 index ede7ee7..0000000 --- a/data/scripts/utils/components.js +++ /dev/null @@ -1,369 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { HTMLCreator } = require('./dom'); - const JSUtils = require('./general'); - - // ***************************************************************************** - // API - - /* - * Toggle switch - */ - - function ToggleSwitch(options) - { - var DomElem = HTMLCreator(); - - var state = options.state; - - var entry = DomElem('div', {class: 'comp-toggle-switch'}); - var title = DomElem('div', {class: 'label'}); - - if (options.attribute) { - entry.setAttribute(options.attribute, ''); - } - - if (options.description) { - title.textContent = options.description; - } - - var button = DomElem('div', {class: 'switch'}); - button.setAttribute('state', state); - button.textContent = state ? options.onState : options.offState; - - if (options.tooltip) { - entry.setAttribute('tooltip', options.tooltip); - title.setAttribute('tooltip', options.tooltip); - button.setAttribute('tooltip', options.tooltip); - } - - entry.appendChild(title); - entry.appendChild(button); - - function setState(newState) - { - if (state == newState) - return; - - state = newState; - options.callback(state); - button.setAttribute('state', state); - button.textContent = state ? options.onState : options.offState; - } - - entry.addEventListener('click', function() { - setState(!state); - }); - - this.setState = setState; - this.DOMRoot = entry; - } - - function ToggleButton(options) - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var state = options.state; - var callback = JSUtils.getValidFunction(options.callback); - - var container = DomElem('div', {class: 'comp-toggle-button'}); - var info = DomElem('div', {class: 'label'}); - info.textContent = options.description; - - var button = DomElem('div', {class: 'button'}); - button.setAttribute('state', state); - button.textContent = state ? options.onState : options.offState; - - container.appendChild(info); - container.appendChild(button); - - // ------------------------------------------------------------------------ - // Events - - var toggle = function toggle() { - setValue(!state, true); - }; - - var setValue = function setValue(value, trigger) { - if (value != state) { - state = value; - button.setAttribute('state', state); - button.textContent = state ? options.onState : options.offState; - if (trigger) { - callback(state); - } - } - }; - - button.addEventListener('click', toggle); - - // ------------------------------------------------------------------------ - // Public properties - - this.toggle = toggle; - this.setValue = setValue; - this.DOMRoot = container; - } - - /* - * Action Button - */ - - function ActionButton(options) - { - var DomElem = HTMLCreator(); - - var entry = DomElem('div', {class: 'comp-button'}); - var title = DomElem('div', {class: 'label'}); - - if (options.description) { - title.textContent = options.description; - } - - var button = DomElem('div', {class: 'button'}); - button.textContent = options.value; - - if (options.tooltip) { - entry.setAttribute('tooltip', options.tooltip); - title.setAttribute('tooltip', options.tooltip); - button.setAttribute('tooltip', options.tooltip); - } - - entry.appendChild(title); - entry.appendChild(button); - - function setValue(value) - { - button.textContent = value; - } - - function getValue() - { - return button.textContent; - } - - button.addEventListener('click', function() { - options.callback(); - }); - - this.setValue = setValue; - this.getValue = getValue; - this.DOMRoot = entry; - } - - /* - * RageControl - */ - - function RangeControl(options) - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var value = options.value; - var step = options.step ? options.step : 1; - var callback = JSUtils.getValidFunction(options.onChange); - - var root = DomElem('div', {class: 'comp-range-control'}); - - var sectionInfo = DomElem('div', {class: 'label'}); - sectionInfo.textContent = options.description; - - var container = DomElem('div', {class: 'range-input'}); - var incButton = DomElem('div', {class: 'button inc'}); - var decButton = DomElem('div', {class: 'button dec'}); - var valueBox = DomElem('input', {type: 'text', class: 'value'}); - valueBox.value = value; - - root.appendChild(sectionInfo); - root.appendChild(container); - container.appendChild(decButton); - container.appendChild(valueBox); - container.appendChild(incButton); - - var setValue = function setValue(newValue, trigger) - { - // revent update trigger with the same value - if (value == newValue) { - return; - } - - // restrict to specified range - if (options.minValue > newValue) { newValue = options.minValue; } - if (options.maxValue < newValue) { newValue = options.maxValue; } - - if (newValue % step != 0) { - newValue -= newValue % step; - } - - // update value - var diff = newValue - value; - value = newValue; - valueBox.value = newValue; - - if (trigger) { - callback(newValue, diff); - } - }; - - var onChange = function onChange() - { - var newValue = valueBox.value | 0; - if (parseInt(valueBox.value) != newValue) { - valueBox.value = value; - return; - } - - setValue(newValue, true); - }; - - valueBox.addEventListener('change', function() { - onChange(true); - }); - - incButton.addEventListener('click', function() { - setValue(value + step, true); - }); - - decButton.addEventListener('click', function() { - setValue(value - step, true); - }); - - // ------------------------------------------------------------------------ - // Public properties - - this.DOMRoot = root; - this.setValue = setValue; - } - - function DropDown(options) - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - var container = DomElem('div', {class: 'comp-dropdown'}); - if (options.id) { - container.setAttribute('id', options.id); - } - - var info = DomElem('div', {class: 'label'}); - info.textContent = options.description; - - var select = DomElem('div', {class: 'select'}); - var selectValue = DomElem('div', {class: 'select-value'}); - selectValue.textContent = options.value; - - var dropList = DomElem('div', {class: 'drop-list', visible: 'false'}); - - container.appendChild(info); - container.appendChild(select); - select.appendChild(selectValue); - select.appendChild(dropList); - - function addOption(label, options) { - if (options == undefined) - options = {}; - - var option = DomElem('div', {class: 'option'}); - option.textContent = label; - option.setAttribute('value', (options.value != undefined) ? options.value : label); - - if (options.property) { - option.setAttribute(options.property, ''); - } - - if (options.selected === true) { - setSelected(option); - } - - dropList.appendChild(option); - } - - // ------------------------------------------------------------------------ - // Events - - var selectedOption; - var visible = false; - var callback = JSUtils.getValidFunction(options.onChange); - - var setValue = function(value, trigger) { - for (var i = 0; i < dropList.children.length; i++) { - if (dropList.children[i].getAttribute('value') == value) { - setSelected(dropList.children[i], trigger); - return; - } - } - }; - - var getValue = function() { - return selectedOption.getAttribute('value'); - }; - - var setSelected = function setSelected(node, trigger) { - if (selectedOption) - selectedOption.removeAttribute('selected'); - - selectedOption = node; - - if (selectedOption) { - var value = selectedOption.getAttribute('value'); - selectValue.textContent = selectedOption.textContent; - container.setAttribute('value', value); - selectedOption.setAttribute('selected', ''); - if (trigger) { - callback(value); - } - } - }; - - var closeDropdown = function closeDropdown(e) { - e.stopPropagation(); - visible = false; - dropList.setAttribute('visible', visible); - document.removeEventListener('click', closeDropdown); - }; - - select.addEventListener('click', function(e) { - e.stopPropagation(); - visible = !visible; - dropList.setAttribute('visible', visible); - document.addEventListener('click', closeDropdown); - }); - - dropList.addEventListener('click', function(e) { - if (e.target.className == 'option') { - setSelected(e.target, true); - closeDropdown(e); - } - }); - - // ------------------------------------------------------------------------ - // Public properties - - this.container = container; - this.addOption = addOption; - this.setValue = setValue; - this.getValue = getValue; - } - - // ***************************************************************************** - // Public API - - exports.DropDown = DropDown; - exports.ActionButton = ActionButton; - exports.ToggleSwitch = ToggleSwitch; - exports.RangeControl = RangeControl; - exports.ToggleButton = ToggleButton; - -}); \ No newline at end of file diff --git a/data/scripts/utils/confirm-box.js b/data/scripts/utils/confirm-box.js deleted file mode 100644 index 2837122..0000000 --- a/data/scripts/utils/confirm-box.js +++ /dev/null @@ -1,101 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { HTMLCreator } = require('./dom'); - const { WindowEvents } = require('./global-events'); - - // ***************************************************************************** - // Bookmark Confirm Box - - function ConfirmBox(options) - { - var DomElem = HTMLCreator(); - - var panel = DomElem('div', {class : 'confirm-box'}); - - var info = DomElem('div', {class : 'info'}); - var controls = DomElem('div', {class : 'controls'}); - - var ok = DomElem('div', {class : 'button ok'}); - ok.textContent = 'Confirm'; - - var cancel = DomElem('div', {class : 'button cancel'}); - cancel.textContent = 'Cancel'; - - var flexArea1 = DomElem('div', {class : 'flex'}); - var flexArea2 = DomElem('div', {class : 'flex'}); - - controls.appendChild(flexArea1); - controls.appendChild(ok); - controls.appendChild(cancel); - controls.appendChild(flexArea2); - panel.appendChild(info); - panel.appendChild(controls); - - var callback; - var defaultCallback = function defaultCallback() {}; - - // ------------------------------------------------------------------------ - // Events - - ok.addEventListener('click', function() { - callback(); - hidePanel(); - }); - - cancel.addEventListener('click', function() { - hidePanel(); - }); - - var openPanel = function openPanel(options) - { - panel.setAttribute('data-active', 'true'); - panel.style.left = options.event.pageX + 'px'; - panel.style.top = options.event.pageY + 'px'; - - document.addEventListener('mousedown', blurMenu); - - info.textContent = options.message; - setCallback(options.callback); - - }; - - var hidePanel = function hidePanel() - { - panel.removeAttribute('data-active'); - document.removeEventListener('mousedown', blurMenu); - callback = defaultCallback; - }; - - var blurMenu = function hideWhenClickOutside(e) - { - var target = e.target; - while (target) { - if (target === panel) - return; - target = target.parentElement; - } - hidePanel(); - }; - - function setCallback(func) { - callback = typeof func === 'function' ? func : defaultCallback; - } - - // Register custom events - WindowEvents.on(document, options.name + '-Open', openPanel); - WindowEvents.on(document, 'ConfimBox-Hide', hidePanel); - - // ------------------------------------------------------------------------ - // Public properties - - this.DOMRoot = panel; - } - - // ***************************************************************************** - // Public API - exports.ConfirmBox = ConfirmBox; -}); \ No newline at end of file diff --git a/data/scripts/utils/context-menu.js b/data/scripts/utils/context-menu.js deleted file mode 100644 index 9dde989..0000000 --- a/data/scripts/utils/context-menu.js +++ /dev/null @@ -1,142 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - // App - const { AppConfig } = require('../config'); - - const { HTMLCreator } = require('./dom'); - - // TODO - remove window/global events - const { WindowEvents, GlobalEvents } = require('./global-events'); - - // var ID = 0; - - // ***************************************************************************** - // Bookmark Context Menu - - var previousContextMenu = null; - - function ContextMenu(options) - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - // var self = this; - var menu = DomElem('div', { class : 'context-menu' }); - if (options.width != undefined) { - menu.style.width = options.width + 'px'; - } - - var context = undefined; - - // ------------------------------------------------------------------------ - // Events - - var openMenu = function openMenu(event) - { - if (previousContextMenu && previousContextMenu != this) { - previousContextMenu.hideMenu(); - } - previousContextMenu = this; - - menu.setAttribute('data-active', 'true'); - var padding = 10; - menu.style.left = Math.min(event.pageX, window.innerWidth - menu.clientWidth - padding) + 'px'; - menu.style.top = Math.min(event.pageY, window.innerHeight - menu.clientHeight - padding) + 'px'; - - document.addEventListener('mousedown', blurMenu); - - WindowEvents.emit(document, 'ConfimBox-Hide'); - }.bind(this); - - var hideMenu = function hideMenu(e) - { - menu.removeAttribute('data-active'); - document.removeEventListener('mousedown', blurMenu); - WindowEvents.emit(document, 'OverlaySystem', { state: false }); - }; - - var blurMenu = function hideWhenClickOutside(e) { - var target = e.target; - while (target) { - if (target === menu) - return; - target = target.parentElement; - } - hideMenu(); - }; - - // Actions - // TODO - investigate switch to event delegation - var NewAction = function NewAction(opt) { - if (opt.func) { - return function actionEvent(e) { - context[opt.func](e); - hideMenu(); - }; - } - if (opt.callback) { - return function actionEvent(e) { - opt.callback(context, e); - hideMenu(); - }; - } - if (opt.globalEvent) { - return function action() { - GlobalEvents.emit(opt.globalEvent, context); - hideMenu(); - }; - } - return function action() { - WindowEvents.emit(document, options.name + '-' + opt.event, context); - hideMenu(); - }; - }; - - var addMenuEntry = function AddMenuEntry(opt) - { - var event = NewAction(opt); - var entry = DomElem('div', {class : 'button'}); - entry.textContent = opt.value; - entry.setAttribute('icon', opt.icon); - menu.appendChild(entry); - - var addSeparator = opt.separator; - if (addSeparator != undefined) - { - var separator = DomElem('div', {class : 'separator'}); - menu.insertBefore(separator, addSeparator == 'top' ? entry : undefined); - } - - entry.addEventListener('click', event); - }; - - // Register custom events - WindowEvents.on(document, options.name + '-Open', function(options) { - // menu.setAttribute('icons', options.showIcons ? 'yes' : 'no'); - openMenu(options.event); - context = options.context; - WindowEvents.emit(document, 'OverlaySystem', { state: true }); - }); - - AppConfig.onChange('context.menu.icons', function(value) { - menu.setAttribute('icons', value ? 'yes' : 'no'); - }); - - // ------------------------------------------------------------------------ - // Public properties - - this.DOMRoot = menu; - this.hideMenu = hideMenu; - this.addMenuEntry = addMenuEntry; - } - - // ***************************************************************************** - // Public API - exports.ContextMenu = ContextMenu; -}); \ No newline at end of file diff --git a/data/scripts/utils/dom.js b/data/scripts/utils/dom.js deleted file mode 100644 index 2cdd432..0000000 --- a/data/scripts/utils/dom.js +++ /dev/null @@ -1,30 +0,0 @@ -define(function(require, exports, module) { - 'use strict'; - - const NS_HTML = 'http://www.w3.org/1999/xhtml'; - - // ------------------------------------------------------------------------ - // Generic Node Creator - - function Node(ns, type, attrs) { - var elem = document.createElementNS(ns, type); - - attrs && Object.keys(attrs).forEach(function(attr) { - elem.setAttribute(attr, attrs[attr]); - }); - - return elem; - } - - function HTMLCreator() { - return Node.bind(null, NS_HTML); - } - - // ------------------------------------------------------------------------ - // Public API - - exports.HTMLCreator = HTMLCreator; -}); - -// ES6 module -// export { HTMLCreator , XULCreator }; diff --git a/data/scripts/utils/drag-context.js b/data/scripts/utils/drag-context.js deleted file mode 100644 index 85c8c1b..0000000 --- a/data/scripts/utils/drag-context.js +++ /dev/null @@ -1,46 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - - const { WindowEvents } = require('./global-events'); - - // ***************************************************************************** - // API - - var DragContext = (function DragContext() { - - var context = null; - - function setContext(ctx) { - context = ctx; - WindowEvents.emit(document, 'TrashCan-Droppable', ctx != null); - } - - function getContext() { - return context; - } - - function hasContext() { - return context != null; - } - - function clearContext() { - setContext(null); - } - - return { - hasContext: hasContext, - clearContext: clearContext, - setContext: setContext, - getContext: getContext - }; - })(); - - - // ***************************************************************************** - // Public API - exports.DragContext = DragContext; - -}); \ No newline at end of file diff --git a/data/scripts/utils/field-edit-widget.js b/data/scripts/utils/field-edit-widget.js deleted file mode 100644 index 186baaa..0000000 --- a/data/scripts/utils/field-edit-widget.js +++ /dev/null @@ -1,154 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ***************************************************************************** - // Custom Modules - const { HTMLCreator } = require('./dom'); - const { WindowEvents, GlobalEvents } = require('./global-events'); - - // ***************************************************************************** - // Global Variables - - var counterID = 0; - - function FieldEditWdiget(options) - { - var DomElem = HTMLCreator(); - - // ------------------------------------------------------------------------ - // Create UI - - // Context will be the session folderID or bookmarkID - var context = undefined; - // Context data represents fields that need to be edited - var context_data = null; - - var widgetID = counterID++; - var fields = {}; - var first_field = null; - - // Edit session widget - var widget = DomElem('div', {class: 'field-edit-widget'}); - var wfields = DomElem('div', {class: 'fields'}); - var save = DomElem('div', {class: 'button save'}); - var cancel = DomElem('div', {class: 'button cancel'}); - - save.textContent = 'Save'; - cancel.textContent = 'Cancel'; - - widget.appendChild(wfields); - widget.appendChild(cancel); - widget.appendChild(save); - - // ------------------------------------------------------------------------ - // Methods - - function addField(options) - { - var group = DomElem('div', {class: 'group'}); - - var input = DomElem('input'); - - var label = DomElem('label'); - label.textContent = options.label; - - group.appendChild(label); - group.appendChild(input); - wfields.appendChild(group); - - fields[options.name] = input; - - if (first_field == null) { - first_field = input; - } - } - - function keyBoardShortcut(e) - { - if (e.keyCode == 13) { - saveValues(); - } - } - - function hideWidget() - { - first_field.blur(); - widget.removeAttribute('data-active'); - document.removeEventListener('keydown', keyBoardShortcut); - } - - var hideIfNotInvoked = function hideIfNotInvoked(ID) - { - if (widgetID !== ID) hideWidget(); - }; - - function invokeWidget(data) - { - context_data = data.fields; - context = data.context; - - for (var key in data.fields) { - if (data.fields[key]) { - fields[key].value = data.fields[key]; - } - else { - if (fields.hasOwnProperty(key)) { - fields[key].value = ''; - data.fields[key] = ''; - } - } - } - - widget.setAttribute('data-active', ''); - document.addEventListener('keydown', keyBoardShortcut); - WindowEvents.emit(document, 'FieldWidgetInvoked', widgetID); - first_field.focus(); - first_field.select(); - } - - function saveValues() - { - var obj = {}; - var prop_save = 0; - for (var key in fields) - { - if (context_data[key] != fields[key].value) - { - prop_save++; - obj[key] = fields[key].value; - } - } - - if (prop_save) - { - WindowEvents.emit(document, options.name + '-Save', {context: context, fields : obj}); - } - - hideWidget(); - } - - function destroy() - { - GlobalEvents.off('BookmarkRemoved', hideWidget); - } - - // ------------------------------------------------------------------------ - // Create UI - - cancel.addEventListener('click', hideWidget); - save.addEventListener('click', saveValues); - - GlobalEvents.on('BookmarkRemoved', hideWidget); - WindowEvents.on(document, options.name + '-Invoke', invokeWidget); - WindowEvents.on(document, 'FieldWidgetInvoked', hideIfNotInvoked); - - // ------------------------------------------------------------------------ - // Public properties - - this.DOMRoot = widget; - this.addField = addField; - } - - // Public API - exports.FieldEditWdiget = FieldEditWdiget; -}); \ No newline at end of file diff --git a/data/scripts/utils/general.js b/data/scripts/utils/general.js deleted file mode 100644 index 87b284d..0000000 --- a/data/scripts/utils/general.js +++ /dev/null @@ -1,53 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - function copyProperties(source, destination) { - for (var prop in source) { - if (source.hasOwnProperty(prop)) { - destination[prop] = source[prop]; - } - } - } - - // Test if object is a valid javascript function - // Returns the same object if true or a new function otherwise - function getValidFunction(object) - { - var value = (typeof object === 'function' ? object : function () {}); - return value; - } - - function LimitCounter(limit, callback) - { - var func = getValidFunction(callback); - var counted = 0; - var triggered = false; - - function checkLimit() { - if (!triggered && counted >= limit) - { - triggered = true; - func(); - } - } - - this.advance = function advance() { - counted++; - checkLimit(); - }.bind(this); - - this.offsetLimit = function offsetLimit(offset) { - limit += offset; - if (limit > 0) { - checkLimit(); - } - }; - } - - // ------------------------------------------------------------------------ - // Public API - - exports.LimitCounter = LimitCounter; - exports.copyProperties = copyProperties; - exports.getValidFunction = getValidFunction; -}); \ No newline at end of file diff --git a/data/scripts/utils/global-events.js b/data/scripts/utils/global-events.js deleted file mode 100644 index 6b4668f..0000000 --- a/data/scripts/utils/global-events.js +++ /dev/null @@ -1,100 +0,0 @@ -define(function(require, exports) { - 'use strict'; - - // ------------------------------------------------------------------------ - // Modules - - const { PubSub } = require('../3rd/pubsub'); - - // ------------------------------------------------------------------------ - // API - - var GlobalEventSystem = (function GlobalEventSystem() { - - var globalEvents = new PubSub(); - - return { - on : globalEvents.subscribe, - off : globalEvents.unsubscribe, - emit: globalEvents.publish, - }; - })(); - - // Handles collection of PubSub events separated by diffrent namespaces - var NamespaceEvents = (function NamespaceEvents() { - - var map = new Map(); - - //------------------------------------------------------------------------- - // Public API - - var broadcastEvent = function broadcastEvent(topic, object) { - map.forEach(function(collection) { - collection.publish(topic, object); - }); - }; - - var emitEvent = function emitEvent(namespace, topic, object) { - var collection = map.get(namespace); - if (collection instanceof PubSub) { - collection.publish(topic, object); - } - }; - - var subscribe = function subscribe(namespace, topic, callback) { - var collection = map.get(namespace); - if (collection instanceof PubSub === false) { - collection = new PubSub(); - map.set(namespace, collection); - } - collection.subscribe(topic, callback); - }; - - var unsubscribe = function unsubscribe(namespace, topic, callback) { - var collection = map.get(namespace); - if (collection instanceof PubSub) { - collection.unsubscribe(topic, callback); - } - }; - - var remove = function remove(namespace) { - var collection = map.get(namespace); - if (collection instanceof PubSub) { - collection.messages = {}; - map.delete(namespace); - } - }; - - return { - on : subscribe, - off: unsubscribe, - emit : emitEvent, - broadcast : broadcastEvent, - remove: remove - }; - }); - - // WindowEvents based on window.document topic - - var WindowEvents = new NamespaceEvents(); - - WindowEvents.onLocal = function onLocal(topic, callback) { - this.on(document, topic, callback); - }; - - WindowEvents.emitLocal = function emitLocal(topic, object) { - this.emit(document, topic, object); - }; - - // ------------------------------------------------------------------------ - // Events - - // ------------------------------------------------------------------------ - // Init - - // ------------------------------------------------------------------------ - // Module exports - - exports.GlobalEvents = GlobalEventSystem; - exports.WindowEvents = WindowEvents; -}); diff --git a/data/session-sync.css b/data/session-sync.css deleted file mode 100644 index 8da35b1..0000000 --- a/data/session-sync.css +++ /dev/null @@ -1,2752 +0,0 @@ -/** - * Dom Components - */ - -/** - * Action Button - */ - - #session-sync .comp-button { - width: 100%; - color: #333; - display: flex; -} - -#session-sync .comp-button .label { - flex: 1; -} - -#session-sync .comp-button .button { - text-align: center; - background: #38B751; - color: #FFF; - flex: 1; -} - -#session-sync .comp-button .button:hover { - cursor: pointer; -} - -/** - * Toggle Switch - */ - -#session-sync .comp-toggle-switch { - width: 100%; - color: #333; - display: flex; -} - -#session-sync .comp-toggle-switch:first-child { - margin-top: 0; -} - -#session-sync .comp-toggle-switch:hover { - cursor: pointer; - color: #4C91D6; -} - -#session-sync .comp-toggle-switch .label { - float: left; -} - -#session-sync .comp-toggle-switch .switch { - margin-left: 0.4em; - padding: 0 0 0.25em 0; - - background-color: #40B740; - background-size: 1.6em; - background-repeat: no-repeat; - background-position: center right 0.2em; - border-radius: 2em; - - text-align: center; - color: #FFF; - float: left; - - flex: 1; - transition: all 0.3s; - position: relative; - - box-sizing: border-box; -} - -#session-sync .comp-toggle-switch .switch:after { - content: " "; - width: 1.4em; - height: 1.4em; - margin: 0.3em 0 0 0.3em; - background: #444; - border-radius: 100%; - position: absolute; - transition-property: left, background; - transition-duration: 0.25s; - transition-timing-function: ease-out; -} - - -#session-sync .comp-toggle-switch .switch[state="true"] { - background-color: #40B740; -} - -#session-sync .comp-toggle-switch .switch[state="true"]:after { - left: calc(100% - 1.7em); - margin-left: 0; -} - -#session-sync .comp-toggle-switch .switch[state="false"]:after { - background: #E01616; - left: 0; -} - -#session-sync .comp-toggle-switch .switch[state="false"] { - background-color: #999; - background-position: center left 0.2em; -} - -/** - * Toggle Switch - */ - -#session-sync .comp-dropdown { - width: 100%; - height: 2.4em; - color: #555; - display: flex; - text-align: center; -} - -#session-sync .comp-dropdown .label { - width: 50%; - height: 100%; - line-height: 2.4em; - background: #F5F5F5; -} - -#session-sync .comp-dropdown .select { - height: 100%; - line-height: 2.4em; - width: 50%; - background: #38B751; - position: relative; -} - -#session-sync .comp-dropdown .select:hover { - background: #349E47; -} - -#session-sync .comp-dropdown .select-value { - height: 100%; - color: #FFF; - text-align: center; -} - -#session-sync .comp-dropdown .select-value:hover { - background: #349E47; - cursor: pointer; -} - -#session-sync .comp-dropdown .option { - height: 2.4em; -} - -#session-sync .comp-dropdown .option:hover { - cursor: pointer; -} - -#session-sync .comp-dropdown .drop-list { - width: 100%; - min-height: 100%; - background: #FFF; - color: #333; - border: 1px solid #208020; - - display: block; - position: absolute; - z-index: 100; - top: 100%; - left: 0; - - box-sizing: border-box; -} - -#session-sync .comp-dropdown .drop-list[visible="true"] { - display: block; -} - -#session-sync .comp-dropdown .drop-list[visible="false"] { - display: none; -} - -#session-sync .comp-dropdown .drop-list .option { - width: 100%; - transition: all 0.2s; - text-align: center; - float: left; -} - -#session-sync .comp-dropdown .drop-list .option:hover { - transition: none; - background: #EEE; -} - -#session-sync .comp-dropdown .drop-list .option[selected] { - background: #38B751; - background: #66AAEF; - color: #FFF; -} - -/** - * Configuration button - */ - -#session-sync .config-panel { - width: 350px; - padding: 30px 0 0 0; - font-size: 12px; - border-right: 1px solid #CCC; - background: #F5F5F5; - opacity: 0.95; - color: #000; - - overflow-y: auto; - position: absolute; - top: 1px; - bottom: 1px; - right: -355px; - z-index: 2000; - box-shadow: 0 0 10px 0 #CCC; - - transition: all 0.25s ease-out; - opacity: 0.5; -} - -#session-sync[config] .config-panel { - right: 0; - opacity: 1; -} - -#session-sync[show="false"] .config-panel { - right: -355px !important; -} - -/* - * Close button - */ - -#session-sync .config-panel .css-close-button { - width: 2.4em; - height: 2.4em; - top: .5em; - right: .5em; - opacity: 0.85 !important; - transition: all 0.2s ease-out; -} - -#session-sync .config-panel .css-close-button:hover { - cursor: pointer; - opacity: 0.7 !important; -} - -/* - * Logo - */ - -#session-sync .config-panel .logo { - width: 100px; - height: 64px; - margin: 2em auto; - padding: 0; - border: none; - background: url("images/icon64.png") center top no-repeat; -} - -#session-sync .config-panel .logo:hover { - cursor: pointer; -} - -/* - * Sctions - */ - -#session-sync .config-panel .section-container { - height: auto; - padding: 1em 0 0 0; - border-bottom: 1px dashed #CCC; - border: none; - position: relative; -} - -#session-sync .config-panel .section-container .section-title { - margin: 0 0 1em 0; - padding: 0; - font-weight: 600; - text-align: left; - color: #333; - border-bottom: 1px solid #DDD; - border-top: none; - font-size: 14px; -} - -#session-sync .config-panel .section-container .section-title:hover { - cursor: pointer; -} - -#session-sync .config-panel .section-container .section-body { - transition: all 0.2s ease-out; -} - -#session-sync .config-panel .section-container[collapsed] .section-body { - overflow: hidden; - height: 0px !important; -} - -#session-sync .config-panel .section-container .drop-icon { - width: 1.5em; - height: 1.5em; - margin: 0 0.5em; - background-image: url("images/arrow-up.png"); - background-size: contain; - float: left; -} - -#session-sync .config-panel .section-container[collapsed] .drop-icon { - background-image: url("images/arrow-down.png"); - background-size: contain; -} - -/* - * Range control - */ - -#session-sync .config-panel .comp-range-control { - width: 100%; - height: 2em; - margin: 0.5em 0; - padding: 0 1em 0 2em; - line-height: 1.8em; - overflow: hidden; - border: none; - - display: flex; - box-sizing: border-box; -} - -#session-sync .config-panel .comp-range-control .label -{ - height: 100%; - font-size: 1em; - color: #555; - border: none; - word-break: break-all; - float: left; - font-size: 1.1em; - - flex: 1; - box-sizing: border-box; -} - - -#session-sync .config-panel .comp-range-control .range-input { - width: 8em; - margin: 0 1em 0 0; - height: 100%; - box-sizing: border-box; - float: left; - display: flex; -} - -#session-sync .config-panel .comp-range-control .range-input > * { - float: left; -} - -#session-sync .config-panel .comp-range-control .range-input .value { - text-align: center !important; -} - -#session-sync .config-panel .comp-range-control .button { - width: 2em; - height: 100%; - filter: opacity(50%); - transition: 0.2s all; -} - -#session-sync .config-panel .comp-range-control .button:hover { - cursor: pointer; - filter: opacity(100%); -} - -#session-sync .config-panel .comp-range-control .button.inc { - background: url("images/arrow-right.png") center top no-repeat; - background-size: 100%; -} - -#session-sync .config-panel .comp-range-control .button.dec -{ - background: url("images/arrow-left.png") center top no-repeat; - background-size: 100%; -} - -#session-sync .config-panel .comp-range-control .value { - width: 2.8em; - margin: 0 0.2em; - padding: 0; - - background: #FFF; - border: 1px solid #CCC; - - font-size: 1.1em; - text-align: center; - line-height: 1.8em; - color: #2D92EB; -} - -/* - * Toggle control - */ - - #session-sync .config-panel .comp-button, - #session-sync .config-panel .comp-dropdown, - #session-sync .config-panel .comp-toggle-switch, - #session-sync .config-panel .comp-toggle-button { - width: 100%; - height: 2em; - margin: 5px 0; - padding: 0 1em 0 2em; - line-height: 1.8em; - display: flex; - box-sizing: border-box; -} - -#session-sync .config-panel .comp-button .label, -#session-sync .config-panel .comp-dropdown .label, -#session-sync .config-panel .comp-toggle-switch .label, -#session-sync .config-panel .comp-toggle-button .label { - height: 100%; - color: #555; - word-break: break-all; - font-size: 1.1em; - float: left; - - flex: 1; - box-sizing: border-box; -} - -#session-sync .config-panel .comp-toggle-switch .switch, -#session-sync .config-panel .comp-toggle-button .button { - width: 8em; - height: 100%; - margin: 0 1em 0 0; - border-radius: 1em; - line-height: 1.9em; - color: white; - text-align: center; - float: left; -} - -#session-sync .config-panel .comp-toggle-switch .switch { - flex: unset; -} - -#session-sync .config-panel .comp-toggle-button .button:hover { - cursor: pointer; -} - -#session-sync .config-panel .comp-toggle-button .button[state="true"] { - background: #38B751; -} - -#session-sync .config-panel .comp-toggle-button .button[state="false"] { - background: #66AAEF; -} - -#session-sync .config-panel .comp-toggle-button .button[state="disable"] { - background: #66AAEF; -} - -/* - * Button Component - */ - -#session-sync .config-panel .comp-button .button { - width: 8em; - height: 100%; - margin: 0 1em 0 0; - border-radius: 1em; - line-height: 1.9em; - overflow: hidden; - text-overflow: ellipsis; - flex: unset; - padding: 0 0.75em; - box-sizing: border-box; -} - -/* - * Dropdown Component - */ - -#session-sync .config-panel .comp-dropdown .select { - width: 8em; - height: 100%; - margin: 0 1em 0 0; - background: #38B751; - border-radius: 1em; - line-height: 1.9em; - color: white; - text-align: center; - float: left; - - transition: all 0.2s ease-out; -} - -#session-sync .config-panel .comp-dropdown .select:hover { - cursor: pointer; - background: #66AAEF; -} - -/* - * Custom states - */ - -#session-sync[history="false"] .config-panel .section-container[id="history-config"] .comp-range-control { - display: none; -} - -/** - * Confirm Box - */ - -#session-sync .confirm-box { - width: auto; - padding: 15px; - margin: -40px 0 0 0; - background: #FFF; - border: 1px #CCC solid; - - display: none; - position: fixed; - left: 0; - top: 0; - overflow: auto; - - box-shadow: 3px 3px 5px 0 #CCC; - transform: translate(-50%, 0); - z-index: 1; - - /* display: block; - z-index: 1001; - top: 120px; - left: 321px; */ -} - -#session-sync .confirm-box[data-active="true"] { - display: block; - z-index: 1001; -} - -#session-sync .confirm-box .info { - width: 100%; - margin: 0 0 5px 0; - padding: 5px 0; - color: #333; - text-align: center; - font-size: 16px; -} - -#session-sync .confirm-box .controls { - padding: 10px 0 0 0; - border-top: 1px solid #DDD; - overflow: hidden; - display: flex; -} - -#session-sync .confirm-box .controls .flex { - flex: 1; -} - -#session-sync .confirm-box .ok { - margin: 0 10px 0 0; - color: #FFF; - background: #E86A5C; /*#EA5F4D;*/ -} - -#session-sync .confirm-box .ok:hover { - background: #DC462C; -} - -#session-sync .confirm-box .cancel { - color: #FFF; - background: #999; -} - -#session-sync .confirm-box .cancel:hover { - background: #777; -} - -#session-sync .confirm-box .button { - width: 75px; - padding: 5px 0; - text-align: center; - float: left; - - box-sizing: border-box; -} - -#session-sync .confirm-box .button:hover { - cursor: pointer; -} - -/** - * Context Menu - */ - -#session-sync .context-menu { - width: 180px; - padding: 5px; - background: #FFF; - border: 1px #CCC solid; - - display: none; - position: fixed; - left: 0; - top: 0; - - box-shadow: 0px 0px 5px 0 #CCC; - transform: translateX(5px); - z-index: 3000; - - box-sizing: border-box; -} - -#session-sync .context-menu[data-active="true"] { - display: block; -} - -#session-sync .context-menu .separator { - width: 100%; - height: 1px; - margin: 3px 0; - background: #CCC; -} - - -#session-sync .context-menu .button { - width: 100%; - padding: 5px 5px 5px 30px; - - background-image: url("images/icons/heart.png"); - background-size: 20px; - background-repeat: no-repeat; - background-position: 3px 3px; - box-sizing: border-box; -} - -#session-sync .context-menu .button:hover { - background-color: #EEE; - cursor: pointer; -} - -#session-sync .context-menu .button[icon="save"] { - background-image: url("images/icons/heart.png"); -} - -#session-sync .context-menu .button[icon="new-tab"] { - background-image: url("images/icons/ribbon.png"); -} - -#session-sync .context-menu .button[icon="same-tab"] { - background-image: url("images/icons/recycle.png"); -} - -#session-sync .context-menu .button[icon="new-window"] { - background-image: url("images/icons/frames.png"); -} - -#session-sync .context-menu .button[icon="copy"] { - background-image: url("images/icons/clipboard.png"); -} - -#session-sync .context-menu .button[icon="edit"] { - background-image: url("images/icons/document.png"); -} - -#session-sync .context-menu .button[icon="delete"] { - background-image: url("images/icons/denied.png"); -} - -#session-sync .context-menu .button[icon="same-window"] { - background-image: url("images/icons/stack.png"); -} - -#session-sync .context-menu .button[icon="new-session"] { - background-image: url("images/icons/star.png"); -} - -#session-sync .context-menu[icons="no"] .button -{ - padding: 5px; - background-image: unset; -} - - -/** - * Edit Session Section - */ -#session-sync .field-edit-widget { - width: 100%; - padding: 10px; - background: #FFF; - border: 1px solid #CCC; - - opacity: 0; - - position: absolute; - - box-sizing: border-box; - box-shadow: 0px 0px 5px 0px #CCC; - - bottom: 0; - left: 0; - z-index: -1; - transition: 0.2s all ease-out; -} - -#session-sync .field-edit-widget[data-active] { - opacity: 1; - z-index: 1000; -} - -#session-sync .field-edit-widget .group { - width: 100%; - height: 2.4em; - margin: 3px 0; - line-height: 2.0em; - float: left; - display: flex; -} - -#session-sync .field-edit-widget label { - width: 80px; - padding: 0 20px 0 0; - text-decoration: underline; - font-size: 1.2em; - text-align: right; -} - -#session-sync .field-edit-widget input { - height: 100%; - padding: 0 1em 0.2em 1em !important; - color: #333; - box-sizing: border-box; - border: 1px solid #CCC; - border-radius: 1.2em !important; - flex: 1; -} - -#session-sync .field-edit-widget input:focus { - border-color: #2D92EB !important; - box-shadow: 0 0 3px 0 rgba(0, 131, 255, 0.5) inset !important; -} - -#session-sync .field-edit-widget .button { - height: 2em; - padding: 0 1.5em; - line-height: 1.8em; - margin: 3px 0 0 10px; - - font-size: 14px; - text-align: center; - color: #FFF; - border: unset; - border-radius: 1em; - background: #999; - - transition: all 0.2s; - float: right; -} - -#session-sync .field-edit-widget .button:hover { - color: #FFF; - cursor: pointer; -} - -#session-sync .field-edit-widget .button.save:hover { - background: #23A823; -} - -#session-sync .field-edit-widget .button.cancel:hover { - background: #DC462C; -} - -/** - * Filter Menu - */ - -#session-sync .filter-panel { - width: 150px; - height: 100%; - min-height: 10000px; - font-size: 16px; - border-right: 1px solid #DDD; - background: #F5F5F5; - opacity: 0.95; - color: #000; - - position: absolute; - top: 1px; - z-index: 100; - display: none; - - transition: all 0.3s; -} - -#session-sync[show="true"] .filter-panel { - left: 0px; -} - -#session-sync[show="false"] .filter-panel { - left: -151px; -} - -#session-sync .filter-panel:hover { - width: 200px; -} - -#session-sync .filter-panel .logo { - margin: 10px 0; - height: 64px; - width: 100%; - background: url("images/icon64.png") center top no-repeat; -} - -#session-sync .filter-panel .separator { - margin: 5px 0; - border-bottom: 1px solid #DDD; -} - - -#session-sync .filter-panel .session-box { - padding: 5px 0; - color: #555; - text-align: center; -} - -#session-sync .filter-panel .session-box:hover { - background: #DDD; - cursor: pointer; -} - -#session-sync .filter-panel .session-box[active] { - background: #2D92EB; - color: #FFF; -} - -#session-sync .filter-panel .filters { -} - -#session-sync .filter-panel .filters .title { - margin: 10px 0; - padding: 10px 0; - text-align: center; -} - -#session-sync .filter-panel .label { - height: 24px; - padding: 3px 0; - text-align: center; - font-size: 12px; - line-height: 22px; - word-break: break-all; - - display: flex; - position: relative; - transition: background 0.2s; -} - -#session-sync .filter-panel .label[edit-mode] div { - display: none; -} - -#session-sync .filter-panel .label[edit-mode] input { - display: block; -} - - -#session-sync .filter-panel .label:hover { - cursor: pointer; - background: #FFF; - transition: none; -} - -#session-sync .filter-panel .name { - width: 80%; - padding: 0 10px; - text-align: right; - overflow: hidden; - - flex: 1; -} - -#session-sync .filter-panel .count { - width: 30px; - height: 12px; - margin: 3px 10px 3px 0; - padding: 3px 0; - background: #DDD; - border-radius: 3px; - text-align: center; - line-height: 100%; - color: #333; -} - -#session-sync .filter-panel .label input { - margin: 0 10px; - color: #333; - display: none; -} - -#session-sync .filter-panel .label[active] { - background: #FFF; -} - -#session-sync .filter-panel .label[active] .name { - color: #4C91D6; - font-weight: bold; -} - -#session-sync .filter-panel .label[active] .count { - background: #23A823; - color: #FFF; -} - -#session-sync .filter-panel .label[active]:after { - content: " "; - width: 3px; - height: 100%; - position: absolute; - right: 0; - top: 0; - background: #1AB61A; -} - -#session-sync .filter-panel .add-label { - margin: 0 auto; - font-size: 12px; - padding: 4px 20px; - color: #555; - text-align: center; -} - -#session-sync .filter-panel .add-label:hover { - cursor: pointer; - background: #2D92EB; - color: #FFF; -} - - -/** - * Generic CSS - */ - - -#session-sync .css-close-button { - width: 2em; - height: 2em; - font-size: 12px; - border-radius: 2em; - position: absolute; - top: 0; - z-index: 0; - opacity: 0.75; - box-sizing: border-box; -} - -#session-sync .css-close-button:hover { - cursor: pointer; - opacity: 1; -} - -#session-sync .css-close-button:hover::after, -#session-sync .css-close-button:hover::before { - background: #333; -} - -#session-sync .css-close-button:after, -#session-sync .css-close-button:before { - content: ""; - width: 60%; - height: 16%; - background: #AAA; - position: absolute; - top: 42%; - left: 20%; - transform: rotate(45deg); -} - -#session-sync .css-close-button:after { - transform: rotate(-45deg); -} -/** - * Session Hotkey Manager - */ - - #session-sync .hotkey-overlay { - position: absolute; - background: rgba(0, 0, 0, 0.85); - width: 100%; - height: 100%; - z-index: 3000; - display: none; - } - - #session-sync .hotkey-overlay[state="true"] { - display: block; - } - -#session-sync .hotkey-manager { - width: 300px; - padding: 10px; - font-size: 12px; - border: 1px solid #CCC; - background: #FFF; - color: #000; - - position: absolute; - top: 30%; - left: 50%; - z-index: 1; - transform: translate(-50%, -50%); - - transition: all 0.25s ease-out; - box-sizing: content-box; -} - -#session-sync .hotkey-manager .header { - padding: 0.5em 10px; - margin: 0 0 0.5em 0; - font-size: 1.4em; - text-align: center; - background: #F5F5F5; - color: #555; -} - -#session-sync .hotkey-manager .info-box { - text-align: center; -} - -#session-sync .hotkey-manager .info-box .message { - color: #DC462C; - text-align: center; - font-weight: bold; -} - -#session-sync .hotkey-manager .info-box .link { - color: #509AE5; - text-align: center; - font-weight: bold; -} - -#session-sync .hotkey-manager .info-box .link:hover { - cursor: pointer; -} - -#session-sync .hotkey-manager .hotkey-info { - width: 100%; - margin: 10px auto; - border: 1px solid #DDD; - - text-align: center; - color: #555; - overflow: hidden; -} - -#session-sync .hotkey-manager .hotkey-info > div { - float: left; - padding: 0.5em 0; - text-align: center; -} - -#session-sync .hotkey-manager .hotkey-info .title { - width: 30%; - background: #38B751; - background: #999; - color: #FFF; -} - -#session-sync .hotkey-manager .hotkey-info .value { - width: 70%; -} - -#session-sync .hotkey-manager .comp-dropdown { - width: 30%; - float: left; -} - -#session-sync .hotkey-manager .comp-dropdown .select { - width: 100%; -} - -#session-sync .hotkey-manager .comp-dropdown .label { - display: none; -} - -#session-sync .hotkey-manager .plus { - width: 5%; - height: 2.4em; - line-height: 2.4em; - text-align: center; - float: left; -} - -#session-sync #shortcut-key.comp-dropdown .drop-list { - width: 300px; - left: unset; - right: 0; -} - -#session-sync #shortcut-key.comp-dropdown .option { - width: 10%; -} - -#session-sync #shortcut-key.comp-dropdown .option[wide] { - width: 25%; -} - -#session-sync .hotkey-manager .shortcut-config, -#session-sync .hotkey-manager .action-bar { - width: 100%; - height: 2.4em; - margin: 10px 0 0 0; - display: flex; -} - -#session-sync .hotkey-manager .action-bar { - margin: 20px 0 0 0; -} - -#session-sync .hotkey-manager .action-bar .flex { - flex: 1; -} - -#session-sync .hotkey-manager .action-bar .ok { - margin: 0 10px 0 0; - color: #FFF; - background: #E86A5C; /*#EA5F4D;*/ -} - -#session-sync .hotkey-manager .action-bar .ok:hover { - background: #DC462C; -} - -#session-sync .hotkey-manager .action-bar .cancel { - color: #FFF; - background: #999; -} - -#session-sync .hotkey-manager .action-bar .cancel:hover { - background: #777; -} - -#session-sync .hotkey-manager .action-bar .button { - width: 75px; - padding: 5px 0; - text-align: center; - float: left; - - box-sizing: border-box; -} - -#session-sync .hotkey-manager .action-bar .button:hover { - cursor: pointer; -} -/** - * Notification System - */ - -#session-sync .notification-system { - width: 100%; - height: 0px; - font-size: 16px; - - position: absolute; - top: 1px; - left: 50%; - z-index: 3000; - transform: translateX(-50%); -} - -#session-sync .notification-system .info -{ - width: 100%; - padding: 0.5em 2em; - padding-left: 3.5em; - background: #2D92EB; - color: #FFF; - /* background: #F5F5F5; */ - /* color: #333; */ - word-break: break-all; - text-align: center; - - position: absolute; - top: -150px; - - opacity: 0; - transition: all 0.2s ease-out; - box-sizing: border-box; - transform: translateX(0); -} - -#session-sync .notification-system .info[active] -{ - opacity: 1; - top: -1px; - border: 1px solid #CCC; -} - -#session-sync .notification-system .info:after { - content: " "; - width: 24px; - height: 24px; - background: url("images/logo.png") center no-repeat; - background-size: contain; - top: 50%; - left: 20px; - position: absolute; - transform: translateY(-50%); -} -/** - * Control Panel - */ - -#session-sync .search-bar { - width: 5em; - height: 1.5em; - margin: 0; - padding: 0; - - background: hsla(0, 0%, 100%, 0.5); - border-radius: 1.5em; - - overflow: hidden; - opacity: 1; - - line-height: 1em; - - position: absolute; - top: 0.5em; - left: 0.5em; - z-index: 2; - - transition: all 0.2s ease-out; -} - -#session-sync .search-bar[content="false"] { - width: 5em !important; -} - -#session-sync .search-bar[content="false"] .search { - text-align: center; -} - -#session-sync .search-bar[active="true"] { - width: 25%; - opacity: 1; -} - -#session-sync .search-bar:hover { - opacity: 1; -} - -#session-sync .search-bar .search { - width: 100%; - height: 100%; - padding: 0 0.75em !important; - background: hsla(0, 0%, 100%, 0.95); - border: 1px solid #CCC; - border-radius: 1em; - color: #333; - font-size: 0.75em; - - box-sizing: border-box; - transition: all 0.2s ease-out; -} - -#session-sync .search-bar .search:focus { - border-color: #2D92EB !important; -} - -#session-sync .search-bar[match="true"][content="true"] .search { - color: green; -} - -#session-sync .search-bar .create-session { - width: 1em; - height: 1em; - background-image: url("images/icons/star.png"); - background-size: 100%; - background-repeat: no-repeat; - background-position: center; - text-align: center; - color: #FFF; - opacity: 0; - - position: absolute; - top: 0.25em; - right: -2.5em; - z-index: 0; - - filter: saturate(0%); - transition: all 0.2s ease-out; - box-sizing: border-box; -} - -#session-sync .search-bar[match="false"][content="true"] .create-session { - opacity: 0.6; - display: block; - right: 1.6em; -} - -#session-sync .search-bar .create-session:hover { - filter: saturate(100%); - cursor: pointer; - opacity: 1; -} - -#session-sync .search-bar .close { - width: 1.2em; - height: 1.2em; - background: #FFF; - border-radius: 2em; - opacity: 1; - - position: absolute; - top: 0.15em; - right: -2em; - z-index: 0; - - box-sizing: border-box; - transition: opacity 0.2s ease-out; -} - -#session-sync .search-bar[active="true"][content="true"] .close { - opacity: 0.75; - right: 0.3em; -} - -#session-sync .search-bar .close:hover { - cursor: pointer; -} - -#session-sync .search-bar .close:hover::after, -#session-sync .search-bar .close:hover::before { - background: #333; -} - -#session-sync .search-bar .close:after, -#session-sync .search-bar .close:before { - content: ""; - width: 60%; - height: 16%; - background: #AAA; - position: absolute; - top: 42%; - left: 20%; - transform: rotate(45deg); -} - -#session-sync .search-bar .close:after { - transform: rotate(-45deg); -} - -/** - * Display info about a Session - */ - -#session-sync .session-container { - height: 100%; - background: #FEFEFE; - - font-size: 14px; - float: right; - overflow: hidden; - - /*CSS3*/ - box-sizing: border-box; - position: relative; - - display: flex; - flex-direction: column; - flex: 1; -} - -/* - * Save button - not used for the moment - */ - -#session-sync[session] .session-container .save-session { - display: none; -} - -#session-sync .session-container .save-session { - height: 3.4em; - width: 3.4em; - - background: url("images/icons/heart.png") no-repeat; - background-size: 2em; - background-position: 1em 0.4em; - background-color: #F5F5F5; - - border-radius: 0 0 0 100%; - border-left: 1px solid #CCC; - border-bottom: 1px solid #CCC; - - position: absolute; - z-index: 1; - top: 0; - right: -1px; - - filter: saturate(75%); -} - -#session-sync .session-container .save-session:hover { - cursor: pointer; - background-color: #FFF; - filter: saturate(100%); -} - -/* - * Style Tabs - */ - -#session-sync .session-container .bookmarks { - width: calc(100% + 10px); - margin: 2px 0 2px 2px; - - overflow-x: hidden; - overflow-y: scroll; - position: relative; - - /*CSS3*/ - box-sizing: border-box; - flex: 1; -} - -#session-sync .session-container .bookmarks:hover { - overflow-y: scroll; -} - -#session-sync .session-container .window-separator { - width: 100%; - height: 2em; - background: #F8F8F8; - color: #555; - - line-height: 1.9em; - position: absolute; - transition: all 0.2s; -} - -#session-sync .session-container .window-separator .text { - text-align: center; - font-weight: 600; -} - -#session-sync .session-container .window-separator:hover { - cursor: pointer; - text-decoration: underline; - color: #333; -} - -#session-sync .session-container .window-separator:hover:after { - content: "(switch to)"; - position: absolute; - font-size: 0.8em; - top: 0; - margin-left: 40px; - left: 50%; -} - -#session-sync .session-container .bookmark { - width: 100%; - height: 2em; - padding: 0 4px; - - line-height: 1.9em; - word-break: keep-all; - - overflow: hidden; - position: absolute; - left: 0; - top: 0; - - /*CSS3*/ - box-sizing: border-box; - transition-property: left, top, background, color; - transition-duration: 0.2s; - display: flex; - flex: 1; -} - -#session-sync .session-container .bookmark:hover { - background: #EEE; - cursor: pointer; - transition-property: left, top; -} - -#session-sync .session-container .bookmark[highlight] { - transition-duration: 0.5s; - background: #4396E9 !important; - color: #FFF; -} - -#session-sync .session-container .bookmark[dragging] { - width: 400px; - margin: 1px 0 0 1px; - background-color: #FBD4B0; - background: #4396E9 !important; - color: #FFF; - opacity: 0.9; - - transition: none; - position: fixed; - z-index: 1000; -} - -#session-sync .session-container .bookmark[dragging="drop"] { - background: #4396E9 !important; - color: #FFF; - opacity: 0.9; - transition: none; - position: absolute; - - z-index: 1000; -} - -#session-sync .session-container .bookmark[dragging]:hover { - cursor: default; -} - -#session-sync .session-container .bookmark[pinned='true']:before { - content: ""; - width: 1.5em; - height: 100%; - background-image: url("images/icons/pin.png"); - background-size: contain; - background-repeat: no-repeat; - background-position: center center; - color: green; - position: absolute; - top: 0; - right: 0.35em; - z-index: 0; - opacity: 0.85; -} - -#session-sync .session-container .bookmark * { - float: left; -} - -#session-sync .session-container .bookmark .favicon { - width: 1.33em; - height: 100%; - background: url("images/icons/globe.png"); - background-size: contain; - background-repeat: no-repeat; - background-position: center center; -} - -#session-sync .session-container .bookmark .text { - height: 100%; - padding: 0 5px; - word-break: keep-all; - white-space: nowrap; - overflow: hidden; - - box-sizing: border-box; - flex: 1; -} - -/* - * History Style - */ - -#session-sync .session-container .history-window-separator { - width: 100%; - height: 2em; - line-height: 1.9em; - background: #F8F8F8; - color: #555; - position: absolute; - transition: all 0.2s; -} - -#session-sync .session-container .history-window-separator .text { - text-align: center; -} - -#session-sync .session-container .history-window-separator:hover { - cursor: pointer; - text-decoration: underline; - color: #333; -} -/** - * Header Bar - */ - -#session-sync .header-bar { - width: 100%; - height: 2.5em; - - background: #F5F5F5; - border-bottom: 1px solid #DDD; - line-height: 2.4em; - font-size: 18px; - position: relative; - - box-sizing: border-box; -} - -/** - * Active session button - */ - -#session-sync .header-bar .active-session-btn { - width: 2.5em; - background: url("images/icon128.png") no-repeat center; - background-size: 1em; - height: 60%; - opacity: 0.5; - border: 1px solid red; - border: unset; - position: absolute; - top: 0.5em; - left: 0; - transition: all 0.2s ease-out; - box-sizing: border-box; - display: none; -} - -#session-sync[session="current"] .header-bar .active-session-btn, -#session-sync .header-bar .active-session-btn:hover { - background-size: 1.2em; - cursor: pointer; - opacity: 1; -} - -/** - * Active session description - */ - -#session-sync .header-bar .description { - width: 100%; - height: 100%; - - text-align: center; - white-space: nowrap; - overflow: hidden; - color: #555; - - box-sizing: border-box; -} - -/** - * Config button - */ - -#session-sync .header-bar .addon-config { - width: 1.8em; - height: 1.8em; - background-image: url("images/menu.png"); - background-size: 55%; - background-repeat: no-repeat; - background-position: center center; - border: 1px solid rgba(0, 0, 0, 0); - position: absolute; - top: 0.3em; - right: 0.3em; - z-index: 2; - opacity: 0.4; - box-sizing: border-box; - display: none; -} - -#session-sync .header-bar .addon-config:hover { - cursor: pointer; - background-color: #EEE; - border: 1px solid #CCC; - opacity: 1; -} - -/** - * Header Menu - */ - -#session-sync .header-bar .header-menu { - width: 1.8em; - height: 1.8em; - background-image: url("images/menu.png"); - background-size: 55%; - background-repeat: no-repeat; - background-position: center center; - border: 1px solid rgba(0, 0, 0, 0); - position: absolute; - top: 0.3em; - right: 0.3em; - z-index: 2; - opacity: 0.4; - box-sizing: border-box; -} - - -#session-sync .header-bar .header-menu:hover { - cursor: pointer; - background-color: #EEE; - border: 1px solid #CCC; - opacity: 1; -} - -#session-sync .header-bar .header-menu[active="1"] { - opacity: 1; -} - -#session-sync .header-bar .header-menu .menu-area { - height: 0; - margin: 0; - padding: 0; - background: #FFF; - font-size: 0.75em; - border: 1px solid rgba(0, 0, 0, 0); - position: absolute; - top: 100%; - right: -1px; - overflow: hidden; - - opacity: 0; - display: flex; - flex-direction: column; - box-sizing: initial; - - transition-property: width, height, padding, opacity; - transition-duration: 0.2s; - transition-timing-function: ease-out; -} - -#session-sync .header-bar .header-menu[active="1"] .menu-area { - height: auto; - padding: 1em; - border: 1px solid #CCC; - - opacity: 1; -} - -#session-sync .header-bar .header-menu .menu-area:hover { - cursor: default; -} - -#session-sync .header-bar .header-menu .menu-row { - display: flex; -} - -#session-sync .header-bar .header-menu .menu-button { - width: 6em; - height: 3.5em; - margin: 0 0 0 0.6em; - padding: 0.5em 0; - border: 1px solid #FFF; - - box-sizing: content-box; - flex: 1; -} - -#session-sync .header-bar .header-menu .menu-button:first-child { - margin: 0; -} - -#session-sync .header-bar .header-menu .menu-button:hover { - cursor: pointer; - background-color: #F5F5F5; - border-color: #DDD; -} - -#session-sync .header-bar .header-menu .menu-button .icon { - height: 2em; - background-image: url("images/icons/power.png"); - background-size: contain !important; - background-repeat: no-repeat; - background-position: center; -} - -#session-sync .header-bar .header-menu .menu-button .title { - height: 1.4em; - margin-top: 0.4em; - font-size: 0.8em; - line-height: 100%; - text-align: center; - color: #555; -} - -/* - * Sorting method - */ - -#session-sync .sorting-method { - width: 2.5em; - height: 2.5em; - padding: 0; - display: flex; - - background-image: url("images/sort.png"); - background-size: 20px; - background-repeat: no-repeat; - background-position: center center; - - position: absolute; - bottom: 0px; - right: 4px; - opacity: 0.1; - - transition: all 0.1s ease-out; - box-sizing: border-box; -} - -#session-sync[list="history"] .sorting-method { - display: none; -} - -#session-sync .sorting-method:hover { - cursor: pointer; - opacity: 1; -} - -#session-sync .sorting-method[active="true"] { - opacity: 1; -} - -#session-sync .sorting-method[active="true"] .dropdown { - display: block; - background: #FFF; -} - -#session-sync .sorting-method .dropdown { - width: 8em; - margin: 0; - padding: 0; - - color: #333; - line-height: 1.8em; - text-align: left; - display: none; - - position: absolute; - bottom: 0; - left: -8em; - - flex: 1; - box-sizing: border-box; -} - -#session-sync .sorting-method .dropdown .option-list { - width: 100%; - - border: 1px solid #CCC; - border-bottom: none; - background: #FFF; - - position: absolute; - bottom: 0; - left: -1px; - z-index: 1; - box-sizing: content-box; -} - -#session-sync .sorting-method .dropdown .option-list .option { - width: 100%; - padding: 3px 10px; - text-align: left; - box-sizing: border-box; - transition-property: left, top, color, background; - transition-duration: 0.2s; - -} - -#session-sync .sorting-method .dropdown .option-list .option:hover { - background: #EEE; - border-color: #CCC; - cursor: pointer; - transition-property: left, top; -} - -#session-sync .sorting-method .dropdown .option-list .option[active] { - color: #FFF; - background: #2D92EB; -} - - -/** - * Control Panel - */ - -#session-sync .session-toolbar { - width: 100%; - height: 3em; - - background: #FFF; - color: #000; - display: flex; - font-size: 12px; - - position: relative; - opacity: 100; - border-bottom: 1px solid #CCC; - box-shadow: 0px 0px 3px 0px #CCC; - z-index: 1; - - transition: all 0.15s; -} - -#session-sync .session-toolbar .session-selector { - width: 220px; - min-width: 160px; - max-width: 60%; - height: 100%; - margin: 0; - padding: 0; - border-right: 1px solid #DDD; - - line-height: 3em; - word-break: break-all; - text-align: center; - color: #555; - - display: flex; - box-sizing: border-box; -} - -#session-sync .session-toolbar .session-selector .button { - text-align: center; - overflow: hidden; - display: block; - color: #333; - flex: 1; -} - -#session-sync .session-toolbar .session-selector .button:hover { - cursor: pointer; -} - -#session-sync .session-toolbar .session-selector .active-session { - background-image: url("images/icon128.png"); - background-repeat: no-repeat; - background-position: center center; - background-size: 1.8em; - background-color: #FFF; -} - -#session-sync[list="active"] .session-toolbar .session-selector .active-session, -#session-sync[list="sync"] .session-toolbar .session-selector .sync, -#session-sync[list="history"] .session-toolbar .session-selector .history { - display: block; - background-color: #2D92EB; - color: #FFF; -} - -#session-sync[history="false"] .session-toolbar .session-selector .history { - display: none; -} - -/* - * Session info - */ - -#session-sync:not([session="restore"]) .session-toolbar .session-date { - display: none; -} - -#session-sync .session-toolbar .session-date { - padding: 0.9em 1em; - color: #999; - text-align: right; - display: block; - position: absolute; - top: -1px; - right: 0; - z-index: 10; -} - -/** - * Menu Section - */ - -#session-sync .session-toolbar .menu-bar { - height: 100%; - padding: 0.5em; - - box-sizing: border-box; - flex: 1; -} - -#session-sync .session-toolbar .menu-bar .button { - width: 2.6em; - height: 100%; - - background: url("images/icons/none.png") no-repeat center; - background-size: contain !important; - - display: none; - float: left; - - filter: saturate(100%); -} - -#session-sync .session-toolbar .menu-bar .button:hover { - cursor: pointer; - filter: saturate(100%); - box-shadow: none; -} - -#session-sync .session-toolbar .menu-bar .separator { - height: 100%; - width: 1px; - margin: 0 0.5em; - background: #CCC; - float: left; - display: none; -} - -#session-sync .session-toolbar .menu-bar .add { - background: url("images/icons/star.png") no-repeat center; - filter: saturate(100%); -} - -#session-sync .session-toolbar .menu-bar .save { - background: url("images/icons/heart.png") no-repeat center; - filter: saturate(100%); -} - -#session-sync .session-toolbar .menu-bar .merge-sessions { - background: url("images/icons/merge.png") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .replace-session { - background: url("images/icons/recycle.png") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .restore { - background: url("images/icons/stack.png") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .restore-new-win { - background: url("images/icons/frames.png") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .fix-session { - background: url("images/icons/tools.png") no-repeat center; -} - -/* - * Menu state - */ - - #session-sync[session="current"] .session-toolbar .menu-bar .button[state-current="true"] { - display: block; -} - -#session-sync[session="restore"] .session-toolbar .menu-bar .button[state-restore="true"] { - display: block; -} - -#session-sync[session="restore"] .session-toolbar .menu-bar .separator { - display: block; -} - -#session-sync[session="history"] .session-toolbar .menu-bar .button[state-history="true"] { - display: block; -} - -/* - * Save configuration - */ - -#session-sync .session-toolbar .menu-bar .save-config { - height: 100%; - padding: 0 40px 0 0; - display: block; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch { - opacity: 1; - transition: opacity 0.2s ease-out; -} - -#session-sync .session-toolbar .menu-bar:hover .save-config .comp-toggle-switch { - opacity: 1; -} - -#session-sync[session="restore"] .session-toolbar .menu-bar .save-config { - display: none; -} - -#session-sync[session="history"] .session-toolbar .menu-bar .save-config .comp-toggle-switch[windows] { - display: none; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch { - height: 100%; - width: 4em; - margin: 0; - float: right; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .label { - display: none; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .switch { - width: 2.6em; - height: 100%; - background-color: #40B740; - background-image: url("images/icons/gear.png"); - background-size: 1.6em; - background-repeat: no-repeat; - background-position: center right 0.2em; - border-radius: 2em; - display: block; - - box-sizing: border-box; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .switch:after { - display: none; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .switch[state="false"] { - background-color: #BBB; - background-position: center left 0.2em; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch[pin] .switch { - background-image: url("images/icons/pin.png"); -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch[pin-state] .switch { - background-image: url("images/icons/pin2.png"); -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch[windows] .switch { - background-image: url("images/icons/stack.png"); -} - -/** - * Display lsit of all sessions - */ - -#session-sync .sessions { - width: 200px; - min-width: 160px; - max-width: 60%; - height: 100%; - padding: 0px; - - border-width: 0 1px 0 0; - border-style: solid; - border-color: #CCC; - background: hsla(0, 0%, 100%, 0.95); - background: #FEFEFE; - float: left; - - overflow-x: hidden; - position: relative; - - display: flex; - flex-direction: column; - - /*CSS3*/ - box-sizing: border-box; -} - -/* - * List resize - */ - -#session-sync .sessions .resize-handle -{ - content: " "; - width: 4px; - height: 24px; - margin-top: -12px; - position: absolute; - background: #DDD; - top: 50%; - border: unset; - right: 0px; - cursor: w-resize; - z-index: 10; -} - -#session-sync .sessions .resize-handle:hover -{ - background: #1AB61A; -} - -#session-sync .sessions .resize-handle[resizing] -{ - background: #2D92EB !important; -} - -/* - * History list - */ - -#session-sync[list="history"] .sessions .history-list { - flex: 1; -} - -#session-sync .sessions .history-list { - width: calc(100% + 12px); - margin: 0 0 0 0; - - position: relative; - overflow-x: hidden; - overflow-y: scroll; - display: block; - - transition-property: width, margin, flex; - transition-duration: 0.2s; - box-sizing: border-box; - flex: 0; -} - -#session-sync .sessions .history-node { - width: 100%; - height: 2.5em; - margin: 0.2em; - padding: 0 0 0 10px; - - line-height: 2.4em; - word-break: break-all; - - overflow: hidden; - - box-sizing: border-box; - transition-property: left, top, color, background; - transition-duration: 0.2s; -} - -#session-sync .sessions .history-node:hover { - background: #EEE; - cursor: pointer; - transition-property: left, top; -} - -#session-sync .sessions .history-node[active="true"] { - background: #2D92EB; - color: #FFF; -} - -/* - * Session list - */ - -#session-sync[list="history"] .sessions .session-list { - flex: 0; -} - -#session-sync .sessions .session-list { - width: calc(100% + 12px); - margin: 0 0 0 0; - - position: relative; - overflow-x: hidden; - overflow-y: scroll; - - transition-property: width, margin, flex; - transition-duration: 0.2s; - box-sizing: border-box; - flex: 1; -} - -/* - * Style Folder Session - */ - -#session-sync .sessions .folder { - width: 100%; - height: 2.5em; - padding: 0 0 0 10px; - - line-height: 2.4em; - word-break: break-all; - - overflow: hidden; - position: absolute; - left: 0; - top: 0; - - box-sizing: border-box; - transition-property: left, top, color, background; - transition-duration: 0.2s; -} - -#session-sync .sessions .folder:hover { - background: #EEE; - cursor: pointer; - transition-property: left, top; -} - -#session-sync .sessions .folder[dragging] { - width: 200px; - margin: 1px 0 0 1px; - background: #4396E9 !important; - color: #FFF; - opacity: 0.9; - - transition: none; - position: fixed; - z-index: 1000; -} - -#session-sync .sessions .folder[dragging]:hover { - cursor: default; -} - -#session-sync .sessions .folder[dragging="drop"] { - background: #4396E9 !important; - color: #FFF; - opacity: 0.9; - transition: none; - position: absolute; - - z-index: 1000; -} - -#session-sync .sessions .folder[active="true"] { - background: #2D92EB; - color: #FFF; -} - -#session-sync .sessions .folder[filter] { - top: 0 !important; - left: -100% !important; -} - -#session-sync .sessions .folder[type='separator']:after { - content: " "; - width: 100%; - height: 2px; - margin: -1px 0 0 0; - background: #CCC; - position: absolute; - box-sizing: border-box; - top: 50%; - left: 1px; -} - -#session-sync .sessions .folder[active="true"][type='separator']:after { - background: #FFF; -} - -#session-sync .sessions .folder[type='bookmark'] { - text-decoration: underline; -} - - -/** - * Tooltip System - */ - -#session-sync .tooltip-system { - padding: 5px 10px; - margin: -16px 0 0 0; - - color: #555; - background: #FFF; - text-align: center; - line-height: 20px; - border: 1px solid #CCC; - border-radius: 5px; - - display: none; - position: fixed; - top: 1px; - left: 0; - z-index: 2000; - - box-shadow: 0 5px 7px 1px #CCC; - box-sizing: border-box; -} - -#session-sync .tooltip-system:after { - content: " "; - width: 16px; - height: 16px; - margin: -8px 0 0 -9px; - - background: #FFF; - border-style: solid; - border-color: #BBB; - border-width: 0 1px 1px 0; - - position: absolute; - left: 50%; - top: 100%; - - transform: rotate(45deg); - z-index: -100; -} - -#session-sync .tooltip-system[active] { - display: block; -} - -/** - * Trash-can - */ - -#session-sync .trash-can { - width: 8em; - height: 8em; - - background-image: url("images/trash-can.png"); - background-position: bottom 1.5em right 1.5em; - background-repeat: no-repeat; - background-size: 50%; - background-color: #FFF; - - border-top-left-radius: 8em; - - font-size: 12px; - - border: 1px solid #CCC; - border-bottom: none; - border-right: none; - - box-shadow: 0 0 5px 0 #FFF; - - opacity: 0; - - position: absolute; - bottom: 1px; - right: 0; - z-index: 100; - - box-sizing: border-box; - transition: all 0.2s ease-out; -} - -#session-sync[show="true"] .trash-can { - display: block; -} - -#session-sync[show="false"] .trash-can { - display: none; -} - -#session-sync .trash-can[droppable="true"] { - opacity: 1 !important; -} - -#session-sync .trash-can[action="enter"] { - opacity: 1; - cursor: pointer; -} - -#session-sync .trash-can:not([items="0"]) { - opacity: 1; -} - -#session-sync .trash-can:hover { - cursor: pointer; - font-size: 16px; - opacity: 1; -} - -#session-sync .count { - width: 2em; - height: 2em; - - background: #E54828; - text-align: center; - line-height: 1.9em; - color: #FFF; - border-radius: 100%; - opacity: 1; - - position: absolute; - top: 3.7em; - left: 1.75em; - z-index: 1; - box-sizing: border-box; - transition: background 0.5s ease-out; -} - -#session-sync .trash-can[items="0"] .count { - background: #1AB61A; -} - -#session-sync .trash-can[items="0"] .dismiss { - display: none; -} - -#session-sync .trash-can .dismiss { - display: block; - right: 0.5em; -} - -#session-sync .trash-can[dismiss="true"] { - opacity: 0; -} - -#session-sync .trash-can[dismiss="true"] .dismiss { - display: none; -} - -#session-sync .trash-can[dismiss="true"]:hover { - opacity: 1; -} - -/** - * URL Bar - */ - -#session-sync .url-bar { - width: calc(100% - 3em); - height: 1.5em; - margin: 0; - padding: 0; - - /* background: #2D92EB; */ - /* border: 1px solid #FFF; */ - border-radius: 1em; - - overflow: hidden; - opacity: 0; - - color: #FFF; - - position: absolute; - top: 0.5em; - left: 0.5em; - z-index: 1; - - box-sizing: content-box; - transition: all 0.2s ease-out; -} - -#session-sync .url-bar .arrow { - width: 2em; - height: 100%; - display: none; - - background: #FFF; - text-align: right; - color: #777; - float: left; -} - -#session-sync .url-bar .arrow:after { - content: " "; - width: 2em; - height: 2em; - margin: 2px 0; - background: inherit; - - position: absolute; - left: 0; - z-index: -2; - transform: translate(1em, -0.1em) rotate(45deg); -} - -#session-sync .url-bar .url { - height: 100%; - padding-left: 8em; - background: #2D92EB; - font-size: 0.75em; - line-height: 2em; - white-space: nowrap; - text-overflow: ellipsis; -} - -#session-sync[search-box-content="true"] .url { - padding-left: calc(25% + 2.5em); -} - -#session-sync .url-bar[data-active="true"] { - opacity: 1; - bottom: -1px; -} - -#session-sync .url-bar .copy-input { - position: absolute; - top: -5em; -} - -#session-sync .url-bar .copy-feedback { - width: 5em; - height: 100%; - - background: #38B751; - color: #FFF; - text-align: center; - font-size: 0.75em; - line-height: 2em; - - position: absolute; - top: 0; - right: -5em; - - transition: all 0.2s; -} - -#session-sync .url-bar .copy-feedback[active] { - right: 0; -} - -/** - * Session-Sync - */ -#session-sync { - width: 100%; - height: 100%; - min-height: 400px; - margin: 0; - - font-size: 12px; - font-family: "Open Sans", "Segoe UI", Arial, Helvetica, sans-serif; - - color: #000 !important; - overflow: hidden; - - -moz-user-select: none; -} - -#session-sync[panel="false"] { - position: absolute; -} - -#session-sync[panel="false"] .sessions-area .panel-resize-handle { - display: none; -} - -/* - * Only DOM nodes from here - */ - -#session-sync #overlay { - width: 100%; - height: 100%; - - background: rgba(255, 255, 255, 0.01); - - position: absolute; - top: 0; - left: 0; - z-index: 2500; - - display: none; -} - -#session-sync #overlay[state="true"] { - display: block; -} - -/* - * Panel Body - */ - - #session-sync #body { - height: 100%; - } - -#session-sync[config] .body { - background-color: rgba(255, 255, 255, 0.95); -} - -#session-sync .sessions-area { - width: 100%; - height: 100%; - - box-sizing: border-box; - - transition: all 0.2s; - - display: flex; - flex-direction: column; -} - -#session-sync[show="false"] .sessions-area { - display: none; -} - -#session-sync[state="pin"] .sessions-area { - top: 0; - right: 0; - margin: 0; - transform: translate(0); -} - -#session-sync[state="centered"] .sessions-area { - top: calc(50% - 50px); - left: 50%; - transform: translate(-50%, -50%); -} - -#session-sync[config] .sessions-area { - left: 400px; - top: 20%; - transform: translate(0); -} - -#session-sync .sessions-area-body { - width: 100%; - overflow: hidden; - - flex: 1; - display: flex; - flex-direction: row; -} - -/* - * Container resize - */ - -#session-sync[resize] .body { - background: rgba(255, 255, 255, 0.95) !important; - cursor: se-resize; -} - -#session-sync[resize] .sessions-area { - transition: none; -} - -#session-sync .sessions-area .panel-resize-handle -{ - content: " "; - width: 12px; - height: 12px; - margin-top: -12px; - background: url("images/resize.png") center no-repeat !important; - position: absolute; - background: #DDD; - bottom: 0; - left: 0px; - cursor: sw-resize; - z-index: 10; - - transform: rotate(90deg); -} - -#session-sync .sessions-area .panel-resize-handle:hover -{ - background: #1AB61A; -} - -#session-sync .sessions-area .panel-resize-handle[resizing] -{ - background: #2D92EB; -} - -#clipboard { - position: absolute; - top: -100px; - z-index: -100; -} \ No newline at end of file diff --git a/data/session-sync.html b/data/session-sync.html deleted file mode 100644 index 1dc4099..0000000 --- a/data/session-sync.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - Session Sync - - - - - - -
- - - \ No newline at end of file diff --git a/dev/css/context-menu.css b/dev/css/context-menu.css deleted file mode 100644 index 64dc537..0000000 --- a/dev/css/context-menu.css +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Context Menu - */ - -#session-sync .context-menu { - width: 180px; - padding: 5px; - background: #FFF; - border: 1px #CCC solid; - - display: none; - position: fixed; - left: 0; - top: 0; - - box-shadow: 0px 0px 5px 0 #CCC; - transform: translateX(5px); - z-index: 3000; - - box-sizing: border-box; -} - -#session-sync .context-menu[data-active="true"] { - display: block; -} - -#session-sync .context-menu .separator { - width: 100%; - height: 1px; - margin: 3px 0; - background: #CCC; -} - - -#session-sync .context-menu .button { - width: 100%; - padding: 5px 5px 5px 30px; - - background-image: url("images/icons/heart.png"); - background-size: 20px; - background-repeat: no-repeat; - background-position: 3px 3px; - box-sizing: border-box; -} - -#session-sync .context-menu .button:hover { - background-color: #EEE; - cursor: pointer; -} - -#session-sync .context-menu .button[icon="save"] { - background-image: url("images/icons/heart.png"); -} - -#session-sync .context-menu .button[icon="new-tab"] { - background-image: url("images/icons/ribbon.png"); -} - -#session-sync .context-menu .button[icon="same-tab"] { - background-image: url("images/icons/recycle.png"); -} - -#session-sync .context-menu .button[icon="new-window"] { - background-image: url("images/icons/frames.png"); -} - -#session-sync .context-menu .button[icon="replace-current"] { - background-image: url("images/material_icons/repeat_one.svg"); -} - -#session-sync .context-menu .button[icon="replace-all"] { - background-image: url("images/material_icons/repeat.svg"); -} - -#session-sync .context-menu .button[icon="copy"] { - background-image: url("images/icons/clipboard.png"); -} - -#session-sync .context-menu .button[icon="edit"] { - background-image: url("images/icons/document.png"); -} - -#session-sync .context-menu .button[icon="delete"] { - background-image: url("images/icons/denied.png"); -} - -#session-sync .context-menu .button[icon="same-window"] { - background-image: url("images/icons/stack.png"); -} - -#session-sync .context-menu .button[icon="new-session"] { - background-image: url("images/icons/star.png"); -} - -#session-sync .context-menu[icons="no"] .button -{ - padding: 5px; - background-image: unset; -} - diff --git a/dev/css/session-container.css b/dev/css/session-container.css deleted file mode 100644 index 104209d..0000000 --- a/dev/css/session-container.css +++ /dev/null @@ -1,231 +0,0 @@ -/** - * Display info about a Session - */ - -#session-sync .session-container { - height: 100%; - background: #FEFEFE; - - font-size: 14px; - float: right; - overflow: hidden; - - /*CSS3*/ - box-sizing: border-box; - position: relative; - - display: flex; - flex-direction: column; - flex: 1; -} - -/* - * Save button - not used for the moment - */ - -#session-sync[session] .session-container .save-session { - display: none; -} - -#session-sync .session-container .save-session { - height: 3.4em; - width: 3.4em; - - background: url("images/icons/heart.png") no-repeat; - background-size: 2em; - background-position: 1em 0.4em; - background-color: #F5F5F5; - - border-radius: 0 0 0 100%; - border-left: 1px solid #CCC; - border-bottom: 1px solid #CCC; - - position: absolute; - z-index: 1; - top: 0; - right: -1px; - - filter: saturate(75%); -} - -#session-sync .session-container .save-session:hover { - cursor: pointer; - background-color: #FFF; - filter: saturate(100%); -} - -/* - * Style Tabs - */ - -#session-sync .session-container .bookmarks { - width: calc(100% + 10px); - margin: 2px 0 2px 2px; - - overflow-x: hidden; - overflow-y: scroll; - position: relative; - - /*CSS3*/ - box-sizing: border-box; - flex: 1; -} - -#session-sync .session-container .bookmarks:hover { - overflow-y: scroll; -} - -#session-sync .session-container .window-separator { - width: 100%; - height: 2em; - background: #F8F8F8; - color: #555; - - line-height: 1.9em; - position: absolute; - transition: all 0.2s; -} - -#session-sync .session-container .window-separator .text { - text-align: center; - font-weight: 600; -} - -#session-sync .session-container .window-separator:hover { - cursor: pointer; - text-decoration: underline; - color: #333; -} - -#session-sync .session-container .window-separator:hover:after { - content: "(switch to)"; - position: absolute; - font-size: 0.8em; - top: 0; - margin-left: 40px; - left: 50%; -} - -#session-sync .session-container .bookmark { - width: 100%; - height: 2em; - padding: 0 4px; - - line-height: 1.9em; - word-break: keep-all; - - overflow: hidden; - position: absolute; - left: 0; - top: 0; - - /*CSS3*/ - box-sizing: border-box; - transition-property: left, top, background, color; - transition-duration: 0.2s; - display: flex; - flex: 1; -} - -#session-sync .session-container .bookmark:hover { - background: #EEE; - cursor: pointer; - transition-property: left, top; -} - -#session-sync .session-container .bookmark[highlight] { - transition-duration: 0.5s; - background: #4396E9 !important; - color: #FFF; -} - -#session-sync .session-container .bookmark[dragging] { - width: 400px; - margin: 1px 0 0 1px; - background-color: #FBD4B0; - background: #4396E9 !important; - color: #FFF; - opacity: 0.9; - - transition: none; - position: fixed; - z-index: 1000; -} - -#session-sync .session-container .bookmark[dragging="drop"] { - background: #4396E9 !important; - color: #FFF; - opacity: 0.9; - transition: none; - position: absolute; - - z-index: 1000; -} - -#session-sync .session-container .bookmark[dragging]:hover { - cursor: default; -} - -#session-sync .session-container .bookmark[pinned='true']:before { - content: ""; - width: 1.5em; - height: 100%; - background-image: url("images/icons/pin.png"); - background-size: contain; - background-repeat: no-repeat; - background-position: center center; - color: green; - position: absolute; - top: 0; - right: 0.35em; - z-index: 0; - opacity: 0.85; -} - -#session-sync .session-container .bookmark * { - float: left; -} - -#session-sync .session-container .bookmark .favicon { - width: 1.33em; - height: 100%; - background: url("images/icons/globe.png"); - background-size: contain; - background-repeat: no-repeat; - background-position: center center; -} - -#session-sync .session-container .bookmark .text { - height: 100%; - padding: 0 5px; - word-break: keep-all; - white-space: nowrap; - overflow: hidden; - - box-sizing: border-box; - flex: 1; -} - -/* - * History Style - */ - -#session-sync .session-container .history-window-separator { - width: 100%; - height: 2em; - line-height: 1.9em; - background: #F8F8F8; - color: #555; - position: absolute; - transition: all 0.2s; -} - -#session-sync .session-container .history-window-separator .text { - text-align: center; -} - -#session-sync .session-container .history-window-separator:hover { - cursor: pointer; - text-decoration: underline; - color: #333; -} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..b0d9570 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,43 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import autoImports from './.wxt/eslint-auto-imports.mjs'; +import stylistic from '@stylistic/eslint-plugin'; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { + ignores: ['.output/**', '.wxt/**', 'node_modules/**'] + }, + js.configs.recommended, + autoImports, + { + plugins: { + '@stylistic': stylistic + }, + languageOptions: { + globals: { + ...globals.browser, + ...globals.webextensions + }, + ecmaVersion: 'latest', + sourceType: 'module' + }, + rules: { + // Stylistic Rules + '@stylistic/indent': [ + 'error', + 'tab', + { + SwitchCase: 1 + } + ], + '@stylistic/linebreak-style': ['error', 'windows'], + '@stylistic/quotes': ['error', 'single'], + '@stylistic/semi': ['error', 'always'], + + // Core Logic Rules + 'no-console': 'off', + 'no-undef': 'off' + } + } +]; diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 3c3662e..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,124 +0,0 @@ -var gulp = require('gulp'); -var gutil = require('gulp-util'); -var open = require('gulp-open'); -var watch = require('gulp-watch'); -var concat = require('gulp-concat'); -var plumber = require('gulp-plumber'); - -var sys = require('sys'); -// var exec = require('child_process').exec; -var express = require('express'); -var tiny_lr = require('tiny-lr')(); -var serve_index = require('serve-index'); -var connect_livereload = require('connect-livereload'); - -// ---------------------------------------------------------------------------- -// Config - -var EXPRESS_PORT = 8080; -var EXPRESS_ROOT = __dirname; -var LIVERELOAD_PORT = 35729; -var APP_ROOT = '.'; -var lr_files = [ - APP_ROOT + '/**/*.html', - APP_ROOT + '/**/*.js', - APP_ROOT + '/**/*.css', - '!' + APP_ROOT + '/node_modules/**/*.*', - '!' + APP_ROOT + '/bootstrap.js', - '!' + APP_ROOT + '/gulpfile.js', -]; - -function shellCmd(error, stdout, stderr) { sys.puts(stdout); } - -var io; -// function broadcast() -// { -// console.log('Trigger Firefox Addon CSS Update!'); -// io.sockets.emit('update'); -// } - -// ---------------------------------------------------------------------------- -// Gulp tasks - -gulp.task('default', ['server', 'socket-io', 'open', 'watch'], function() {}); - -gulp.task('socket-io', function() { - var app = require('express')(); - var server = require('http').Server(app); - io = require('socket.io')(server); - - server.listen(8888); - - app.use(express.static(__dirname + '/data/socket.io')); - app.get('/', function (req, res) { - res.sendFile(__dirname + '/index.html'); - }); - - io.on('connection', function (socket) { - - console.log('connected', socket.id); - - socket.on('request', function (data) { - socket.emit('update'); - }); - }); -}); - -gulp.task('server', function() { - var app = express(); - - app.use(connect_livereload({ - port: LIVERELOAD_PORT - })); - - // TODO inspect how to list files from directories - app.use(serve_index(__dirname + '/' + APP_ROOT, {'icons': true})); - app.use(express.static(__dirname + '/' + APP_ROOT)); - app.listen(EXPRESS_PORT); -}); - -gulp.task('open', function() { - var options = { - url: 'http://localhost:' + EXPRESS_PORT, - app: 'firefox' - }; - gulp.src(__filename).pipe(open('', options)); -}); - -// ---------------------------------------------------------------------------- -// Concat CSS - -gulp.task('css', function(event) { - var stream = gulp.src('dev/css/**/*.css') - .pipe(plumber()) - .pipe(concat('session-sync.css')) - .pipe(gulp.dest(APP_ROOT + '/data')); - return stream; -}); - -// ---------------------------------------------------------------------------- -// Watch + LiveReload - -gulp.task('watch', ['livereload'], function() { - watch(lr_files, notifyLivereload); - watch('./dev/css/**/*.css', function(events, done) { - console.log('Message', done); - gulp.start('css', function() { - // broadcast(); - }); - }); -}); - -gulp.task('livereload', function() { - tiny_lr.listen(LIVERELOAD_PORT); -}); - -function notifyLivereload(event) { - var fileName = require('path').relative(EXPRESS_ROOT, event.path); - tiny_lr.changed({ - body: { - files: [fileName] - } - }); - gutil.log('[' + event.event + '] :', gutil.colors.cyan(fileName)); -} diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 1c36fbe..0000000 --- a/manifest.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "Session Sync", - "version": "3.1.12", - "author": "Gabriel Ivanica", - "description": "Save sessions as bookmarks and sync them through Firefox Sync (or any other sync engine).\nPowerful session management - organize, edit, change, save, restore, etc\n", - "homepage_url": "https://github.com/ReDEnergy/SessionSync", - "manifest_version": 2, - "default_locale": "en", - "applications": { - "gecko": { - "id": "session-sync@gabrielivanica.com" - } - }, - "icons": { - "16": "data/images/icon16.png", - "32": "data/images/icon32.png", - "48": "data/images/icon48.png", - "64": "data/images/icon64.png", - "96": "data/images/icon96.png", - "128": "data/images/icon128.png" - }, - "background": { - "scripts": [ - "scripts/main.js", - "scripts/favicon-service.js", - "scripts/session-management.js", - "scripts/session-history.js", - "scripts/development.js" - ] - }, - "permissions": [ - "storage", - "bookmarks", - "tabs", - "clipboardWrite", - "unlimitedStorage" - ], - "browser_action": { - "browser_style": true, - "default_icon": { - "16": "data/images/icon16.png", - "32": "data/images/icon32.png", - "64": "data/images/icon64.png" - }, - "default_title": "Session Sync", - "default_popup": "data/session-sync.html" - }, - "commands": { - "_execute_browser_action": { - "suggested_key": { - "default": "Ctrl+Shift+F" - }, - "description": "Toggle Widget" - }, - "session-sync-detach-tab": { - "suggested_key": { - "default": "Ctrl+Shift+V" - }, - "description": "Tab View" - }, - "session-sync-detach-window": { - "suggested_key": { - "default": "Ctrl+Shift+U" - }, - "description": "Window View" - } - } -} \ No newline at end of file diff --git a/pack_addon.bat b/pack_addon.bat deleted file mode 100644 index 44ff8d2..0000000 --- a/pack_addon.bat +++ /dev/null @@ -1,16 +0,0 @@ -set EXEC_DIR=%~dp0 -set SOURCE_DIR=%~dp0 -cd %~dp0 - -:: Date date/time -SET ZIP="%SOURCE_DIR%\bin\7za.exe" -SET ARCHIVE="Session Sync.zip" - -:: Export as archive -DEL %ARCHIVE% -%ZIP% a -tzip -bt -mx1 %ARCHIVE% ^ - .\data ^ - .\scripts ^ - .\_locales ^ - .\manifest.json ^ - .\README.md diff --git a/package.json b/package.json index 4b4336b..6167ecc 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,33 @@ { - "name": "session-sync", - "author": "Gabriel Ivanica", - "url": "https://github.com/ReDEnergy/SessionSync", - "version": "3.1.1", - "description": "Save sessions as bookmarks and sync them through any sync engine.\nManage sessions, edit, save, restore.\nSession-Sync will automatically update sessions across multiple machines", - "license": "MPL-2.0", - "devDependencies": { - "connect-livereload": "^0.5.3", - "express": "^4.13.1", - "gulp": "^3.9.0", - "gulp-callback": "0.0.3", - "gulp-concat": "^2.6.0", - "gulp-open": "^1.0.0", - "gulp-plumber": "^1.0.1", - "gulp-util": "^3.0.6", - "gulp-watch": "^4.3.3", - "serve-index": "^1.7.1", - "socket.io": "^1.4.8", - "tiny-lr": "^0.1.6" - } -} + "name": "session-sync", + "author": "Gabriel Ivanica", + "url": "https://github.com/ReDEnergy/SessionSync", + "version": "3.3.0", + "description": "Save sessions as bookmarks and sync them through any sync engine.\nManage sessions, edit, save, restore.\nSession-Sync will automatically update sessions across multiple machines", + "license": "MPL-2.0", + "scripts": { + "dev": "wxt", + "dev:chrome": "wxt -b chrome --mv3 --host 127.0.0.1", + "dev:firefox": "wxt -b firefox --mv3 --host 127.0.0.1", + "build": "wxt build", + "build:chrome": "wxt build -b chrome --mv3", + "build:firefox": "wxt build -b firefox --mv3", + "clean": "wxt clean", + "zip": "wxt zip", + "zip:chrome": "wxt zip -b chrome --mv3", + "zip:firefox": "wxt zip -b firefox --mv3", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "postinstall": "wxt prepare" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@stylistic/eslint-plugin": "^5.10.0", + "eslint": "^9.39.4", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.5.0", + "prettier": "^3.8.3", + "wxt": "^0.20.20" + } +} \ No newline at end of file diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json new file mode 100644 index 0000000..4a88268 --- /dev/null +++ b/public/_locales/en/messages.json @@ -0,0 +1,14 @@ +{ + "test": { + "message": "Restore in new window" + }, + "session_sync_tutorial": { + "message": "Tutorial" + }, + "session_sync_detach_tab": { + "message": "Tab View" + }, + "session_sync_detach_window": { + "message": "Window View" + } +} diff --git a/data/home/home.css b/public/home/home.css similarity index 79% rename from data/home/home.css rename to public/home/home.css index e946611..4bc69bb 100644 --- a/data/home/home.css +++ b/public/home/home.css @@ -6,14 +6,14 @@ ***********/ body { - margin:0; + margin: 0; - background-color: #EBF1F5; + background-color: #ebf1f5; background-image: url(../images/grain_header.png), url(../images/grain.png); background-repeat: repeat-x, repeat; font-size: 12px; - font-family: "Open Sans", "Segoe UI", Arial, Helvetica, sans-serif; + font-family: 'Open Sans', 'Segoe UI', Arial, Helvetica, sans-serif; cursor: default; } @@ -32,12 +32,11 @@ body { * Identity */ - #identity { width: auto; height: 120px; text-align: center; - font-family: "Open Sans Light", "Open Sans", Arial, Helvetica, sans-serif; + font-family: 'Open Sans Light', 'Open Sans', Arial, Helvetica, sans-serif; position: relative; } @@ -45,7 +44,7 @@ body { width: auto; height: auto; font-size: 60px; - color: #DF5600; + color: #df5600; overflow: visible; text-align: left; @@ -59,7 +58,7 @@ body { margin: 0 auto 0; width: 80px; height: 80px; - background-image: url("../images/logo.png"); + background-image: url('../images/logo.png'); background-position: center; background-repeat: no-repeat; background-size: contain; @@ -96,7 +95,7 @@ body { #github .logo { width: 50px; height: 100%; - background-image: url("../images/github-logo.png"); + background-image: url('../images/github-logo.png'); background-size: 40px; background-repeat: no-repeat; background-position: center center; @@ -106,7 +105,7 @@ body { #github .brand { width: 100px; height: 100%; - background-image: url("../images/github.png"); + background-image: url('../images/github.png'); background-size: contain; background-repeat: no-repeat; background-position: center right; @@ -125,10 +124,12 @@ body { #feedback .icon { width: 50px; height: 100%; - background-image: url("../images/mail.png"); + background-image: url('../images/mail.png'); background-size: contain; background-repeat: no-repeat; - background-position: center right, center left; + background-position: + center right, + center left; float: left; } @@ -145,7 +146,6 @@ body { opacity: 1; } - #tooltip { width: auto; font-size: 14px; @@ -157,7 +157,7 @@ body { background: rgba(255, 255, 255, 1); text-align: center; line-height: 20px; - border: 1px solid #CCC; + border: 1px solid #ccc; border-radius: 5px; display: none; @@ -172,14 +172,14 @@ body { } #tooltip:before { - content: " "; + content: ' '; width: 16px; height: 16px; margin: -8px 0 0 -8px; - background: #FFF; + background: #fff; border-style: solid; - border-color: #BBB; + border-color: #bbb; border-width: 0 1px 1px 0; position: absolute; @@ -194,7 +194,6 @@ body { display: block; } - /******************* * Box Containers *******************/ @@ -225,17 +224,17 @@ body { padding: 0.2em; margin: 0.5em; border-radius: 1em; - background: #CCC; + background: #ccc; float: left; } #container .bullet[active] { - background: #2D92EB; + background: #2d92eb; cursor: pointer; } #container .bullet:hover { - background: #2D92EB; + background: #2d92eb; cursor: pointer; } @@ -244,7 +243,7 @@ body { height: 100%; position: absolute; left: 0; - transition: 1.0s left ease; + transition: 1s left ease; } #container .helpers { @@ -277,11 +276,11 @@ body { } #container #scroll-left { - background-image: url("../images/arrow-left.png"); + background-image: url('../images/arrow-left.png'); } #container #scroll-right { - background-image: url("../images/arrow-right.png"); + background-image: url('../images/arrow-right.png'); right: 0; } @@ -289,7 +288,7 @@ body { width: 100%; padding: 10px; font-size: 30px; - border-bottom: 2px solid #FFF; + border-bottom: 2px solid #fff; text-align: center; box-sizing: border-box; } @@ -305,15 +304,15 @@ body { #container .info { width: 100%; - padding: 20px 0 ; + padding: 20px 0; font-size: 16px; text-align: center; } #container .highlight { - color: #DF5600 + color: #df5600; } #container a { text-decoration: none; -} \ No newline at end of file +} diff --git a/public/home/home.html b/public/home/home.html new file mode 100644 index 0000000..f013dfe --- /dev/null +++ b/public/home/home.html @@ -0,0 +1,142 @@ + + + + + Session Sync - Tutorial + + + + + +
+ +
+ +
+
+ Session Sync + +
+
+ + + + + +
+
+
+
+ +
+
+
+
+ Press + CTRL + SHIFT + F + to open the panel or use the widget provided to open the panel +
+
+ You can find all your recent browsing history in the history tab. +
+ Restore the session or save it if you want to keep indefinitely
+ Configure history settings in the configuration panel +
+
+ Use the search bar to easy + filter through saved sessions +
+ You can focus on the search bar by pressing + CTRL + F or + F3 +
+
+ Press right click to access aditional context menus for sessions or + bookmarks +
+
+
+ Resize the UI panel using the + resize-handle from the + bottom-left corner +
+
+ Resize session list width by dragging around the + green resize handle +
+
+ The addon offers multiple configuration options. Explore all of them in the + configuration panel +
+ Configuration panel can be accessed from the addon UI menu +
+
+ Drag and drop bookmark items over the trash bin drop area location + (bottom-right corner) to delete them +
+ Click on the red button to restore the + previous deleted items +
+
+ Session sync offers the ability to save (or not) pinned tabs and also to + remember the + pinned state +
+
+ You can edit session and bookmarks information to provide more valuable future + information +
+
+ Sort sessions by name, position, or date using the sort-selection menu from the + bottom-left corner of the UI +
+
+ The session menu toolbar provides fast access to various commands such as + restore, replace, merge or add + the current tab as bookmark +
+
+ Move your mouse above a bookmark to view the URL. +
+ Click on the URL bar to copy the link +
+
+ You can detach the UI into a separate window or + tab
+ Window Mode: + CTRL + SHIFT + 2 +   Tab Mode: + CTRL + SHIFT + U
+ or simply use the buttons from addon menu
+
+
+ Saved session are stored as bookmarks. It's recommended to use + Firefox Account + to sync your bookmarks.
+ Currenly you can only use Firefox Bookmark Manager (CTRL + SHIFT + O) to + export/import all your bookmarks (which includes all Session Sync saved data). +
+ Session Sync detects automatically all updated bookmarks after an import. +
+
+
+
+
+ + + + diff --git a/data/home/home.js b/public/home/home.js similarity index 88% rename from data/home/home.js rename to public/home/home.js index 31ef313..4fffb24 100644 --- a/data/home/home.js +++ b/public/home/home.js @@ -10,12 +10,11 @@ var tooltip; window.addEventListener('load', load); function load() { - Carousel = document.getElementById('carousel'); bullets = document.getElementById('bullets'); tooltip = document.getElementById('tooltip'); - bullets.addEventListener('mouseleave', function() { + bullets.addEventListener('mouseleave', function () { tooltip.removeAttribute('active'); }); @@ -35,9 +34,7 @@ function load() { } var initTutorial = function initTutorial() { - - if (inited) - return; + if (inited) return; inited = true; @@ -48,7 +45,6 @@ var initTutorial = function initTutorial() { initCarousel(); updateActiveBullet(bullets.firstElementChild.firstElementChild); - }; function updateActiveBullet(node) { @@ -63,9 +59,8 @@ function updateActiveBullet(node) { } } -function setCarouselTo(index) -{ - if (index < 0 || index > (size - 1)) { +function setCarouselTo(index) { + if (index < 0 || index > size - 1) { return; } @@ -74,8 +69,7 @@ function setCarouselTo(index) updateActiveBullet(TutorialEntries.getEntryByIndex(index).bullet); } -function advanceCarousel(offset) -{ +function advanceCarousel(offset) { var position = Carousel.getAttribute('advance') | 0; position += offset; if (position < 0 || position > (size - 1) * 100) { @@ -86,8 +80,7 @@ function advanceCarousel(offset) updateActiveBullet(TutorialEntries.getEntryByIndex(position / 100).bullet); } -function initCarousel() -{ +function initCarousel() { var moveLeft = document.getElementById('scroll-left'); var moveRight = document.getElementById('scroll-right'); @@ -95,8 +88,7 @@ function initCarousel() moveRight.addEventListener('click', advanceCarousel.bind(null, 100)); } -function HelpEntry(options) -{ +function HelpEntry(options) { var box = document.createElement('div'); box.className = 'box'; box.setAttribute('info', options.key); @@ -115,7 +107,7 @@ function HelpEntry(options) var visual = document.createElement('div'); visual.className = 'tutorial-image'; - visual.style.backgroundImage = 'url("../images/tutorial/' + options.key + '.png")'; + visual.style.backgroundImage = 'url("/images/tutorial/' + options.key + '.png")'; box.appendChild(title); box.appendChild(info); @@ -129,10 +121,10 @@ function HelpEntry(options) bullet.className = 'bullet'; bullet.setAttribute('left', options.index); bullet.setAttribute('tooltip', options.title); - bullet.addEventListener('click', function() { + bullet.addEventListener('click', function () { setCarouselTo(options.index); }); - bullet.addEventListener('mouseover', function() { + bullet.addEventListener('mouseover', function () { var pos = bullet.getBoundingClientRect(); tooltip.setAttribute('active', ''); tooltip.style.left = pos.x + pos.width / 2 + 'px'; @@ -148,8 +140,7 @@ function HelpEntry(options) this.link = reference; } -var TutorialEntries = (function TutorialEntries() -{ +var TutorialEntries = (function TutorialEntries() { var entries = {}; var entryList = [ { @@ -157,7 +148,7 @@ var TutorialEntries = (function TutorialEntries() key: 'overview' }, { - title : 'Session history', + title: 'Session history', key: 'history-list' }, { @@ -205,19 +196,18 @@ var TutorialEntries = (function TutorialEntries() key: 'resize-sessions' }, { - title : 'Detach mode', + title: 'Detach mode', key: 'detach-mode' }, { title: 'Export/Import Sessions', key: 'export-import' - }, + } ]; - function init() - { + function init() { var index = 0; - entryList.forEach(function(info) { + entryList.forEach(function (info) { info.index = index; var entry = new HelpEntry(info); entries[info.key] = entry; @@ -243,7 +233,6 @@ var TutorialEntries = (function TutorialEntries() getEntryByIndex: getEntryByIndex, getEntryByKey: getEntryByKey }; - })(); // browser.commands.getAll(). diff --git a/data/images/arrow-down.png b/public/images/arrow-down.png similarity index 100% rename from data/images/arrow-down.png rename to public/images/arrow-down.png diff --git a/data/images/arrow-left.png b/public/images/arrow-left.png similarity index 100% rename from data/images/arrow-left.png rename to public/images/arrow-left.png diff --git a/data/images/arrow-right.png b/public/images/arrow-right.png similarity index 100% rename from data/images/arrow-right.png rename to public/images/arrow-right.png diff --git a/data/images/arrow-up.png b/public/images/arrow-up.png similarity index 100% rename from data/images/arrow-up.png rename to public/images/arrow-up.png diff --git a/data/images/favicon.png b/public/images/favicon.png similarity index 100% rename from data/images/favicon.png rename to public/images/favicon.png diff --git a/data/images/github-logo.png b/public/images/github-logo.png similarity index 100% rename from data/images/github-logo.png rename to public/images/github-logo.png diff --git a/data/images/github.png b/public/images/github.png similarity index 100% rename from data/images/github.png rename to public/images/github.png diff --git a/data/images/grain.png b/public/images/grain.png similarity index 100% rename from data/images/grain.png rename to public/images/grain.png diff --git a/data/images/grain_header.png b/public/images/grain_header.png similarity index 100% rename from data/images/grain_header.png rename to public/images/grain_header.png diff --git a/data/images/icon128.png b/public/images/icon128.png similarity index 100% rename from data/images/icon128.png rename to public/images/icon128.png diff --git a/data/images/icon16.png b/public/images/icon16.png similarity index 100% rename from data/images/icon16.png rename to public/images/icon16.png diff --git a/data/images/icon32.png b/public/images/icon32.png similarity index 100% rename from data/images/icon32.png rename to public/images/icon32.png diff --git a/data/images/icon48.png b/public/images/icon48.png similarity index 100% rename from data/images/icon48.png rename to public/images/icon48.png diff --git a/data/images/icon64.png b/public/images/icon64.png similarity index 100% rename from data/images/icon64.png rename to public/images/icon64.png diff --git a/data/images/icon96.png b/public/images/icon96.png similarity index 100% rename from data/images/icon96.png rename to public/images/icon96.png diff --git a/data/images/icons/booklet.png b/public/images/icons/booklet.png similarity index 100% rename from data/images/icons/booklet.png rename to public/images/icons/booklet.png diff --git a/data/images/icons/bookshelf.png b/public/images/icons/bookshelf.png similarity index 100% rename from data/images/icons/bookshelf.png rename to public/images/icons/bookshelf.png diff --git a/data/images/icons/briefcase.png b/public/images/icons/briefcase.png similarity index 100% rename from data/images/icons/briefcase.png rename to public/images/icons/briefcase.png diff --git a/data/images/icons/brightness.png b/public/images/icons/brightness.png similarity index 100% rename from data/images/icons/brightness.png rename to public/images/icons/brightness.png diff --git a/data/images/icons/browser.png b/public/images/icons/browser.png similarity index 100% rename from data/images/icons/browser.png rename to public/images/icons/browser.png diff --git a/data/images/icons/clipboard.png b/public/images/icons/clipboard.png similarity index 100% rename from data/images/icons/clipboard.png rename to public/images/icons/clipboard.png diff --git a/data/images/icons/denied.png b/public/images/icons/denied.png similarity index 100% rename from data/images/icons/denied.png rename to public/images/icons/denied.png diff --git a/data/images/icons/document.png b/public/images/icons/document.png similarity index 100% rename from data/images/icons/document.png rename to public/images/icons/document.png diff --git a/data/images/icons/frames.png b/public/images/icons/frames.png similarity index 100% rename from data/images/icons/frames.png rename to public/images/icons/frames.png diff --git a/data/images/icons/gear.png b/public/images/icons/gear.png similarity index 100% rename from data/images/icons/gear.png rename to public/images/icons/gear.png diff --git a/data/images/icons/globe.png b/public/images/icons/globe.png similarity index 100% rename from data/images/icons/globe.png rename to public/images/icons/globe.png diff --git a/data/images/icons/heart.png b/public/images/icons/heart.png similarity index 100% rename from data/images/icons/heart.png rename to public/images/icons/heart.png diff --git a/data/images/icons/mail.png b/public/images/icons/mail.png similarity index 100% rename from data/images/icons/mail.png rename to public/images/icons/mail.png diff --git a/data/images/icons/merge.png b/public/images/icons/merge.png similarity index 100% rename from data/images/icons/merge.png rename to public/images/icons/merge.png diff --git a/data/images/icons/pin.png b/public/images/icons/pin.png similarity index 100% rename from data/images/icons/pin.png rename to public/images/icons/pin.png diff --git a/data/images/icons/pin2.png b/public/images/icons/pin2.png similarity index 100% rename from data/images/icons/pin2.png rename to public/images/icons/pin2.png diff --git a/data/images/icons/power.png b/public/images/icons/power.png similarity index 100% rename from data/images/icons/power.png rename to public/images/icons/power.png diff --git a/data/images/icons/recycle.png b/public/images/icons/recycle.png similarity index 100% rename from data/images/icons/recycle.png rename to public/images/icons/recycle.png diff --git a/data/images/icons/ribbon.png b/public/images/icons/ribbon.png similarity index 100% rename from data/images/icons/ribbon.png rename to public/images/icons/ribbon.png diff --git a/data/images/icons/stack.png b/public/images/icons/stack.png similarity index 100% rename from data/images/icons/stack.png rename to public/images/icons/stack.png diff --git a/data/images/icons/star.png b/public/images/icons/star.png similarity index 100% rename from data/images/icons/star.png rename to public/images/icons/star.png diff --git a/data/images/icons/tools.png b/public/images/icons/tools.png similarity index 100% rename from data/images/icons/tools.png rename to public/images/icons/tools.png diff --git a/data/images/icons/x.png b/public/images/icons/x.png similarity index 100% rename from data/images/icons/x.png rename to public/images/icons/x.png diff --git a/data/images/icons_v2/menu.png b/public/images/icons_v2/menu.png similarity index 100% rename from data/images/icons_v2/menu.png rename to public/images/icons_v2/menu.png diff --git a/data/images/icons_v2/more.png b/public/images/icons_v2/more.png similarity index 100% rename from data/images/icons_v2/more.png rename to public/images/icons_v2/more.png diff --git a/data/images/logo.png b/public/images/logo.png similarity index 100% rename from data/images/logo.png rename to public/images/logo.png diff --git a/data/images/mail.png b/public/images/mail.png similarity index 100% rename from data/images/mail.png rename to public/images/mail.png diff --git a/data/images/material_icons/repeat.svg b/public/images/material_icons/repeat.svg similarity index 100% rename from data/images/material_icons/repeat.svg rename to public/images/material_icons/repeat.svg diff --git a/data/images/material_icons/repeat_one.svg b/public/images/material_icons/repeat_one.svg similarity index 100% rename from data/images/material_icons/repeat_one.svg rename to public/images/material_icons/repeat_one.svg diff --git a/public/images/material_icons/tab_group.svg b/public/images/material_icons/tab_group.svg new file mode 100644 index 0000000..b94ee3c --- /dev/null +++ b/public/images/material_icons/tab_group.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/images/menu.png b/public/images/menu.png similarity index 100% rename from data/images/menu.png rename to public/images/menu.png diff --git a/data/images/resize.png b/public/images/resize.png similarity index 100% rename from data/images/resize.png rename to public/images/resize.png diff --git a/data/images/sort.png b/public/images/sort.png similarity index 100% rename from data/images/sort.png rename to public/images/sort.png diff --git a/data/images/trash-can.png b/public/images/trash-can.png similarity index 100% rename from data/images/trash-can.png rename to public/images/trash-can.png diff --git a/data/images/tutorial/addon-menu.png b/public/images/tutorial/addon-menu.png similarity index 100% rename from data/images/tutorial/addon-menu.png rename to public/images/tutorial/addon-menu.png diff --git a/data/images/tutorial/config-panel.png b/public/images/tutorial/config-panel.png similarity index 100% rename from data/images/tutorial/config-panel.png rename to public/images/tutorial/config-panel.png diff --git a/data/images/tutorial/context-menus.png b/public/images/tutorial/context-menus.png similarity index 100% rename from data/images/tutorial/context-menus.png rename to public/images/tutorial/context-menus.png diff --git a/data/images/tutorial/delete-item.png b/public/images/tutorial/delete-item.png similarity index 100% rename from data/images/tutorial/delete-item.png rename to public/images/tutorial/delete-item.png diff --git a/data/images/tutorial/detach-mode.png b/public/images/tutorial/detach-mode.png similarity index 100% rename from data/images/tutorial/detach-mode.png rename to public/images/tutorial/detach-mode.png diff --git a/data/images/tutorial/export-import.png b/public/images/tutorial/export-import.png similarity index 100% rename from data/images/tutorial/export-import.png rename to public/images/tutorial/export-import.png diff --git a/data/images/tutorial/history-list.png b/public/images/tutorial/history-list.png similarity index 100% rename from data/images/tutorial/history-list.png rename to public/images/tutorial/history-list.png diff --git a/data/images/tutorial/main-menu.png b/public/images/tutorial/main-menu.png similarity index 100% rename from data/images/tutorial/main-menu.png rename to public/images/tutorial/main-menu.png diff --git a/data/images/tutorial/overview.png b/public/images/tutorial/overview.png similarity index 100% rename from data/images/tutorial/overview.png rename to public/images/tutorial/overview.png diff --git a/data/images/tutorial/resize-sessions.png b/public/images/tutorial/resize-sessions.png similarity index 100% rename from data/images/tutorial/resize-sessions.png rename to public/images/tutorial/resize-sessions.png diff --git a/data/images/tutorial/resize-ui.png b/public/images/tutorial/resize-ui.png similarity index 100% rename from data/images/tutorial/resize-ui.png rename to public/images/tutorial/resize-ui.png diff --git a/data/images/tutorial/session-context-menu.png b/public/images/tutorial/session-context-menu.png similarity index 100% rename from data/images/tutorial/session-context-menu.png rename to public/images/tutorial/session-context-menu.png diff --git a/data/images/tutorial/session-edit.png b/public/images/tutorial/session-edit.png similarity index 100% rename from data/images/tutorial/session-edit.png rename to public/images/tutorial/session-edit.png diff --git a/data/images/tutorial/session-filter.png b/public/images/tutorial/session-filter.png similarity index 100% rename from data/images/tutorial/session-filter.png rename to public/images/tutorial/session-filter.png diff --git a/data/images/tutorial/session-menu.png b/public/images/tutorial/session-menu.png similarity index 100% rename from data/images/tutorial/session-menu.png rename to public/images/tutorial/session-menu.png diff --git a/data/images/tutorial/session-save-settings.png b/public/images/tutorial/session-save-settings.png similarity index 100% rename from data/images/tutorial/session-save-settings.png rename to public/images/tutorial/session-save-settings.png diff --git a/data/images/tutorial/session-sorting.png b/public/images/tutorial/session-sorting.png similarity index 100% rename from data/images/tutorial/session-sorting.png rename to public/images/tutorial/session-sorting.png diff --git a/data/images/tutorial/sessions.png b/public/images/tutorial/sessions.png similarity index 100% rename from data/images/tutorial/sessions.png rename to public/images/tutorial/sessions.png diff --git a/data/images/tutorial/url-bar.png b/public/images/tutorial/url-bar.png similarity index 100% rename from data/images/tutorial/url-bar.png rename to public/images/tutorial/url-bar.png diff --git a/scripts/development.js b/scripts/development.js deleted file mode 100644 index dd844b3..0000000 --- a/scripts/development.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -(function DevelopmentMode() -{ - // ------------------------------------------------------------------------ - // Init - - browser.commands.onCommand.addListener(function(command) { - if (command == 'session-sync-save-history') { - for (var i = 0; i < 10; i++) { - SessionAutoSave.activeSession.update(); - } - } - }); - - // ------------------------------------------------------------------------ - // Events - -})(); \ No newline at end of file diff --git a/scripts/favicon-service.js b/scripts/favicon-service.js deleted file mode 100644 index 89641e5..0000000 --- a/scripts/favicon-service.js +++ /dev/null @@ -1,122 +0,0 @@ -'use strict'; - -var FaviconService = (function FaviconService() { - - // var ignoreFaviconSearch = { - // 'chrom' : true, - // 'javas' : true, - // 'data:' : true, - // 'file:' : true, - // 'about' : true, - // 'moz-e' : true, - // }; - - var database = {}; - var urlParser = document.createElement('a'); - var serviceStatus = true; - var serviceStatusKey = 'services.favicon.enabled'; - - var getBase64ImageFromUrl = async function getBase64ImageFromUrl(imageUrl) { - - var res = await fetch(imageUrl); - var blob = await res.blob(); - - return new Promise((resolve, reject) => { - var reader = new FileReader(); - reader.addEventListener("load", function () { - resolve(reader.result); - }, false); - - reader.onerror = function() { - return reject(this); - }; - reader.readAsDataURL(blob); - }); - } - - var getHostName = function getHostName(url) - { - urlParser.href = url; - return urlParser.hostname; - } - - var getFaviconUrl = function getFaviconUrl(url, callback) - { - var key = '@favIconUrl:' + getHostName(url); - browser.storage.local.get(key) - .then(function (obj) { - callback(obj[key]); - }, callback); - } - - var setFaviconUrl = function setFaviconUrl(key, favIconUrl) - { - getBase64ImageFromUrl(favIconUrl) - .then(function(result) { - browser.storage.local.set({ [key] : result }); - }, - function onError(info) { - browser.storage.local.set({ [key] : favIconUrl }); - }); - } - - var checkTabFaviconSaved = function checkTabFaviconSaved(tab, waitToLoad) - { - if (serviceStatus == false) { - return; - } - - if (tab.favIconUrl) - { - getFaviconUrl(tab.url, function (value) { - if (value === undefined || tab.url != value) { - var key = '@favIconUrl:' + getHostName(tab.url); - setFaviconUrl(key, tab.favIconUrl) - } - }); - } - else if (waitToLoad != false) - { - function handle(tabId, changeInfo, tabInfo) - { - browser.tabs.onUpdated.removeListener(handle); - checkTabFaviconSaved(tabInfo, false); - } - - browser.tabs.onUpdated.addListener(handle, { - tabId: tab.id, - properties: ['favIconUrl'] - }); - } - } - - var initConfig = function initConfig() - { - browser.storage.local.get(serviceStatusKey) - .then(function (obj) { - serviceStatus = obj[serviceStatusKey]; - }); - }; - - // ************************************************************************ - // Events - - browser.storage.onChanged.addListener(function onChange(object) { - if (object[serviceStatusKey]) { - serviceStatus = object[serviceStatusKey].newValue; - } - }); - - // ************************************************************************ - // Init - - initConfig(); - - // ************************************************************************ - // Public API - - return { - getFaviconUrl: getFaviconUrl, - checkTabFaviconSaved: checkTabFaviconSaved - } -})(); diff --git a/scripts/main.js b/scripts/main.js deleted file mode 100644 index a80b939..0000000 --- a/scripts/main.js +++ /dev/null @@ -1,195 +0,0 @@ -// console.log(browser.runtime.getManifest()); - -var Commands = { - toggleWidget: '_execute_browser_action', - openTutorial: 'session-sync-tutorial', - detachToTab: 'session-sync-detach-tab', - detachToWindow: 'session-sync-detach-window', - leaveFeedback: 'session-sync-leave-feedback', - openGithub: 'session-sync-open-github' -}; - -browser.runtime.onInstalled.addListener(function (startInfo) { - if (startInfo.reason == 'install') { - SessionSync.checkEvent(Commands.openTutorial); - } -}); - -browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { - SessionSync.checkEvent(message.event); -}); - -browser.commands.onCommand.addListener(function(command) { - SessionSync.checkEvent(command); -}); - -var SessionSync = (function () { - - var windowInfoKey = 'style.window.detach'; - var activeWindow = null; - var defaultWindowProperties = { - width: 800, - height: 600 - }; - - browser.windows.onRemoved.addListener(function (windowID) { - if (activeWindow && windowID == activeWindow.id) { - activeWindow = null; - } - }); - - function checkEvent(event) - { - switch (event) - { - case Commands.detachToTab: - detachedToTab(); - break; - - case Commands.detachToWindow: - detachToWindow(); - break; - - case Commands.leaveFeedback: - leaveFeedback(); - break; - - case Commands.openTutorial: - browser.tabs.create({ - url: 'data/home/home.html', - active: true, - }); - break; - - case Commands.openGithub: - browser.tabs.create({ - url: 'https://github.com/ReDEnergy/SessionSync', - active: true, - }); - break; - } - } - - function leaveFeedback() - { - browser.tabs.create({ - url: 'mailto:gabriel.ivanica@gmail.com?Subject=[Session-Sync]%20User%20feedback' - }).then(function (tab) { - browser.tabs.remove(tab.id); - }); - } - - function detachToWindow() - { - if (activeWindow) { - browser.windows.update(activeWindow.id, { - focused: true - }); - return; - } - - browser.storage.local.get([windowInfoKey]).then(function (sizeInfo) { - var windowInfo = sizeInfo[windowInfoKey]; - if (windowInfo == undefined) - { - windowInfo = defaultWindowProperties; - } - - browser.windows.create({ - width: windowInfo.width, - height: windowInfo.height, - // left: 0, - // top: 0, - url: 'data/session-sync.html', - type: 'panel' - }).then(function (newWindow) { - activeWindow = newWindow; - // WindowUtils.centerWindow(window); - }); - }); - } - - function detachedToTab() - { - browser.tabs.query({ - title: 'Session Sync', - windowId: browser.windows.WINDOW_ID_CURRENT - }) - .then(function (tabs) { - if (tabs.length > 0) { - for (let i in tabs) { - if (tabs[i].url.indexOf('moz-extension') == 0) { - browser.tabs.update(tabs[i].id, { - active: true - }); - return; - } - } - } - - browser.tabs.create({ - url: 'data/session-sync.html' - }); - }); - } - - return { - checkEvent: checkEvent, - }; -})(); - -var WindowUtils = (function () -{ - var windowMargins = { - left: -8, - top: 12 - }; - - function resizeAndCenter(window, windowProperties) { - // console.log('Fullscreen', window); - - var screen = { - width : window.width + 2 * windowMargins.left, - height: window.height + 2 * windowMargins.top - }; - - windowProperties.left = window.left + (((screen.width - windowProperties.width) / 2) | 0) + windowMargins.left; - windowProperties.top = window.top + (((screen.height - windowProperties.height) / 2) | 0) + windowMargins.top; - - // console.log('Properties', windowProperties, 'Screen', screen); - browser.windows.update(window.id, windowProperties); - } - - function centerWindow(window) { - - // console.log(window); - - var properties = { - width: window.width, - height: window.height, - state: window.state, - }; - - browser.windows.update(window.id, { - state: 'maximized' - }).then(function (window) { - resizeAndCenter(window, properties); - }); - } - - return { - centerWindow: centerWindow - }; - -})(); - -// ------------------------------------------------------------------------ -// App Init - -// Delete previous undo events -browser.storage.local.set({'undo.events' : []}); -browser.storage.local.set({'trashcan.hide' : true}); -browser.storage.local.set({'trashcan.hide-count' : 0}); -browser.storage.local.set({'session.active.filter' : ''}); -browser.storage.local.set({'session.selected' : null}); -browser.storage.local.set({'session.history.selected' : null}); diff --git a/scripts/session-history.js b/scripts/session-history.js deleted file mode 100644 index aca8025..0000000 --- a/scripts/session-history.js +++ /dev/null @@ -1,252 +0,0 @@ -'use strict'; - -var BrowserSession = function BrowserSession() -{ - this.timer; - this.active = true; - this.tabCount = 0; - this.startTime = (new Date()).getTime(); - this.lastSave = this.startTime; - this.windows = { }; -}; - -var WindowSession = function WindowSession(id) -{ - this.id = id; - this.tabs = []; - this.active = true; - this.saveTime = (new Date()).getTime(); -}; - -BrowserSession.prototype.update = function update() -{ - // Prepare session information - browser.windows.getAll({ - populate: true, - windowTypes: ['normal'] - }) - .then(function (mozWindows) { - var tabCount = 0; - - for (var key in this.windows) - { - this.windows[key].active = false; - } - - for (let mozWindow of mozWindows) - { - if (mozWindow.incognito && this.config.savePrivate == false) - { - delete this.windows[mozWindow.id]; - continue; - } - - var windowInfo = new WindowSession(mozWindow.id); - - // Add window tabs - for (var i in mozWindow.tabs) - { - var tab = mozWindow.tabs[i]; - var info = SessionManagement.getTabInfo(tab.url); - var sessionTab = { - title: tab.title, - url: info.url, - favIconUrl: info.lazyLoading ? info.favIconUrl : tab.favIconUrl, - pinned: tab.pinned, - }; - tabCount++; - windowInfo.tabs[tab.index] = sessionTab; - } - - this.windows[mozWindow.id] = windowInfo; - } - - // Update internal storage - this.lastSave = (new Date()).getTime(); - this.tabCount = tabCount; - - // Save new session - browser.storage.local.set({ 'history.active' : this}); - - }.bind(this)); -}; - -BrowserSession.prototype.clear = function clear() -{ - // Save new session - this.windows = {}; - browser.storage.local.set({ 'history.active' : this}); -}; - -BrowserSession.prototype.start = function start(updateInterval) -{ - if (this.timer == undefined) { - // console.log('Session auto-save: start'); - this.timer = setInterval(this.update.bind(this), updateInterval * 1000); - this.update(); - } -}; - -BrowserSession.prototype.stop = function stop() -{ - if (this.timer != undefined) { - // console.log('Session auto-save: stop'); - clearInterval(this.timer); - this.timer = undefined; - } -}; - -BrowserSession.prototype.resetInterval = function resetInterval(updateInterval) -{ - if (this.timer != undefined) { - // console.log('Session auto-save: reset interval'); - this.stop(); - this.start(updateInterval); - } -}; - -BrowserSession.prototype.setConfig = function setConfig(config) -{ - this.config = config; - this.resetInterval(config.interval); - config.enabled ? this.start(config.interval) : this.stop(); -}; - -var SessionAutoSave = (function SessionAutoSave() -{ - // ------------------------------------------------------------------------ - // Init - - var activeSession = new BrowserSession(); - var sessions = []; - var sessionsKey = 'history.sessions'; - var activeSessionKey = 'history.active'; - - var config = { - key: 'history.config', - enabled: true, - interval: 15, - saveBuffer: 3, - savingSlots: 20, - expireTimeHours: 48, // hours - savePrivate: false, - }; - - browser.storage.local.get(config.key) - .then(function onSuccess(obj) { - if (obj[config.key]) - { - config = obj[config.key]; - init(); - } - else - { - browser.storage.local.set({ - [config.key] : config - }).then(init); - } - - }, function onError() { - init(); - }); - - var upgradeStorage = function upgradeStorage() - { - for (var i = 0; i < sessions.length; i++) - { - // New storage works with WindowSession objects - if (sessions[i] != undefined && Array.isArray(sessions[i].windows[0]) == true) - { - for (var j = 0; j < sessions[i].windows.length; j++) { - var session = new WindowSession(0); - session.state = 'history'; - session.saveTime = sessions[i].lastSave; - session.tabs = sessions[i].windows[j]; - sessions[i].windows[j] = session; - } - } - } - }; - - var updateStorage = function updateStorage() - { - upgradeStorage(); - - var removeCount = Math.max(sessions.length - config.savingSlots, 0); - var expireTime = new Date(new Date() - config.expireTimeHours * 3600 * 1000); - - var list = []; - // console.log('To remove', removeCount); - - // compact free space and delete expired sessions - sessions.forEach(function (session) { - if (session != undefined && (removeCount <= 0 || session.lastSave > expireTime)) - { - list.push(session); - } - else - { - if (removeCount > 0) - removeCount--; - } - }); - - if (removeCount > 0) - { - list = list.slice(removeCount); - } - - // update session list - sessions = list; - }; - - var init = function init() - { - browser.storage.local.get([activeSessionKey, sessionsKey]) - .then(function(obj) { - - if (obj[sessionsKey]) { - sessions = obj[sessionsKey]; - updateStorage(); - } - - // console.log(sessions); - - if (obj[activeSessionKey]) { - obj[activeSessionKey].windows = Object.values(obj[activeSessionKey].windows); - delete obj[activeSessionKey].active; - sessions.push(obj[activeSessionKey]); - } - - browser.storage.local.set({ [sessionsKey] : sessions }); - activeSession.setConfig(config); - }); - }; - - // ------------------------------------------------------------------------ - // Events - - browser.runtime.onMessage.addListener(function (message) { - if (message.event == 'history.deleteActive') { - activeSession.clear(); - } - }); - - var onConfigChanged = function onConfigChanged(object) - { - if (object[config.key]) { - config = object[config.key].newValue; - activeSession.setConfig(config); - } - }; - - browser.storage.onChanged.addListener(onConfigChanged); - - // ------------------------------------------------------------------------ - // Public API - - return { - activeSession : activeSession - }; - -})(); \ No newline at end of file diff --git a/scripts/session-management.js b/scripts/session-management.js deleted file mode 100644 index 885acd1..0000000 --- a/scripts/session-management.js +++ /dev/null @@ -1,379 +0,0 @@ -'use strict'; - -var SessionManagement = (function SessionManagement() -{ - var lazyLoadingKey = 'restore.lazy.loading'; - var lazyLoadingUrl = browser.extension.getURL('data/lazy/lazy.html'); - var lazyLoading = false; - var windowsTabsToKill = []; - - var reverseOrderKey = 'restore.reverse.order'; - var reverseOrder = false; - - function getLazyLoadingParameters(url) - { - let paramater = {}; - let params = url.split('?'); - if (params.length > 1) { - let paras = params[1].split('&'); - for (let p of paras) { - paramater[p.split('=')[0]] = decodeURIComponent(p.split('=')[1]); - } - } - return paramater; - } - - function getTabInfo(url) - { - if (url.startsWith(lazyLoadingUrl)) - { - var info = getLazyLoadingParameters(url); - info.lazyLoading = true; - return info; - } - return { - url: url - }; - } - - var setLazyLoading = function setLazyLoading(state) - { - lazyLoading = state; - browser.tabs.onActivated.removeListener(onLazyTabActivated); - if (state) - { - browser.tabs.onActivated.addListener(onLazyTabActivated); - } - }; - - var initConfig = function initConfig() - { - browser.storage.local.get(lazyLoadingKey) - .then(function (obj) { - setLazyLoading(obj[lazyLoadingKey]); - }); - - browser.storage.local.get(reverseOrderKey) - .then(function (obj) { - reverseOrder = (obj[reverseOrderKey] == true); - }); - }; - - var openTab = function openTab(options) - { - var checkFavicon = options.favicon === true ? FaviconService.checkTabFaviconSaved : () => {}; - delete options.favicon; - - var mode = options.mode; - delete options.mode; - - switch (mode) - { - case 'newTab': { - browser.tabs.create(options).then(checkFavicon); - break; - } - - case 'newWindow': { - browser.windows.create(options).then(function (windowInfo) { - checkFavicon(windowInfo.tabs[0]); - }); - break; - } - - case 'activeTab': { - browser.tabs.query({active: true, windowId: browser.windows.WINDOW_ID_CURRENT}) - .then(tabs => browser.tabs.get(tabs[0].id)) - .then(tab => { - browser.tabs.update(tab.id, options).then(checkFavicon); - }); - break; - } - - default: - break; - } - }; - - var createLazyTab = function createLazyTab(bookmark) - { - let title = 'data/lazy/lazy.html' + - '?state=redirect' + - '&title=' + encodeURIComponent(bookmark.title) + - '&url=' + encodeURIComponent(bookmark.url); - - if (bookmark.favIconUrl) { - title += '&favIconUrl=' + encodeURIComponent(bookmark.favIconUrl); - } - - browser.tabs.create({ - url: title, - active: false, - pinned: bookmark.pinned, - windowId: bookmark.windowID, - }) - .then( - function success() { - }, - function error() { - browser.tabs.create({ - url: title, - active: false, - pinned: bookmark.pinned, - windowId: bookmark.windowID, - }) - .then(function (tab) { - browser.tabs.update([tab.id], { - title: bookmark.title, - }); - }); - } - ); - }; - - var openLazyTab = function openLazyTab(bookmark) - { - if (bookmark.favIconUrl) { - createLazyTab(bookmark); - } - else - { - FaviconService.getFaviconUrl(bookmark.url, function (favIconUrl) { - bookmark.favIconUrl = favIconUrl; - createLazyTab(bookmark); - }); - } - }; - - var checkReverseOrder = function checkReverseOrder(bookmarks) - { - if (reverseOrder === true) - { - bookmarks = bookmarks.reverse(); - } - }; - - var restoreSession = function restoreSession(folderID, options) - { - browser.bookmarks.getChildren(folderID) - .then(function (bookmarks) { - if (options && options.newWindow) - { - createSessionWindow(bookmarks) - .then(function(createdWindow) { - if (createdWindow) - { - if (options.closeAll) - { - browser.windows.getAll({ - populate: true, - windowTypes: ['normal'] - }) - .then(function (windows) { - console.log(windows, createdWindow); - windows.forEach(function (mozWidnow) { - if (mozWidnow.id != createdWindow.id) { - browser.windows.remove(mozWidnow.id); - } - }); - }); - } - - if (options.closeWindow) - { - browser.windows.remove(options.closeWindow); - } - } - }, function onError() { - console.log('strage error'); - }); - } - else - { - checkReverseOrder(bookmarks); - bookmarks.forEach(function (bookmark) { - - if (lazyLoading) - { - openLazyTab(bookmark); - } - else - { - browser.tabs.create({ - url: bookmark.url, - active: false, - }) - .then(function(tabInfo) { - FaviconService.checkTabFaviconSaved(tabInfo); - }); - } - }); - } - }, - function fail() { - console.log('Error getting bookmarks from folder ID: ', folderID); - }); - }; - - var createSessionWindow = function createSessionWindow(bookmarks) - { - var action = new Promise((resolve, reject) => { - if (bookmarks.length > 0) - { - browser.windows.create({}) - .then(function(mozWindow) { - resolve(mozWindow); - var windowID = mozWindow.id; - windowsTabsToKill[windowID] = mozWindow.tabs[0].id; - - checkReverseOrder(bookmarks); - bookmarks.forEach(function (bookmark) { - if (lazyLoading) - { - bookmark.windowID = windowID; - openLazyTab(bookmark); - } - else - { - browser.tabs.create({ - url: bookmark.url, - pinned: bookmark.pinned, - windowId: windowID, - }) - .then(function(tabInfo) { - FaviconService.checkTabFaviconSaved(tabInfo); - }); - } - }); - }); - } - else - { - reject(); - } - }); - return action; - }; - - var restoreSessions = function restoreSessions(windows) - { - windows.forEach (function (bookmarks) { - createSessionWindow(bookmarks); - }); - }; - - var restoreLazyTab = function restoreLazyTab(tab, url) - { - let redirectInfo = getLazyLoadingParameters(url ? url : tab.url); - if (redirectInfo.state == 'redirect') - { - browser.tabs.update(tab.id, { - url: redirectInfo.url - }) - .then(function(tabInfo) { - tabInfo.url = redirectInfo.url; - FaviconService.checkTabFaviconSaved(tabInfo); - }); - } - }; - - // ************************************************************************ - // Events - - var onLazyTabActivated = function onLazyTabActivated(tab) - { - if (lazyLoading) - { - browser.tabs.get(tab.tabId).then(function (activeTab) { - if (activeTab.url.startsWith('moz-extension')) - { - restoreLazyTab(activeTab); - } - else if (activeTab.status == 'loading') { - restoreLazyTab(activeTab, activeTab.title); - } - }); - } - }; - - var fixLazySession = function fixLazySession(folderID) - { - var result = browser.bookmarks.getChildren(folderID); - result.then(function (bookmarks) { - console.log(bookmarks); - - bookmarks.forEach(function (bookmark) { - if (bookmark.url.startsWith('moz-extension')) - { - let redirectInfo = getLazyLoadingParameters(bookmark.url); - if (redirectInfo.url != undefined) { - console.log(bookmark, redirectInfo); - browser.bookmarks.update(bookmark.id, { - url: redirectInfo.url - }); - } - } - }); - }, function fail() { - console.log('Error getting bookmarks from folder ID: ', folderID); - }); - }; - - browser.runtime.onMessage.addListener(function (message) { - switch (message.event) - { - case 'restore-session': - restoreSession(message.bookmarkID, message.options); - break; - - case 'restore-sessions': - restoreSessions(message.windows); - break; - - case 'save-favicon': - FaviconService.checkTabFaviconSaved(message.tab, false); - break; - - case 'open-tab': - openTab(message.options); - break; - - case 'fix-lazy-session': - fixLazySession(message.bookmarkID); - break; - - } - }); - - browser.storage.onChanged.addListener(function onChange(object) { - if (object[lazyLoadingKey]) { - setLazyLoading(object[lazyLoadingKey].newValue); - } - if (object[reverseOrderKey]) { - reverseOrder = object[reverseOrderKey].newValue == true; - } - }); - - browser.tabs.onCreated.addListener(function (tab) { - var killTabID = windowsTabsToKill[tab.windowId]; - if (killTabID != undefined && killTabID != tab.id) - { - browser.tabs.remove(killTabID); - windowsTabsToKill[tab.windowId] = undefined; - restoreLazyTab(tab, tab.title); - } - }); - - // ************************************************************************ - // Init - - initConfig(); - - // ************************************************************************ - // Public API - - return { - getTabInfo : getTabInfo - }; - -})(); diff --git a/dev/css/_style.css b/src/assets/css/_style.css similarity index 63% rename from dev/css/_style.css rename to src/assets/css/_style.css index 708137f..f4144d3 100644 --- a/dev/css/_style.css +++ b/src/assets/css/_style.css @@ -1,149 +1,148 @@ -/** - * Session-Sync - */ -#session-sync { - width: 100%; - height: 100%; - min-height: 400px; - margin: 0; - - font-size: 12px; - font-family: "Open Sans", "Segoe UI", Arial, Helvetica, sans-serif; - - color: #000 !important; - overflow: hidden; - - -moz-user-select: none; -} - -#session-sync[panel="false"] { - position: absolute; -} - -#session-sync[panel="false"] .sessions-area .panel-resize-handle { - display: none; -} - -/* - * Only DOM nodes from here - */ - -#session-sync #overlay { - width: 100%; - height: 100%; - - background: rgba(255, 255, 255, 0.01); - - position: absolute; - top: 0; - left: 0; - z-index: 2500; - - display: none; -} - -#session-sync #overlay[state="true"] { - display: block; -} - -/* - * Panel Body - */ - - #session-sync #body { - height: 100%; - } - -#session-sync[config] .body { - background-color: rgba(255, 255, 255, 0.95); -} - -#session-sync .sessions-area { - width: 100%; - height: 100%; - - box-sizing: border-box; - - transition: all 0.2s; - - display: flex; - flex-direction: column; -} - -#session-sync[show="false"] .sessions-area { - display: none; -} - -#session-sync[state="pin"] .sessions-area { - top: 0; - right: 0; - margin: 0; - transform: translate(0); -} - -#session-sync[state="centered"] .sessions-area { - top: calc(50% - 50px); - left: 50%; - transform: translate(-50%, -50%); -} - -#session-sync[config] .sessions-area { - left: 400px; - top: 20%; - transform: translate(0); -} - -#session-sync .sessions-area-body { - width: 100%; - overflow: hidden; - - flex: 1; - display: flex; - flex-direction: row; -} - -/* - * Container resize - */ - -#session-sync[resize] .body { - background: rgba(255, 255, 255, 0.95) !important; - cursor: se-resize; -} - -#session-sync[resize] .sessions-area { - transition: none; -} - -#session-sync .sessions-area .panel-resize-handle -{ - content: " "; - width: 12px; - height: 12px; - margin-top: -12px; - background: url("images/resize.png") center no-repeat !important; - position: absolute; - background: #DDD; - bottom: 0; - left: 0px; - cursor: sw-resize; - z-index: 10; - - transform: rotate(90deg); -} - -#session-sync .sessions-area .panel-resize-handle:hover -{ - background: #1AB61A; -} - -#session-sync .sessions-area .panel-resize-handle[resizing] -{ - background: #2D92EB; -} - -#clipboard { - position: absolute; - top: -100px; - z-index: -100; -} \ No newline at end of file +/** + * Session-Sync + */ +#session-sync { + width: 100%; + height: 100%; + min-width: 500px; + min-height: 400px; + margin: 0; + + font-size: 12px; + font-family: 'Open Sans', 'Segoe UI', Arial, Helvetica, sans-serif; + + color: #000 !important; + color-scheme: light dark; + overflow: hidden; + + user-select: none; +} + +#session-sync[panel='false'] { + position: absolute; +} + +#session-sync[panel='false'] .sessions-area .panel-resize-handle { + display: none; +} + +/* + * Only DOM nodes from here + */ + +#session-sync #overlay { + width: 100%; + height: 100%; + + background: rgba(255, 255, 255, 0.01); + + position: absolute; + top: 0; + left: 0; + z-index: 2500; + + display: none; +} + +#session-sync #overlay[state='true'] { + display: block; +} + +/* + * Panel Body + */ + +#session-sync #body { + height: 100%; +} + +#session-sync[config] .body { + background-color: rgba(255, 255, 255, 0.95); +} + +#session-sync .sessions-area { + width: 100%; + height: 100%; + + box-sizing: border-box; + + transition: all 0.2s; + + display: flex; + flex-direction: column; +} + +#session-sync[show='false'] .sessions-area { + display: none; +} + +#session-sync[state='pin'] .sessions-area { + top: 0; + right: 0; + margin: 0; + transform: translate(0); +} + +#session-sync[state='centered'] .sessions-area { + top: calc(50% - 50px); + left: 50%; + transform: translate(-50%, -50%); +} + +#session-sync[config] .sessions-area { + left: 400px; + top: 20%; + transform: translate(0); +} + +#session-sync .sessions-area-body { + width: 100%; + overflow: hidden; + + flex: 1; + display: flex; + flex-direction: row; +} + +/* + * Container resize + */ + +#session-sync[resize] .body { + background: rgba(255, 255, 255, 0.95) !important; + cursor: se-resize; +} + +#session-sync[resize] .sessions-area { + transition: none; +} + +#session-sync .sessions-area .panel-resize-handle { + content: ' '; + width: 12px; + height: 12px; + margin-top: -12px; + background: url('../images/resize.png') center no-repeat !important; + position: absolute; + background: #ddd; + bottom: 0; + left: 0px; + cursor: sw-resize; + z-index: 10; + + transform: rotate(90deg); +} + +#session-sync .sessions-area .panel-resize-handle:hover { + background: #1ab61a; +} + +#session-sync .sessions-area .panel-resize-handle[resizing] { + background: #2d92eb; +} + +#clipboard { + position: absolute; + top: -100px; + z-index: -100; +} diff --git a/dev/css/components.css b/src/assets/css/components.css similarity index 77% rename from dev/css/components.css rename to src/assets/css/components.css index 0716747..97206b9 100644 --- a/dev/css/components.css +++ b/src/assets/css/components.css @@ -6,7 +6,7 @@ * Action Button */ - #session-sync .comp-button { +#session-sync .comp-button { width: 100%; color: #333; display: flex; @@ -18,8 +18,8 @@ #session-sync .comp-button .button { text-align: center; - background: #38B751; - color: #FFF; + background: #38b751; + color: #fff; flex: 1; } @@ -43,7 +43,7 @@ #session-sync .comp-toggle-switch:hover { cursor: pointer; - color: #4C91D6; + color: #4c91d6; } #session-sync .comp-toggle-switch .label { @@ -54,14 +54,14 @@ margin-left: 0.4em; padding: 0 0 0.25em 0; - background-color: #40B740; + background-color: #40b740; background-size: 1.6em; background-repeat: no-repeat; background-position: center right 0.2em; border-radius: 2em; text-align: center; - color: #FFF; + color: #fff; float: left; flex: 1; @@ -72,7 +72,7 @@ } #session-sync .comp-toggle-switch .switch:after { - content: " "; + content: ' '; width: 1.4em; height: 1.4em; margin: 0.3em 0 0 0.3em; @@ -84,22 +84,21 @@ transition-timing-function: ease-out; } - -#session-sync .comp-toggle-switch .switch[state="true"] { - background-color: #40B740; +#session-sync .comp-toggle-switch .switch[state='true'] { + background-color: #40b740; } -#session-sync .comp-toggle-switch .switch[state="true"]:after { +#session-sync .comp-toggle-switch .switch[state='true']:after { left: calc(100% - 1.7em); margin-left: 0; } -#session-sync .comp-toggle-switch .switch[state="false"]:after { - background: #E01616; +#session-sync .comp-toggle-switch .switch[state='false']:after { + background: #e01616; left: 0; } -#session-sync .comp-toggle-switch .switch[state="false"] { +#session-sync .comp-toggle-switch .switch[state='false'] { background-color: #999; background-position: center left 0.2em; } @@ -120,29 +119,29 @@ width: 50%; height: 100%; line-height: 2.4em; - background: #F5F5F5; + background: #f5f5f5; } #session-sync .comp-dropdown .select { height: 100%; line-height: 2.4em; width: 50%; - background: #38B751; + background: #38b751; position: relative; } #session-sync .comp-dropdown .select:hover { - background: #349E47; + background: #349e47; } #session-sync .comp-dropdown .select-value { height: 100%; - color: #FFF; + color: #fff; text-align: center; } #session-sync .comp-dropdown .select-value:hover { - background: #349E47; + background: #349e47; cursor: pointer; } @@ -157,7 +156,7 @@ #session-sync .comp-dropdown .drop-list { width: 100%; min-height: 100%; - background: #FFF; + background: #fff; color: #333; border: 1px solid #208020; @@ -170,11 +169,11 @@ box-sizing: border-box; } -#session-sync .comp-dropdown .drop-list[visible="true"] { +#session-sync .comp-dropdown .drop-list[visible='true'] { display: block; } -#session-sync .comp-dropdown .drop-list[visible="false"] { +#session-sync .comp-dropdown .drop-list[visible='false'] { display: none; } @@ -187,11 +186,11 @@ #session-sync .comp-dropdown .drop-list .option:hover { transition: none; - background: #EEE; + background: #eee; } #session-sync .comp-dropdown .drop-list .option[selected] { - background: #38B751; - background: #66AAEF; - color: #FFF; + background: #38b751; + background: #66aaef; + color: #fff; } diff --git a/dev/css/config-panel.css b/src/assets/css/config-panel.css similarity index 77% rename from dev/css/config-panel.css rename to src/assets/css/config-panel.css index f193bf2..1c4d44e 100644 --- a/dev/css/config-panel.css +++ b/src/assets/css/config-panel.css @@ -6,8 +6,8 @@ width: 350px; padding: 30px 0 0 0; font-size: 12px; - border-right: 1px solid #CCC; - background: #F5F5F5; + border-right: 1px solid #ccc; + background: #f5f5f5; opacity: 0.95; color: #000; @@ -17,7 +17,7 @@ bottom: 1px; right: -355px; z-index: 2000; - box-shadow: 0 0 10px 0 #CCC; + box-shadow: 0 0 10px 0 #ccc; transition: all 0.25s ease-out; opacity: 0.5; @@ -28,7 +28,7 @@ opacity: 1; } -#session-sync[show="false"] .config-panel { +#session-sync[show='false'] .config-panel { right: -355px !important; } @@ -39,8 +39,8 @@ #session-sync .config-panel .css-close-button { width: 2.4em; height: 2.4em; - top: .5em; - right: .5em; + top: 0.5em; + right: 0.5em; opacity: 0.85 !important; transition: all 0.2s ease-out; } @@ -60,7 +60,7 @@ margin: 2em auto; padding: 0; border: none; - background: url("images/icon64.png") center top no-repeat; + background: url('../images/icon64.png') center top no-repeat; } #session-sync .config-panel .logo:hover { @@ -74,7 +74,7 @@ #session-sync .config-panel .section-container { height: auto; padding: 1em 0 0 0; - border-bottom: 1px dashed #CCC; + border-bottom: 1px dashed #ccc; border: none; position: relative; } @@ -85,7 +85,7 @@ font-weight: 600; text-align: left; color: #333; - border-bottom: 1px solid #DDD; + border-bottom: 1px solid #ddd; border-top: none; font-size: 14px; } @@ -107,13 +107,13 @@ width: 1.5em; height: 1.5em; margin: 0 0.5em; - background-image: url("images/arrow-up.png"); + background-image: url('../images/arrow-up.png'); background-size: contain; float: left; } #session-sync .config-panel .section-container[collapsed] .drop-icon { - background-image: url("images/arrow-down.png"); + background-image: url('../images/arrow-down.png'); background-size: contain; } @@ -134,8 +134,7 @@ box-sizing: border-box; } -#session-sync .config-panel .comp-range-control .label -{ +#session-sync .config-panel .comp-range-control .label { height: 100%; font-size: 1em; color: #555; @@ -148,7 +147,6 @@ box-sizing: border-box; } - #session-sync .config-panel .comp-range-control .range-input { width: 8em; margin: 0 1em 0 0; @@ -179,13 +177,12 @@ } #session-sync .config-panel .comp-range-control .button.inc { - background: url("images/arrow-right.png") center top no-repeat; + background: url('../images/arrow-right.png') center top no-repeat; background-size: 100%; } -#session-sync .config-panel .comp-range-control .button.dec -{ - background: url("images/arrow-left.png") center top no-repeat; +#session-sync .config-panel .comp-range-control .button.dec { + background: url('../images/arrow-left.png') center top no-repeat; background-size: 100%; } @@ -194,23 +191,23 @@ margin: 0 0.2em; padding: 0; - background: #FFF; - border: 1px solid #CCC; + background: #fff; + border: 1px solid #ccc; font-size: 1.1em; text-align: center; line-height: 1.8em; - color: #2D92EB; + color: #2d92eb; } /* * Toggle control */ - #session-sync .config-panel .comp-button, - #session-sync .config-panel .comp-dropdown, - #session-sync .config-panel .comp-toggle-switch, - #session-sync .config-panel .comp-toggle-button { +#session-sync .config-panel .comp-button, +#session-sync .config-panel .comp-dropdown, +#session-sync .config-panel .comp-toggle-switch, +#session-sync .config-panel .comp-toggle-button { width: 100%; height: 2em; margin: 5px 0; @@ -254,16 +251,16 @@ cursor: pointer; } -#session-sync .config-panel .comp-toggle-button .button[state="true"] { - background: #38B751; +#session-sync .config-panel .comp-toggle-button .button[state='true'] { + background: #38b751; } -#session-sync .config-panel .comp-toggle-button .button[state="false"] { - background: #66AAEF; +#session-sync .config-panel .comp-toggle-button .button[state='false'] { + background: #66aaef; } -#session-sync .config-panel .comp-toggle-button .button[state="disable"] { - background: #66AAEF; +#session-sync .config-panel .comp-toggle-button .button[state='disable'] { + background: #66aaef; } /* @@ -291,7 +288,7 @@ width: 8em; height: 100%; margin: 0 1em 0 0; - background: #38B751; + background: #38b751; border-radius: 1em; line-height: 1.9em; color: white; @@ -303,13 +300,13 @@ #session-sync .config-panel .comp-dropdown .select:hover { cursor: pointer; - background: #66AAEF; + background: #66aaef; } /* * Custom states */ -#session-sync[history="false"] .config-panel .section-container[id="history-config"] .comp-range-control { +#session-sync[history='false'] .config-panel .section-container[id='history-config'] .comp-range-control { display: none; } diff --git a/dev/css/confirm-box.css b/src/assets/css/confirm-box.css similarity index 76% rename from dev/css/confirm-box.css rename to src/assets/css/confirm-box.css index 70f9149..0716d97 100644 --- a/dev/css/confirm-box.css +++ b/src/assets/css/confirm-box.css @@ -6,8 +6,8 @@ width: auto; padding: 15px; margin: -40px 0 0 0; - background: #FFF; - border: 1px #CCC solid; + background: #fff; + border: 1px #ccc solid; display: none; position: fixed; @@ -15,7 +15,7 @@ top: 0; overflow: auto; - box-shadow: 3px 3px 5px 0 #CCC; + box-shadow: 3px 3px 5px 0 #ccc; transform: translate(-50%, 0); z-index: 1; @@ -25,7 +25,7 @@ left: 321px; */ } -#session-sync .confirm-box[data-active="true"] { +#session-sync .confirm-box[data-active='true'] { display: block; z-index: 1001; } @@ -41,7 +41,7 @@ #session-sync .confirm-box .controls { padding: 10px 0 0 0; - border-top: 1px solid #DDD; + border-top: 1px solid #ddd; overflow: hidden; display: flex; } @@ -52,16 +52,16 @@ #session-sync .confirm-box .ok { margin: 0 10px 0 0; - color: #FFF; - background: #E86A5C; /*#EA5F4D;*/ + color: #fff; + background: #e86a5c; /*#EA5F4D;*/ } #session-sync .confirm-box .ok:hover { - background: #DC462C; + background: #dc462c; } #session-sync .confirm-box .cancel { - color: #FFF; + color: #fff; background: #999; } diff --git a/src/assets/css/context-menu.css b/src/assets/css/context-menu.css new file mode 100644 index 0000000..8faa6d3 --- /dev/null +++ b/src/assets/css/context-menu.css @@ -0,0 +1,97 @@ +/** + * Context Menu + */ + +#session-sync .context-menu { + width: 180px; + padding: 5px; + background: #fff; + border: 1px #ccc solid; + + display: none; + position: fixed; + left: 0; + top: 0; + + box-shadow: 0px 0px 5px 0 #ccc; + transform: translateX(5px); + z-index: 3000; + + box-sizing: border-box; +} + +#session-sync .context-menu[data-active='true'] { + display: block; +} + +#session-sync .context-menu .separator { + width: 100%; + height: 1px; + margin: 3px 0; + background: #ccc; +} + +#session-sync .context-menu .button { + width: 100%; + padding: 5px 5px 5px 30px; + + background-image: url('../images/icons/heart.png'); + background-size: 20px; + background-repeat: no-repeat; + background-position: 3px 3px; + box-sizing: border-box; +} + +#session-sync .context-menu .button:hover { + background-color: #eee; + cursor: pointer; +} + +#session-sync .context-menu .button[icon='save'] { + background-image: url('../images/icons/heart.png'); +} + +#session-sync .context-menu .button[icon='new-tab'] { + background-image: url('../images/icons/ribbon.png'); +} + +#session-sync .context-menu .button[icon='same-tab'] { + background-image: url('../images/icons/recycle.png'); +} + +#session-sync .context-menu .button[icon='new-window'] { + background-image: url('../images/icons/frames.png'); +} + +#session-sync .context-menu .button[icon='replace-current'] { + background-image: url('../images/material_icons/repeat_one.svg'); +} + +#session-sync .context-menu .button[icon='replace-all'] { + background-image: url('../images/material_icons/repeat.svg'); +} + +#session-sync .context-menu .button[icon='copy'] { + background-image: url('../images/icons/clipboard.png'); +} + +#session-sync .context-menu .button[icon='edit'] { + background-image: url('../images/icons/document.png'); +} + +#session-sync .context-menu .button[icon='delete'] { + background-image: url('../images/icons/denied.png'); +} + +#session-sync .context-menu .button[icon='same-window'] { + background-image: url('../images/icons/stack.png'); +} + +#session-sync .context-menu .button[icon='new-session'] { + background-image: url('../images/icons/star.png'); +} + +#session-sync .context-menu[icons='no'] .button { + padding: 5px; + background-image: unset; +} diff --git a/dev/css/field-widget.css b/src/assets/css/field-widget.css similarity index 80% rename from dev/css/field-widget.css rename to src/assets/css/field-widget.css index e83be9e..f5ed17e 100644 --- a/dev/css/field-widget.css +++ b/src/assets/css/field-widget.css @@ -4,15 +4,15 @@ #session-sync .field-edit-widget { width: 100%; padding: 10px; - background: #FFF; - border: 1px solid #CCC; + background: #fff; + border: 1px solid #ccc; opacity: 0; position: absolute; box-sizing: border-box; - box-shadow: 0px 0px 5px 0px #CCC; + box-shadow: 0px 0px 5px 0px #ccc; bottom: 0; left: 0; @@ -29,7 +29,7 @@ width: 100%; height: 2.4em; margin: 3px 0; - line-height: 2.0em; + line-height: 2em; float: left; display: flex; } @@ -47,13 +47,13 @@ padding: 0 1em 0.2em 1em !important; color: #333; box-sizing: border-box; - border: 1px solid #CCC; + border: 1px solid #ccc; border-radius: 1.2em !important; flex: 1; } #session-sync .field-edit-widget input:focus { - border-color: #2D92EB !important; + border-color: #2d92eb !important; box-shadow: 0 0 3px 0 rgba(0, 131, 255, 0.5) inset !important; } @@ -65,7 +65,7 @@ font-size: 14px; text-align: center; - color: #FFF; + color: #fff; border: unset; border-radius: 1em; background: #999; @@ -75,14 +75,14 @@ } #session-sync .field-edit-widget .button:hover { - color: #FFF; + color: #fff; cursor: pointer; } #session-sync .field-edit-widget .button.save:hover { - background: #23A823; + background: #23a823; } #session-sync .field-edit-widget .button.cancel:hover { - background: #DC462C; + background: #dc462c; } diff --git a/dev/css/filter-panel.css b/src/assets/css/filter-panel.css similarity index 82% rename from dev/css/filter-panel.css rename to src/assets/css/filter-panel.css index 4a55421..dc2a9d1 100644 --- a/dev/css/filter-panel.css +++ b/src/assets/css/filter-panel.css @@ -7,8 +7,8 @@ height: 100%; min-height: 10000px; font-size: 16px; - border-right: 1px solid #DDD; - background: #F5F5F5; + border-right: 1px solid #ddd; + background: #f5f5f5; opacity: 0.95; color: #000; @@ -20,11 +20,11 @@ transition: all 0.3s; } -#session-sync[show="true"] .filter-panel { +#session-sync[show='true'] .filter-panel { left: 0px; } -#session-sync[show="false"] .filter-panel { +#session-sync[show='false'] .filter-panel { left: -151px; } @@ -36,15 +36,14 @@ margin: 10px 0; height: 64px; width: 100%; - background: url("images/icon64.png") center top no-repeat; + background: url('../images/icon64.png') center top no-repeat; } #session-sync .filter-panel .separator { margin: 5px 0; - border-bottom: 1px solid #DDD; + border-bottom: 1px solid #ddd; } - #session-sync .filter-panel .session-box { padding: 5px 0; color: #555; @@ -52,13 +51,13 @@ } #session-sync .filter-panel .session-box:hover { - background: #DDD; + background: #ddd; cursor: pointer; } #session-sync .filter-panel .session-box[active] { - background: #2D92EB; - color: #FFF; + background: #2d92eb; + color: #fff; } #session-sync .filter-panel .filters { @@ -91,10 +90,9 @@ display: block; } - #session-sync .filter-panel .label:hover { cursor: pointer; - background: #FFF; + background: #fff; transition: none; } @@ -112,7 +110,7 @@ height: 12px; margin: 3px 10px 3px 0; padding: 3px 0; - background: #DDD; + background: #ddd; border-radius: 3px; text-align: center; line-height: 100%; @@ -126,27 +124,27 @@ } #session-sync .filter-panel .label[active] { - background: #FFF; + background: #fff; } #session-sync .filter-panel .label[active] .name { - color: #4C91D6; + color: #4c91d6; font-weight: bold; } #session-sync .filter-panel .label[active] .count { - background: #23A823; - color: #FFF; + background: #23a823; + color: #fff; } #session-sync .filter-panel .label[active]:after { - content: " "; + content: ' '; width: 3px; height: 100%; position: absolute; right: 0; top: 0; - background: #1AB61A; + background: #1ab61a; } #session-sync .filter-panel .add-label { @@ -159,7 +157,6 @@ #session-sync .filter-panel .add-label:hover { cursor: pointer; - background: #2D92EB; - color: #FFF; + background: #2d92eb; + color: #fff; } - diff --git a/dev/css/generic.css b/src/assets/css/generic.css similarity index 94% rename from dev/css/generic.css rename to src/assets/css/generic.css index ad22b8a..2e24c1e 100644 --- a/dev/css/generic.css +++ b/src/assets/css/generic.css @@ -2,7 +2,6 @@ * Generic CSS */ - #session-sync .css-close-button { width: 2em; height: 2em; @@ -27,10 +26,10 @@ #session-sync .css-close-button:after, #session-sync .css-close-button:before { - content: ""; + content: ''; width: 60%; height: 16%; - background: #AAA; + background: #aaa; position: absolute; top: 42%; left: 20%; @@ -39,4 +38,4 @@ #session-sync .css-close-button:after { transform: rotate(-45deg); -} \ No newline at end of file +} diff --git a/dev/css/hotkey-manager.css b/src/assets/css/hotkey-manager.css similarity index 88% rename from dev/css/hotkey-manager.css rename to src/assets/css/hotkey-manager.css index 22138e5..484ad9f 100644 --- a/dev/css/hotkey-manager.css +++ b/src/assets/css/hotkey-manager.css @@ -2,25 +2,25 @@ * Session Hotkey Manager */ - #session-sync .hotkey-overlay { +#session-sync .hotkey-overlay { position: absolute; background: rgba(0, 0, 0, 0.85); width: 100%; height: 100%; z-index: 3000; display: none; - } +} - #session-sync .hotkey-overlay[state="true"] { +#session-sync .hotkey-overlay[state='true'] { display: block; - } +} #session-sync .hotkey-manager { width: 300px; padding: 10px; font-size: 12px; - border: 1px solid #CCC; - background: #FFF; + border: 1px solid #ccc; + background: #fff; color: #000; position: absolute; @@ -38,7 +38,7 @@ margin: 0 0 0.5em 0; font-size: 1.4em; text-align: center; - background: #F5F5F5; + background: #f5f5f5; color: #555; } @@ -47,13 +47,13 @@ } #session-sync .hotkey-manager .info-box .message { - color: #DC462C; + color: #dc462c; text-align: center; font-weight: bold; } #session-sync .hotkey-manager .info-box .link { - color: #509AE5; + color: #509ae5; text-align: center; font-weight: bold; } @@ -65,7 +65,7 @@ #session-sync .hotkey-manager .hotkey-info { width: 100%; margin: 10px auto; - border: 1px solid #DDD; + border: 1px solid #ddd; text-align: center; color: #555; @@ -80,9 +80,9 @@ #session-sync .hotkey-manager .hotkey-info .title { width: 30%; - background: #38B751; + background: #38b751; background: #999; - color: #FFF; + color: #fff; } #session-sync .hotkey-manager .hotkey-info .value { @@ -142,16 +142,16 @@ #session-sync .hotkey-manager .action-bar .ok { margin: 0 10px 0 0; - color: #FFF; - background: #E86A5C; /*#EA5F4D;*/ + color: #fff; + background: #e86a5c; /*#EA5F4D;*/ } #session-sync .hotkey-manager .action-bar .ok:hover { - background: #DC462C; + background: #dc462c; } #session-sync .hotkey-manager .action-bar .cancel { - color: #FFF; + color: #fff; background: #999; } @@ -170,4 +170,4 @@ #session-sync .hotkey-manager .action-bar .button:hover { cursor: pointer; -} \ No newline at end of file +} diff --git a/dev/css/notification-system.css b/src/assets/css/notification-system.css similarity index 75% rename from dev/css/notification-system.css rename to src/assets/css/notification-system.css index 311e7b7..eed05e0 100644 --- a/dev/css/notification-system.css +++ b/src/assets/css/notification-system.css @@ -14,13 +14,12 @@ transform: translateX(-50%); } -#session-sync .notification-system .info -{ +#session-sync .notification-system .info { width: 100%; padding: 0.5em 2em; padding-left: 3.5em; - background: #2D92EB; - color: #FFF; + background: #2d92eb; + color: #fff; /* background: #F5F5F5; */ /* color: #333; */ word-break: break-all; @@ -35,21 +34,20 @@ transform: translateX(0); } -#session-sync .notification-system .info[active] -{ +#session-sync .notification-system .info[active] { opacity: 1; top: -1px; - border: 1px solid #CCC; + border: 1px solid #ccc; } #session-sync .notification-system .info:after { - content: " "; + content: ' '; width: 24px; height: 24px; - background: url("images/logo.png") center no-repeat; + background: url('../images/logo.png') center no-repeat; background-size: contain; top: 50%; left: 20px; position: absolute; transform: translateY(-50%); -} \ No newline at end of file +} diff --git a/dev/css/search-bar.css b/src/assets/css/search-bar.css similarity index 73% rename from dev/css/search-bar.css rename to src/assets/css/search-bar.css index d16b13f..c360454 100644 --- a/dev/css/search-bar.css +++ b/src/assets/css/search-bar.css @@ -1,143 +1,143 @@ -/** - * Control Panel - */ - -#session-sync .search-bar { - width: 5em; - height: 1.5em; - margin: 0; - padding: 0; - - background: hsla(0, 0%, 100%, 0.5); - border-radius: 1.5em; - - overflow: hidden; - opacity: 1; - - line-height: 1em; - - position: absolute; - top: 0.5em; - left: 0.5em; - z-index: 2; - - transition: all 0.2s ease-out; -} - -#session-sync .search-bar[content="false"] { - width: 5em !important; -} - -#session-sync .search-bar[content="false"] .search { - text-align: center; -} - -#session-sync .search-bar[active="true"] { - width: 25%; - opacity: 1; -} - -#session-sync .search-bar:hover { - opacity: 1; -} - -#session-sync .search-bar .search { - width: 100%; - height: 100%; - padding: 0 0.75em !important; - background: hsla(0, 0%, 100%, 0.95); - border: 1px solid #CCC; - border-radius: 1em; - color: #333; - font-size: 0.75em; - - box-sizing: border-box; - transition: all 0.2s ease-out; -} - -#session-sync .search-bar .search:focus { - border-color: #2D92EB !important; -} - -#session-sync .search-bar[match="true"][content="true"] .search { - color: green; -} - -#session-sync .search-bar .create-session { - width: 1em; - height: 1em; - background-image: url("images/icons/star.png"); - background-size: 100%; - background-repeat: no-repeat; - background-position: center; - text-align: center; - color: #FFF; - opacity: 0; - - position: absolute; - top: 0.25em; - right: -2.5em; - z-index: 0; - - filter: saturate(0%); - transition: all 0.2s ease-out; - box-sizing: border-box; -} - -#session-sync .search-bar[match="false"][content="true"] .create-session { - opacity: 0.6; - display: block; - right: 1.6em; -} - -#session-sync .search-bar .create-session:hover { - filter: saturate(100%); - cursor: pointer; - opacity: 1; -} - -#session-sync .search-bar .close { - width: 1.2em; - height: 1.2em; - background: #FFF; - border-radius: 2em; - opacity: 1; - - position: absolute; - top: 0.15em; - right: -2em; - z-index: 0; - - box-sizing: border-box; - transition: opacity 0.2s ease-out; -} - -#session-sync .search-bar[active="true"][content="true"] .close { - opacity: 0.75; - right: 0.3em; -} - -#session-sync .search-bar .close:hover { - cursor: pointer; -} - -#session-sync .search-bar .close:hover::after, -#session-sync .search-bar .close:hover::before { - background: #333; -} - -#session-sync .search-bar .close:after, -#session-sync .search-bar .close:before { - content: ""; - width: 60%; - height: 16%; - background: #AAA; - position: absolute; - top: 42%; - left: 20%; - transform: rotate(45deg); -} - -#session-sync .search-bar .close:after { - transform: rotate(-45deg); -} +/** + * Control Panel + */ + +#session-sync .search-bar { + width: 5em; + height: 1.5em; + margin: 0; + padding: 0; + + background: hsla(0, 0%, 100%, 0.5); + border-radius: 1.5em; + + overflow: hidden; + opacity: 1; + + line-height: 1em; + + position: absolute; + top: 0.5em; + left: 0.5em; + z-index: 2; + + transition: all 0.2s ease-out; +} + +#session-sync .search-bar[content='false'] { + width: 5em !important; +} + +#session-sync .search-bar[content='false'] .search { + text-align: center; +} + +#session-sync .search-bar[active='true'] { + width: 25%; + opacity: 1; +} + +#session-sync .search-bar:hover { + opacity: 1; +} + +#session-sync .search-bar .search { + width: 100%; + height: 100%; + padding: 0 0.75em !important; + background: hsla(0, 0%, 100%, 0.95); + border: 1px solid #ccc; + border-radius: 1em; + color: #333; + font-size: 0.75em; + + box-sizing: border-box; + transition: all 0.2s ease-out; +} + +#session-sync .search-bar .search:focus { + border-color: #2d92eb !important; +} + +#session-sync .search-bar[match='true'][content='true'] .search { + color: green; +} + +#session-sync .search-bar .create-session { + width: 1em; + height: 1em; + background-image: url('../images/icons/star.png'); + background-size: 100%; + background-repeat: no-repeat; + background-position: center; + text-align: center; + color: #fff; + opacity: 0; + + position: absolute; + top: 0.25em; + right: -2.5em; + z-index: 0; + + filter: saturate(0%); + transition: all 0.2s ease-out; + box-sizing: border-box; +} + +#session-sync .search-bar[match='false'][content='true'] .create-session { + opacity: 0.6; + display: block; + right: 1.6em; +} + +#session-sync .search-bar .create-session:hover { + filter: saturate(100%); + cursor: pointer; + opacity: 1; +} + +#session-sync .search-bar .close { + width: 1.2em; + height: 1.2em; + background: #fff; + border-radius: 2em; + opacity: 1; + + position: absolute; + top: 0.15em; + right: -2em; + z-index: 0; + + box-sizing: border-box; + transition: opacity 0.2s ease-out; +} + +#session-sync .search-bar[active='true'][content='true'] .close { + opacity: 0.75; + right: 0.3em; +} + +#session-sync .search-bar .close:hover { + cursor: pointer; +} + +#session-sync .search-bar .close:hover::after, +#session-sync .search-bar .close:hover::before { + background: #333; +} + +#session-sync .search-bar .close:after, +#session-sync .search-bar .close:before { + content: ''; + width: 60%; + height: 16%; + background: #aaa; + position: absolute; + top: 42%; + left: 20%; + transform: rotate(45deg); +} + +#session-sync .search-bar .close:after { + transform: rotate(-45deg); +} diff --git a/src/assets/css/session-container.css b/src/assets/css/session-container.css new file mode 100644 index 0000000..488041c --- /dev/null +++ b/src/assets/css/session-container.css @@ -0,0 +1,357 @@ +/** + * Display info about a Session + */ + +#session-sync .session-container { + height: 100%; + background: #fefefe; + + font-size: 14px; + float: right; + overflow: hidden; + + /*CSS3*/ + box-sizing: border-box; + position: relative; + + display: flex; + flex-direction: column; + flex: 1; +} + +/* + * Save button - not used for the moment + */ + +#session-sync[session] .session-container .save-session { + display: none; +} + +#session-sync .session-container .save-session { + height: 3.4em; + width: 3.4em; + + background: url('../images/icons/heart.png') no-repeat; + background-size: 2em; + background-position: 1em 0.4em; + background-color: #f5f5f5; + + border-radius: 0 0 0 100%; + border-left: 1px solid #ccc; + border-bottom: 1px solid #ccc; + + position: absolute; + z-index: 1; + top: 0; + right: -1px; + + filter: saturate(75%); +} + +#session-sync .session-container .save-session:hover { + cursor: pointer; + background-color: #fff; + filter: saturate(100%); +} + +/* + * Style Tabs + */ + +#session-sync .session-container .bookmarks { + width: calc(100% + 10px); + margin: 2px 0 2px 2px; + + overflow-x: hidden; + overflow-y: scroll; + position: relative; + + /*CSS3*/ + box-sizing: border-box; + flex: 1; +} + +/* When scrollbar is enabled, use standard width to show it */ +#session-sync[scrollbar="wide"] .session-container .bookmarks { + width: 100%; +} + +#session-sync .session-container .window-separator { + width: 100%; + height: 2em; + background: #f8f8f8; + color: #555; + + line-height: 1.9em; + position: absolute; + transition: all 0.2s; +} + +#session-sync .session-container .window-separator .title { + text-align: center; + font-weight: 600; +} + +#session-sync .session-container .window-separator:hover { + cursor: pointer; + text-decoration: underline; + color: #333; +} + +#session-sync .session-container .window-separator:hover:after { + content: '(switch to)'; + position: absolute; + font-size: 0.8em; + top: 0; + margin-left: 40px; + left: 50%; +} + +#session-sync .session-container .bookmark { + width: 100%; + height: 2em; + padding: 0 0.4em; + + line-height: 1.9em; + word-break: keep-all; + + overflow: hidden; + position: absolute; + left: 0; + top: 0; + + /*CSS3*/ + box-sizing: border-box; + transition-property: left, top, background, color; + transition-duration: 0.2s; + display: flex; + flex: 1; +} + +#session-sync .session-container .bookmark:hover { + background: #eee; + cursor: pointer; + transition-property: left, top; +} + +#session-sync .session-container .bookmark[highlight] { + transition-duration: 0.5s; + background: #4396e9 !important; + color: #fff; +} + +#session-sync .session-container .bookmark[dragging] { + width: 400px; + margin: 1px 0 0 1px; + background-color: #fbd4b0; + background: #4396e9 !important; + color: #fff; + opacity: 0.9; + + transition: none; + position: fixed; + z-index: 1000; +} + +#session-sync .session-container .bookmark[dragging='drop'] { + background: #4396e9 !important; + color: #fff; + opacity: 0.9; + transition: none; + position: absolute; + + z-index: 1000; +} + +#session-sync .session-container .bookmark[dragging]:hover { + cursor: default; +} + +/* + * Bookmark Properties + */ + +#session-sync .session-container .bookmark * { + float: left; +} + +#session-sync .session-container .bookmark .favicon { + width: 1.33em; + height: 100%; + background: url('../images/icons/globe.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center center; +} + +#session-sync .session-container .bookmark .title { + height: 100%; + padding: 0 0.5em; + word-break: keep-all; + white-space: nowrap; + overflow: hidden; + + box-sizing: border-box; + flex: 1; +} + +#session-sync .session-container .bookmark .properties { + height: 1.6em; + margin-top: 0.2em; +} + +#session-sync .session-container .bookmark .properties > div:not(:last-child) { + margin-right: 0.5em; +} + +/* Bookmark Pin */ + +#session-sync .session-container .bookmark .pin { + width: 1.5em; + height: 100%; + background-image: url('../images/icons/pin.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center center; + color: #fff; +} + +/* Bookmark Container */ + +#session-sync .session-container .bookmark .container { + height: 100%; + max-width: 10em; + border-radius: 1em; + color: #fff; + padding: 0 0.5em 0 0.5em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + box-sizing: border-box; +} + +#session-sync .session-container .bookmark .container .name { + height: 100%; + padding: 0 0.5em 0 0.1em; + line-height: 1.5em; +} + +#session-sync .session-container .bookmark .container .icon { + width: 1.6em; + height: 100%; + + background-size: 1.25em !important; + background-repeat: no-repeat !important; + background-position: center center !important; +} + +/* Tab Groups */ + +#session-sync .session-container .bookmark .tab-group-tag { + height: 100%; + max-width: 12em; + padding: 0 0.6em; + + /* padding: 0 0.6em 0 1.8em; + background-image: url('../images/material_icons/tab_group.svg'); + background-size: 1em; + background-repeat: no-repeat; + background-position: 0.4em center; */ + + color: #fff; + white-space: nowrap; + text-overflow: ellipsis; + font-weight: 600; + font-size: 0.9em; + overflow: hidden; + + border-radius: 6px; + border: 1px solid rgba(255, 255, 255, 0.2); + + box-sizing: border-box; + display: flex; +} + +#session-sync .session-container .bookmark .tab-group-tag[color='grey'], +#session-sync .session-container .bookmark .tab-group-tag[color='gray'] { + background-color: #5f636810; + color: #5f6368; + border: 1px solid #5f6368; +} + +#session-sync .session-container .bookmark .tab-group-tag[color='blue'] { + background-color: #1a73e810; + color: #1a73e8; + border: 1px solid #1a73e8; +} + +#session-sync .session-container .bookmark .tab-group-tag[color='red'] { + background-color: #d9302510; + color: #d93025; + border: 1px solid #d93025; +} + +#session-sync .session-container .bookmark .tab-group-tag[color='yellow'] { + background-color: #c4821010; + color: #c48210; + border: 1px solid #c48210; +} + +#session-sync .session-container .bookmark .tab-group-tag[color='green'] { + background-color: #1e8e3e10; + color: #1e8e3e; + border: 1px solid #1e8e3e; +} + +#session-sync .session-container .bookmark .tab-group-tag[color='pink'] { + background-color: #d0188410; + color: #d01884; + border: 1px solid #d01884; +} + +#session-sync .session-container .bookmark .tab-group-tag[color='purple'] { + background-color: #9334e610; + color: #9334e6; + border: 1px solid #9334e6; +} + +#session-sync .session-container .bookmark .tab-group-tag[color='cyan'] { + background-color: #007b8310; + color: #007b83; + border: 1px solid #007b83; +} + +#session-sync .session-container .bookmark .tab-group-tag[color='orange'] { + background-color: #bb4b0a10; + color: #bb4b0a; + border: 1px solid #bb4b0a; +} + +#session-sync .session-container .bookmark .tab-group-tag .name { + line-height: 1.5em; +} + +/* + * History Style + */ + +#session-sync .session-container .history-window-separator { + width: 100%; + height: 2em; + line-height: 1.9em; + background: #f8f8f8; + color: #555; + position: absolute; + transition: all 0.2s; +} + +#session-sync .session-container .history-window-separator .title { + text-align: center; +} + +#session-sync .session-container .history-window-separator:hover { + cursor: pointer; + text-decoration: underline; + color: #333; +} diff --git a/dev/css/session-header-bar.css b/src/assets/css/session-header-bar.css similarity index 81% rename from dev/css/session-header-bar.css rename to src/assets/css/session-header-bar.css index cc4d254..613d798 100644 --- a/dev/css/session-header-bar.css +++ b/src/assets/css/session-header-bar.css @@ -6,8 +6,8 @@ width: 100%; height: 2.5em; - background: #F5F5F5; - border-bottom: 1px solid #DDD; + background: #f5f5f5; + border-bottom: 1px solid #ddd; line-height: 2.4em; font-size: 18px; position: relative; @@ -21,7 +21,7 @@ #session-sync .header-bar .active-session-btn { width: 2.5em; - background: url("images/icon128.png") no-repeat center; + background: url('../images/icon128.png') no-repeat center; background-size: 1em; height: 60%; opacity: 0.5; @@ -35,7 +35,7 @@ display: none; } -#session-sync[session="current"] .header-bar .active-session-btn, +#session-sync[session='current'] .header-bar .active-session-btn, #session-sync .header-bar .active-session-btn:hover { background-size: 1.2em; cursor: pointer; @@ -65,7 +65,7 @@ #session-sync .header-bar .addon-config { width: 1.8em; height: 1.8em; - background-image: url("images/menu.png"); + background-image: url('../images/menu.png'); background-size: 55%; background-repeat: no-repeat; background-position: center center; @@ -81,8 +81,8 @@ #session-sync .header-bar .addon-config:hover { cursor: pointer; - background-color: #EEE; - border: 1px solid #CCC; + background-color: #eee; + border: 1px solid #ccc; opacity: 1; } @@ -93,7 +93,7 @@ #session-sync .header-bar .header-menu { width: 1.8em; height: 1.8em; - background-image: url("images/icons_v2/menu.png"); + background-image: url('../images/icons_v2/menu.png'); background-size: 75% 80%; background-repeat: no-repeat; background-position: center center; @@ -106,15 +106,14 @@ box-sizing: border-box; } - #session-sync .header-bar .header-menu:hover { cursor: pointer; - background-color: #EEE; - border: 1px solid #CCC; + background-color: #eee; + border: 1px solid #ccc; opacity: 1; } -#session-sync .header-bar .header-menu[active="1"] { +#session-sync .header-bar .header-menu[active='1'] { opacity: 1; } @@ -122,7 +121,7 @@ height: 0; margin: 0; padding: 0; - background: #FFF; + background: #fff; font-size: 0.75em; border: 1px solid rgba(0, 0, 0, 0); position: absolute; @@ -140,10 +139,10 @@ transition-timing-function: ease-out; } -#session-sync .header-bar .header-menu[active="1"] .menu-area { +#session-sync .header-bar .header-menu[active='1'] .menu-area { height: auto; padding: 1em; - border: 1px solid #CCC; + border: 1px solid #ccc; opacity: 1; } @@ -161,7 +160,7 @@ height: 3.5em; margin: 0 0 0 0.6em; padding: 0.5em 0; - border: 1px solid #FFF; + border: 1px solid #fff; box-sizing: content-box; flex: 1; @@ -173,13 +172,13 @@ #session-sync .header-bar .header-menu .menu-button:hover { cursor: pointer; - background-color: #F5F5F5; - border-color: #DDD; + background-color: #f5f5f5; + border-color: #ddd; } #session-sync .header-bar .header-menu .menu-button .icon { height: 2em; - background-image: url("images/icons/power.png"); + background-image: url('../images/icons/power.png'); background-size: contain !important; background-repeat: no-repeat; background-position: center; diff --git a/dev/css/session-sorting.css b/src/assets/css/session-sorting.css similarity index 79% rename from dev/css/session-sorting.css rename to src/assets/css/session-sorting.css index 827ca58..29a6761 100644 --- a/dev/css/session-sorting.css +++ b/src/assets/css/session-sorting.css @@ -8,7 +8,7 @@ padding: 0; display: flex; - background-image: url("images/sort.png"); + background-image: url('../images/sort.png'); background-size: 20px; background-repeat: no-repeat; background-position: center center; @@ -22,7 +22,7 @@ box-sizing: border-box; } -#session-sync[list="history"] .sorting-method { +#session-sync[list='history'] .sorting-method { display: none; } @@ -31,13 +31,13 @@ opacity: 1; } -#session-sync .sorting-method[active="true"] { +#session-sync .sorting-method[active='true'] { opacity: 1; } -#session-sync .sorting-method[active="true"] .dropdown { +#session-sync .sorting-method[active='true'] .dropdown { display: block; - background: #FFF; + background: #fff; } #session-sync .sorting-method .dropdown { @@ -61,9 +61,9 @@ #session-sync .sorting-method .dropdown .option-list { width: 100%; - border: 1px solid #CCC; + border: 1px solid #ccc; border-bottom: none; - background: #FFF; + background: #fff; position: absolute; bottom: 0; @@ -79,18 +79,16 @@ box-sizing: border-box; transition-property: left, top, color, background; transition-duration: 0.2s; - } #session-sync .sorting-method .dropdown .option-list .option:hover { - background: #EEE; - border-color: #CCC; + background: #eee; + border-color: #ccc; cursor: pointer; transition-property: left, top; } #session-sync .sorting-method .dropdown .option-list .option[active] { - color: #FFF; - background: #2D92EB; + color: #fff; + background: #2d92eb; } - diff --git a/dev/css/session-toolbar.css b/src/assets/css/session-toolbar.css similarity index 62% rename from dev/css/session-toolbar.css rename to src/assets/css/session-toolbar.css index e63484c..e5a1305 100644 --- a/dev/css/session-toolbar.css +++ b/src/assets/css/session-toolbar.css @@ -1,265 +1,272 @@ -/** - * Control Panel - */ - -#session-sync .session-toolbar { - width: 100%; - height: 3em; - - background: #FFF; - color: #000; - display: flex; - font-size: 12px; - - position: relative; - opacity: 100; - border-bottom: 1px solid #CCC; - box-shadow: 0px 0px 3px 0px #CCC; - z-index: 1; - - transition: all 0.15s; -} - -#session-sync .session-toolbar .session-selector { - width: 220px; - min-width: 160px; - max-width: 60%; - height: 100%; - margin: 0; - padding: 0; - border-right: 1px solid #DDD; - - line-height: 3em; - word-break: break-all; - text-align: center; - color: #555; - - display: flex; - box-sizing: border-box; -} - -#session-sync .session-toolbar .session-selector .button { - text-align: center; - overflow: hidden; - display: block; - color: #333; - flex: 1; -} - -#session-sync .session-toolbar .session-selector .button:hover { - cursor: pointer; -} - -#session-sync .session-toolbar .session-selector .active-session { - background-image: url("images/icon128.png"); - background-repeat: no-repeat; - background-position: center center; - background-size: 1.8em; - background-color: #FFF; -} - -#session-sync[list="active"] .session-toolbar .session-selector .active-session, -#session-sync[list="sync"] .session-toolbar .session-selector .sync, -#session-sync[list="history"] .session-toolbar .session-selector .history { - display: block; - background-color: #2D92EB; - color: #FFF; -} - -#session-sync[history="false"] .session-toolbar .session-selector .history { - display: none; -} - -/* - * Session info - */ - -#session-sync:not([session="restore"]) .session-toolbar .session-date { - display: none; -} - -#session-sync .session-toolbar .session-date { - padding: 0.9em 1em; - color: #999; - text-align: right; - display: block; - position: absolute; - top: -1px; - right: 0; - z-index: 10; -} - -/** - * Menu Section - */ - -#session-sync .session-toolbar .menu-bar { - height: 100%; - padding: 0.5em; - - box-sizing: border-box; - flex: 1; -} - -#session-sync .session-toolbar .menu-bar .button { - width: 2.6em; - height: 100%; - - background: url("images/icons/none.png") no-repeat center; - background-size: contain !important; - - display: none; - float: left; - - filter: saturate(100%); -} - -#session-sync .session-toolbar .menu-bar .button:hover { - cursor: pointer; - filter: saturate(100%); - box-shadow: none; -} - -#session-sync .session-toolbar .menu-bar .separator { - height: 100%; - width: 1px; - margin: 0 0.5em; - background: #CCC; - float: left; - display: none; -} - -#session-sync .session-toolbar .menu-bar .add { - background: url("images/icons/star.png") no-repeat center; - filter: saturate(100%); -} - -#session-sync .session-toolbar .menu-bar .save { - background: url("images/icons/heart.png") no-repeat center; - filter: saturate(100%); -} - -#session-sync .session-toolbar .menu-bar .merge-sessions { - background: url("images/icons/merge.png") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .replace-session { - background: url("images/icons/recycle.png") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .restore { - background: url("images/icons/stack.png") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .restore-new-win { - background: url("images/icons/frames.png") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .repalce-session-all { - background: url("images/material_icons/repeat.svg") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .repalce-session { - background: url("images/material_icons/repeat_one.svg") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .fix-session { - background: url("images/icons/tools.png") no-repeat center; -} - -#session-sync .session-toolbar .menu-bar .customize-toolbar { - background: url("images/icons_v2/more.png") no-repeat center; -} - -/* - * Menu state - */ - - #session-sync[session="current"] .session-toolbar .menu-bar .button[state-current="true"] { - display: block; -} - -#session-sync[session="restore"] .session-toolbar .menu-bar .button[state-restore="true"] { - display: block; -} - -#session-sync[session="restore"] .session-toolbar .menu-bar .separator { - display: block; -} - -#session-sync[session="history"] .session-toolbar .menu-bar .button[state-history="true"] { - display: block; -} - -/* - * Save configuration - */ - -#session-sync .session-toolbar .menu-bar .save-config { - height: 100%; - padding: 0 40px 0 0; - display: block; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch { - opacity: 1; - transition: opacity 0.2s ease-out; -} - -#session-sync .session-toolbar .menu-bar:hover .save-config .comp-toggle-switch { - opacity: 1; -} - -#session-sync[session="restore"] .session-toolbar .menu-bar .save-config { - display: none; -} - -#session-sync[session="history"] .session-toolbar .menu-bar .save-config .comp-toggle-switch[windows] { - display: none; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch { - height: 100%; - width: 4em; - margin: 0; - float: right; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .label { - display: none; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .switch { - width: 2.6em; - height: 100%; - background-color: #40B740; - background-image: url("images/icons/gear.png"); - background-size: 1.6em; - background-repeat: no-repeat; - background-position: center right 0.2em; - border-radius: 2em; - display: block; - - box-sizing: border-box; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .switch:after { - display: none; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .switch[state="false"] { - background-color: #BBB; - background-position: center left 0.2em; -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch[pin] .switch { - background-image: url("images/icons/pin.png"); -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch[pin-state] .switch { - background-image: url("images/icons/pin2.png"); -} - -#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch[windows] .switch { - background-image: url("images/icons/stack.png"); -} +/** + * Control Panel + */ + +#session-sync .session-toolbar { + width: 100%; + height: 3em; + + background: #fff; + color: #000; + display: flex; + font-size: 12px; + + position: relative; + opacity: 100; + border-bottom: 1px solid #ccc; + box-shadow: 0px 0px 3px 0px #ccc; + z-index: 1; + + transition: all 0.15s; +} + +#session-sync .session-toolbar .session-selector { + width: 220px; + min-width: 160px; + max-width: 60%; + height: 100%; + margin: 0; + padding: 0; + border-right: 1px solid #ddd; + + line-height: 3em; + word-break: break-all; + text-align: center; + color: #555; + + display: flex; + box-sizing: border-box; +} + +#session-sync .session-toolbar .session-selector .button { + text-align: center; + overflow: hidden; + display: block; + color: #333; + flex: 1; +} + +#session-sync .session-toolbar .session-selector .button:hover { + cursor: pointer; +} + +#session-sync .session-toolbar .session-selector .active-session { + background-image: url('../images/icon128.png'); + background-repeat: no-repeat; + background-position: center center; + background-size: 1.8em; + background-color: #fff; +} + +#session-sync[list='active'] .session-toolbar .session-selector .active-session, +#session-sync[list='sync'] .session-toolbar .session-selector .sync, +#session-sync[list='history'] .session-toolbar .session-selector .history { + display: block; + background-color: #2d92eb; + color: #fff; +} + +#session-sync[history='false'] .session-toolbar .session-selector .history { + display: none; +} + +/* + * Session info + */ + +#session-sync:not([session='restore']) .session-toolbar .session-date { + display: none; +} + +#session-sync .session-toolbar .session-date { + padding: 0.9em 1em; + color: #999; + text-align: right; + display: block; + position: absolute; + top: -1px; + right: 0; + z-index: 10; +} + +/** + * Menu Section + */ + +#session-sync .session-toolbar .menu-bar { + height: 100%; + padding: 0.5em; + + box-sizing: border-box; + flex: 1; +} + +#session-sync .session-toolbar[session-type='separator'] .menu-bar { + display: none; +} + +#session-sync[session='current'] .session-toolbar .menu-bar { + display: block; +} + +#session-sync .session-toolbar .menu-bar .button { + width: 2.6em; + height: 100%; + + background: url('../images/icons/none.png') no-repeat center; + background-size: contain !important; + + display: none; + float: left; + + /* opacity: 0.75; + filter: grayscale(100%); */ +} + +#session-sync .session-toolbar .menu-bar .button:hover { + cursor: pointer; + opacity: 1; + box-shadow: none; +} + +#session-sync .session-toolbar .menu-bar .separator { + height: 100%; + width: 1px; + margin: 0 0.5em; + background: #ccc; + float: left; + display: none; +} + +#session-sync .session-toolbar .menu-bar .add { + background: url('../images/icons/star.png') no-repeat center; +} + +#session-sync .session-toolbar .menu-bar .save { + background: url('../images/icons/heart.png') no-repeat center; +} + +#session-sync .session-toolbar .menu-bar .merge-sessions { + background: url('../images/icons/merge.png') no-repeat center; +} + +#session-sync .session-toolbar .menu-bar .replace-session { + background: url('../images/icons/recycle.png') no-repeat center; +} + +#session-sync .session-toolbar .menu-bar .restore { + background: url('../images/icons/stack.png') no-repeat center; +} + +#session-sync .session-toolbar .menu-bar .restore-new-win { + background: url('../images/icons/frames.png') no-repeat center; +} + +#session-sync .session-toolbar .menu-bar .repalce-session-all { + background: url('../images/material_icons/repeat.svg') no-repeat center; +} + +#session-sync .session-toolbar .menu-bar .repalce-session { + background: url('../images/material_icons/repeat_one.svg') no-repeat center; +} + +#session-sync .session-toolbar .menu-bar .fix-session { + background: url('../images/icons/tools.png') no-repeat center; +} + +#session-sync .session-toolbar .menu-bar .customize-toolbar { + background: url('../images/icons_v2/more.png') no-repeat center; +} + +/* + * Menu state + */ + +#session-sync[session='current'] .session-toolbar .menu-bar .button[state-current='true'] { + display: block; +} + +#session-sync[session='restore'] .session-toolbar .menu-bar .button[state-restore='true'] { + display: block; +} + +#session-sync[session='restore'] .session-toolbar .menu-bar .separator { + display: block; +} + +#session-sync[session='history'] .session-toolbar .menu-bar .button[state-history='true'] { + display: block; +} + +/* + * Save configuration + */ + +#session-sync .session-toolbar .menu-bar .save-config { + height: 100%; + padding: 0 40px 0 0; + display: block; +} + +#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch { + opacity: 1; + transition: opacity 0.2s ease-out; +} + +#session-sync .session-toolbar .menu-bar:hover .save-config .comp-toggle-switch { + opacity: 1; +} + +#session-sync[session='restore'] .session-toolbar .menu-bar .save-config { + display: none; +} + +#session-sync[session='history'] .session-toolbar .menu-bar .save-config .comp-toggle-switch[windows] { + display: none; +} + +#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch { + height: 100%; + width: 4em; + margin: 0; + float: right; +} + +#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .label { + display: none; +} + +#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .switch { + width: 2.6em; + height: 100%; + background-color: #40b740; + background-image: url('../images/icons/gear.png'); + background-size: 1.6em; + background-repeat: no-repeat; + background-position: center right 0.2em; + border-radius: 2em; + display: block; + + box-sizing: border-box; +} + +#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .switch:after { + display: none; +} + +#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch .switch[state='false'] { + background-color: #bbb; + background-position: center left 0.2em; +} + +#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch[pin] .switch { + background-image: url('../images/icons/pin.png'); +} + +#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch[pin-state] .switch { + background-image: url('../images/icons/pin2.png'); +} + +#session-sync .session-toolbar .menu-bar .save-config .comp-toggle-switch[windows] .switch { + background-image: url('../images/icons/stack.png'); +} diff --git a/dev/css/sessions-list.css b/src/assets/css/sessions-list.css similarity index 67% rename from dev/css/sessions-list.css rename to src/assets/css/sessions-list.css index 849586f..d3a015e 100644 --- a/dev/css/sessions-list.css +++ b/src/assets/css/sessions-list.css @@ -11,9 +11,9 @@ border-width: 0 1px 0 0; border-style: solid; - border-color: #CCC; + border-color: #ccc; background: hsla(0, 0%, 100%, 0.95); - background: #FEFEFE; + background: #fefefe; float: left; overflow-x: hidden; @@ -30,14 +30,13 @@ * List resize */ -#session-sync .sessions .resize-handle -{ - content: " "; +#session-sync .sessions .resize-handle { + content: ' '; width: 4px; height: 24px; margin-top: -12px; position: absolute; - background: #DDD; + background: #ddd; top: 50%; border: unset; right: 0px; @@ -45,21 +44,19 @@ z-index: 10; } -#session-sync .sessions .resize-handle:hover -{ - background: #1AB61A; +#session-sync .sessions .resize-handle:hover { + background: #1ab61a; } -#session-sync .sessions .resize-handle[resizing] -{ - background: #2D92EB !important; +#session-sync .sessions .resize-handle[resizing] { + background: #2d92eb !important; } /* * History list */ -#session-sync[list="history"] .sessions .history-list { +#session-sync[list='history'] .sessions .history-list { flex: 1; } @@ -78,6 +75,12 @@ flex: 0; } +/* When scrollbar is enabled, use standard width to show it */ +#session-sync[scrollbar="wide"] .sessions .history-list, +#session-sync[scrollbar="wide"] .sessions .session-list { + width: 100%; +} + #session-sync .sessions .history-node { width: 100%; height: 2.5em; @@ -95,21 +98,21 @@ } #session-sync .sessions .history-node:hover { - background: #EEE; + background: #eee; cursor: pointer; transition-property: left, top; } -#session-sync .sessions .history-node[active="true"] { - background: #2D92EB; - color: #FFF; +#session-sync .sessions .history-node[active='true'] { + background: #2d92eb; + color: #fff; } /* * Session list */ -#session-sync[list="history"] .sessions .session-list { +#session-sync[list='history'] .sessions .session-list { flex: 0; } @@ -150,7 +153,7 @@ } #session-sync .sessions .folder:hover { - background: #EEE; + background: #eee; cursor: pointer; transition-property: left, top; } @@ -158,8 +161,8 @@ #session-sync .sessions .folder[dragging] { width: 200px; margin: 1px 0 0 1px; - background: #4396E9 !important; - color: #FFF; + background: #4396e9 !important; + color: #fff; opacity: 0.9; transition: none; @@ -171,9 +174,9 @@ cursor: default; } -#session-sync .sessions .folder[dragging="drop"] { - background: #4396E9 !important; - color: #FFF; +#session-sync .sessions .folder[dragging='drop'] { + background: #4396e9 !important; + color: #fff; opacity: 0.9; transition: none; position: absolute; @@ -181,9 +184,9 @@ z-index: 1000; } -#session-sync .sessions .folder[active="true"] { - background: #2D92EB; - color: #FFF; +#session-sync .sessions .folder[active='true'] { + background: #2d92eb; + color: #fff; } #session-sync .sessions .folder[filter] { @@ -192,22 +195,21 @@ } #session-sync .sessions .folder[type='separator']:after { - content: " "; + content: ' '; width: 100%; height: 2px; margin: -1px 0 0 0; - background: #CCC; + background: #ccc; position: absolute; box-sizing: border-box; top: 50%; left: 1px; } -#session-sync .sessions .folder[active="true"][type='separator']:after { - background: #FFF; +#session-sync .sessions .folder[active='true'][type='separator']:after { + background: #fff; } #session-sync .sessions .folder[type='bookmark'] { text-decoration: underline; } - diff --git a/dev/css/tooltip-system.css b/src/assets/css/tooltip-system.css similarity index 81% rename from dev/css/tooltip-system.css rename to src/assets/css/tooltip-system.css index 82ddb77..a6d4417 100644 --- a/dev/css/tooltip-system.css +++ b/src/assets/css/tooltip-system.css @@ -7,10 +7,10 @@ margin: -16px 0 0 0; color: #555; - background: #FFF; + background: #fff; text-align: center; line-height: 20px; - border: 1px solid #CCC; + border: 1px solid #ccc; border-radius: 5px; display: none; @@ -19,19 +19,19 @@ left: 0; z-index: 2000; - box-shadow: 0 5px 7px 1px #CCC; + box-shadow: 0 5px 7px 1px #ccc; box-sizing: border-box; } #session-sync .tooltip-system:after { - content: " "; + content: ' '; width: 16px; height: 16px; margin: -8px 0 0 -9px; - background: #FFF; + background: #fff; border-style: solid; - border-color: #BBB; + border-color: #bbb; border-width: 0 1px 1px 0; position: absolute; diff --git a/dev/css/trash-can.css b/src/assets/css/trash-can.css similarity index 61% rename from dev/css/trash-can.css rename to src/assets/css/trash-can.css index 1a17d0d..9c7b363 100644 --- a/dev/css/trash-can.css +++ b/src/assets/css/trash-can.css @@ -6,21 +6,21 @@ width: 8em; height: 8em; - background-image: url("images/trash-can.png"); + background-image: url('../images/trash-can.png'); background-position: bottom 1.5em right 1.5em; background-repeat: no-repeat; background-size: 50%; - background-color: #FFF; + background-color: #fff; border-top-left-radius: 8em; font-size: 12px; - border: 1px solid #CCC; + border: 1px solid #ccc; border-bottom: none; border-right: none; - box-shadow: 0 0 5px 0 #FFF; + box-shadow: 0 0 5px 0 #fff; opacity: 0; @@ -33,24 +33,24 @@ transition: all 0.2s ease-out; } -#session-sync[show="true"] .trash-can { +#session-sync[show='true'] .trash-can { display: block; } -#session-sync[show="false"] .trash-can { +#session-sync[show='false'] .trash-can { display: none; } -#session-sync .trash-can[droppable="true"] { +#session-sync .trash-can[droppable='true'] { opacity: 1 !important; } -#session-sync .trash-can[action="enter"] { +#session-sync .trash-can[action='enter'] { opacity: 1; cursor: pointer; } -#session-sync .trash-can:not([items="0"]) { +#session-sync .trash-can:not([items='0']) { opacity: 1; } @@ -64,10 +64,10 @@ width: 2em; height: 2em; - background: #E54828; + background: #e54828; text-align: center; line-height: 1.9em; - color: #FFF; + color: #fff; border-radius: 100%; opacity: 1; @@ -79,11 +79,11 @@ transition: background 0.5s ease-out; } -#session-sync .trash-can[items="0"] .count { - background: #1AB61A; +#session-sync .trash-can[items='0'] .count { + background: #1ab61a; } -#session-sync .trash-can[items="0"] .dismiss { +#session-sync .trash-can[items='0'] .dismiss { display: none; } @@ -92,14 +92,14 @@ right: 0.5em; } -#session-sync .trash-can[dismiss="true"] { +#session-sync .trash-can[dismiss='true'] { opacity: 0; } -#session-sync .trash-can[dismiss="true"] .dismiss { +#session-sync .trash-can[dismiss='true'] .dismiss { display: none; } -#session-sync .trash-can[dismiss="true"]:hover { +#session-sync .trash-can[dismiss='true']:hover { opacity: 1; } diff --git a/dev/css/url-bar.css b/src/assets/css/url-bar.css similarity index 77% rename from dev/css/url-bar.css rename to src/assets/css/url-bar.css index 0f2d56e..70af50c 100644 --- a/dev/css/url-bar.css +++ b/src/assets/css/url-bar.css @@ -15,7 +15,7 @@ overflow: hidden; opacity: 0; - color: #FFF; + color: #fff; position: absolute; top: 0.5em; @@ -31,14 +31,14 @@ height: 100%; display: none; - background: #FFF; + background: #fff; text-align: right; color: #777; float: left; } #session-sync .url-bar .arrow:after { - content: " "; + content: ' '; width: 2em; height: 2em; margin: 2px 0; @@ -47,24 +47,24 @@ position: absolute; left: 0; z-index: -2; - transform: translate(1em, -0.1em) rotate(45deg); + transform: translate(1em, -0.1em) rotate(45deg); } #session-sync .url-bar .url { height: 100%; padding-left: 8em; - background: #2D92EB; + background: #2d92eb; font-size: 0.75em; line-height: 2em; white-space: nowrap; text-overflow: ellipsis; } -#session-sync[search-box-content="true"] .url { +#session-sync[search-box-content='true'] .url { padding-left: calc(25% + 2.5em); } -#session-sync .url-bar[data-active="true"] { +#session-sync .url-bar[data-active='true'] { opacity: 1; bottom: -1px; } @@ -78,8 +78,8 @@ width: 5em; height: 100%; - background: #38B751; - color: #FFF; + background: #38b751; + color: #fff; text-align: center; font-size: 0.75em; line-height: 2em; diff --git a/src/assets/session-sync.css b/src/assets/session-sync.css new file mode 100644 index 0000000..80eba37 --- /dev/null +++ b/src/assets/session-sync.css @@ -0,0 +1,19 @@ +@import './css/_style.css'; +@import './css/components.css'; +@import './css/config-panel.css'; +@import './css/confirm-box.css'; +@import './css/context-menu.css'; +@import './css/field-widget.css'; +@import './css/filter-panel.css'; +@import './css/generic.css'; +@import './css/hotkey-manager.css'; +@import './css/notification-system.css'; +@import './css/search-bar.css'; +@import './css/session-container.css'; +@import './css/session-header-bar.css'; +@import './css/session-sorting.css'; +@import './css/session-toolbar.css'; +@import './css/sessions-list.css'; +@import './css/tooltip-system.css'; +@import './css/trash-can.css'; +@import './css/url-bar.css'; diff --git a/src/background/favicon-service.js b/src/background/favicon-service.js new file mode 100644 index 0000000..84b0ba7 --- /dev/null +++ b/src/background/favicon-service.js @@ -0,0 +1,97 @@ +'use strict'; + +const FaviconService = (function FaviconService() { + var serviceStatus = true; + var serviceStatusKey = 'services.favicon.enabled'; + + var getBase64ImageFromUrl = async function getBase64ImageFromUrl(imageUrl) { + var res = await fetch(imageUrl); + var blob = await res.blob(); + + return new Promise((resolve, reject) => { + var reader = new FileReader(); + reader.addEventListener( + 'load', + function () { + resolve(reader.result); + }, + false + ); + + reader.onerror = function () { + return reject(this); + }; + reader.readAsDataURL(blob); + }); + }; + + var getHostName = function getHostName(url) { + try { + return new URL(url).hostname; + } catch { + return ''; + } + }; + + var getFaviconUrl = function getFaviconUrl(url, callback) { + var key = '@favIconUrl:' + getHostName(url); + browser.storage.local.get(key).then(function (obj) { + callback(obj[key]); + }, callback); + }; + + var setFaviconUrl = function setFaviconUrl(key, favIconUrl) { + getBase64ImageFromUrl(favIconUrl).then( + function (result) { + browser.storage.local.set({ [key]: result }); + }, + function onError() { + browser.storage.local.set({ [key]: favIconUrl }); + } + ); + }; + + var checkTabFaviconSaved = function checkTabFaviconSaved(tab, waitToLoad) { + if (serviceStatus == false) { + return; + } + + if (tab.favIconUrl) { + getFaviconUrl(tab.url, function (value) { + if (value === undefined || tab.url != value) { + var key = '@favIconUrl:' + getHostName(tab.url); + setFaviconUrl(key, tab.favIconUrl); + } + }); + } else if (waitToLoad != false) { + function handle(tabId, changeInfo, tabInfo) { + browser.tabs.onUpdated.removeListener(handle); + checkTabFaviconSaved(tabInfo, false); + } + + browser.tabs.onUpdated.addListener(handle, { + tabId: tab.id, + properties: ['favIconUrl'] + }); + } + }; + + var init = function init() { + browser.storage.local.get(serviceStatusKey).then(function (obj) { + serviceStatus = obj[serviceStatusKey]; + }); + + browser.storage.onChanged.addListener(function onChange(object) { + if (object[serviceStatusKey]) { + serviceStatus = object[serviceStatusKey].newValue; + } + }); + }; + + return { + init: init, + checkTabFaviconSaved: checkTabFaviconSaved + }; +})(); + +export { FaviconService }; diff --git a/src/background/init.js b/src/background/init.js new file mode 100644 index 0000000..01e450f --- /dev/null +++ b/src/background/init.js @@ -0,0 +1,23 @@ +import { SessionSync } from './main'; +import { SessionManagement } from './session-management'; +import { SessionAutoSave } from './session-history'; +import { FaviconService } from './favicon-service'; + +export function initBackground() { + FaviconService.init(); + SessionSync.init(); + SessionManagement.init(); + SessionAutoSave.init(); + + // Delete previous undo events + if (typeof browser !== 'undefined' && browser.storage && browser.storage.local) { + browser.storage.local.set({ 'undo.events': [] }); + browser.storage.local.set({ 'trashcan.hide': true }); + browser.storage.local.set({ 'trashcan.hide-count': 0 }); + browser.storage.local.set({ 'session.active.filter': '' }); + browser.storage.local.set({ 'session.selected': null }); + browser.storage.local.set({ 'session.history.selected': null }); + } + + console.log('Session Sync -> background script initialized!'); +} diff --git a/src/background/main.js b/src/background/main.js new file mode 100644 index 0000000..817730d --- /dev/null +++ b/src/background/main.js @@ -0,0 +1,201 @@ +'use strict'; + +export const Commands = { + openTutorial: 'session_sync_tutorial', + detachToTab: 'session_sync_detach_tab', + detachToWindow: 'session_sync_detach_window', + leaveFeedback: 'session_sync_leave_feedback', + openGithub: 'session_sync_open_github' +}; + +export const SessionSync = (function () { + var windowInfoKey = 'style.window.detach'; + var activeWindow = null; + var defaultWindowProperties = { + width: 800, + height: 600 + }; + + function checkEvent(event) { + switch (event) { + case Commands.detachToTab: + detachedToTab(); + break; + + case Commands.detachToWindow: + detachToWindow(); + break; + + case Commands.leaveFeedback: + leaveFeedback(); + break; + + case Commands.openTutorial: + browser.tabs.create({ + url: 'home/home.html', + active: true + }); + break; + + case Commands.openGithub: + browser.tabs.create({ + url: 'https://github.com/ReDEnergy/SessionSync', + active: true + }); + break; + } + } + + function leaveFeedback() { + browser.tabs + .create({ + url: 'mailto:gabriel.ivanica@gmail.com?Subject=[Session-Sync]%20User%20feedback' + }) + .then(function (tab) { + browser.tabs.remove(tab.id); + }); + } + + function detachToWindow() { + if (activeWindow) { + browser.windows.update(activeWindow.id, { + focused: true + }); + return; + } + + browser.storage.local.get([windowInfoKey]).then(function (sizeInfo) { + var windowInfo = sizeInfo[windowInfoKey]; + if (windowInfo == undefined) { + windowInfo = defaultWindowProperties; + } + + browser.windows + .create({ + width: windowInfo.width, + height: windowInfo.height, + url: 'session-sync.html', + type: 'panel' + }) + .then(function (newWindow) { + activeWindow = newWindow; + }); + }); + } + + function detachedToTab() { + browser.tabs + .query({ + title: 'Session Sync', + windowId: browser.windows.WINDOW_ID_CURRENT + }) + .then(function (tabs) { + if (tabs.length > 0) { + for (let i in tabs) { + if (tabs[i].url.indexOf('moz-extension') == 0) { + browser.tabs.update(tabs[i].id, { + active: true + }); + return; + } + } + } + + browser.tabs.create({ + url: 'session-sync.html' + }); + }); + } + + function init() { + try { + if (typeof browser !== 'undefined' && browser.windows && browser.windows.onRemoved) { + browser.windows.onRemoved.addListener(function (windowID) { + if (activeWindow && windowID === activeWindow.id) { + activeWindow = null; + } + }); + } + } catch (err) { + console.warn('browser.windows.onRemoved.addListener failed', err); + } + + try { + if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.onInstalled) { + browser.runtime.onInstalled.addListener(function (startInfo) { + if (startInfo.reason == 'install') { + SessionSync.checkEvent(Commands.openTutorial); + } + }); + } + } catch (err) { + console.warn('browser.runtime.onInstalled.addListener failed', err); + } + + try { + if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.onMessage) { + browser.runtime.onMessage.addListener(function (message) { + if (message && message.event) { + SessionSync.checkEvent(message.event); + } + }); + } + } catch (err) { + console.warn('browser.runtime.onMessage.addListener failed', err); + } + + try { + if (typeof browser !== 'undefined' && browser.commands && browser.commands.onCommand) { + browser.commands.onCommand.addListener(function (command) { + SessionSync.checkEvent(command); + }); + } + } catch (err) { + console.warn('browser.commands.onCommand.addListener failed', err); + } + } + + return { + checkEvent: checkEvent, + init: init + }; +})(); + +export const WindowUtils = (function () { + var windowMargins = { + left: -8, + top: 12 + }; + + function resizeAndCenter(window, windowProperties) { + var screen = { + width: window.width + 2 * windowMargins.left, + height: window.height + 2 * windowMargins.top + }; + + windowProperties.left = window.left + (((screen.width - windowProperties.width) / 2) | 0) + windowMargins.left; + windowProperties.top = window.top + (((screen.height - windowProperties.height) / 2) | 0) + windowMargins.top; + + browser.windows.update(window.id, windowProperties); + } + + function centerWindow(window) { + var properties = { + width: window.width, + height: window.height, + state: window.state + }; + + browser.windows + .update(window.id, { + state: 'maximized' + }) + .then(function (window) { + resizeAndCenter(window, properties); + }); + } + + return { + centerWindow: centerWindow + }; +})(); diff --git a/src/background/session-history.js b/src/background/session-history.js new file mode 100644 index 0000000..96eb9c7 --- /dev/null +++ b/src/background/session-history.js @@ -0,0 +1,264 @@ +'use strict'; + +const storageKey = { + config: 'history.config', + active: 'history.active', + list: 'history.sessions' +}; + +function BrowserSession() { + this.timer = undefined; + this.active = true; + this.tabCount = 0; + this.startTime = new Date().getTime(); + this.lastSave = this.startTime; + this.windows = {}; +} + +function WindowSession(id) { + this.id = id; + this.tabs = []; + this.active = true; + this.saveTime = new Date().getTime(); +} + +BrowserSession.prototype.update = async function update() { + if (typeof browser === 'undefined' || !browser.windows) return; + + // Prepare session information + try { + const mozWindows = await browser.windows.getAll({ + populate: true, + windowTypes: ['normal'] + }); + + let groupMap = new Map(); + if (browser.tabGroups) { + try { + const groups = await browser.tabGroups.query({}); + groups.forEach((g) => groupMap.set(g.id, g)); + } catch (e) { + console.error('Error fetching groups in history update', e); + } + } + + var tabCount = 0; + + for (var key in this.windows) { + this.windows[key].active = false; + } + + for (let mozWindow of mozWindows) { + if (mozWindow.incognito && this.config && this.config.savePrivate == false) { + delete this.windows[mozWindow.id]; + continue; + } + + var windowInfo = new WindowSession(mozWindow.id); + + // Add window tabs + for (var i in mozWindow.tabs) { + var tab = mozWindow.tabs[i]; + var group = groupMap.get(tab.groupId); + var sessionTab = { + title: tab.title, + url: tab.url, + pinned: tab.pinned, + cookieStoreId: tab.cookieStoreId, + groupId: tab.groupId, + groupTitle: group ? group.title : undefined, + groupColor: group ? group.color : undefined + }; + tabCount++; + windowInfo.tabs[tab.index] = sessionTab; + } + + this.windows[mozWindow.id] = windowInfo; + } + + // Update internal storage + this.lastSave = new Date().getTime(); + this.tabCount = tabCount; + + // Save new session + if (browser.storage && browser.storage.local) { + browser.storage.local.set({ [storageKey.active]: this }); + } + } catch (err) { + console.error('Error in BrowserSession.update', err); + } +}; + +BrowserSession.prototype.clear = function clear() { + // Save new session + this.windows = {}; + if (typeof browser !== 'undefined' && browser.storage && browser.storage.local) { + browser.storage.local.set({ [storageKey.active]: this }); + } +}; + +BrowserSession.prototype.start = function start(updateInterval) { + if (this.timer == undefined) { + // console.log('Session auto-save: start'); + this.timer = setInterval(this.update.bind(this), updateInterval * 1000); + this.update(); + } +}; + +BrowserSession.prototype.stop = function stop() { + if (this.timer != undefined) { + // console.log('Session auto-save: stop'); + clearInterval(this.timer); + this.timer = undefined; + } +}; + +BrowserSession.prototype.resetInterval = function resetInterval(updateInterval) { + if (this.timer != undefined) { + // console.log('Session auto-save: reset interval'); + this.stop(); + this.start(updateInterval); + } +}; + +BrowserSession.prototype.setConfig = function setConfig(conf) { + this.config = conf; + this.resetInterval(conf.interval); + conf.enabled ? this.start(conf.interval) : this.stop(); +}; + +let activeSession = new BrowserSession(); +let sessions = []; +let config = { + enabled: false, + interval: 15, + saveBuffer: 3, + savingSlots: 20, + expireTimeHours: 48, // hours + savePrivate: false +}; + +const upgradeStorage = function upgradeStorage() { + for (var i = 0; i < sessions.length; i++) { + // New storage works with WindowSession objects + if (sessions[i] != undefined && Array.isArray(sessions[i].windows[0]) == true) { + for (var j = 0; j < sessions[i].windows.length; j++) { + var session = new WindowSession(0); + session.state = 'history'; + session.saveTime = sessions[i].lastSave; + session.tabs = sessions[i].windows[j]; + sessions[i].windows[j] = session; + } + } + } +}; + +const updateStorage = function updateStorage() { + upgradeStorage(); + + var removeCount = Math.max(sessions.length - config.savingSlots, 0); + var expireTime = new Date(new Date() - config.expireTimeHours * 3600 * 1000); + + var list = []; + // console.log('To remove', removeCount); + + // compact free space and delete expired sessions + sessions.forEach(function (session) { + if (session != undefined && (removeCount <= 0 || session.lastSave > expireTime)) { + list.push(session); + } else { + if (removeCount > 0) removeCount--; + } + }); + + if (removeCount > 0) { + list = list.slice(removeCount); + } + + // update session list + sessions = list; +}; + +const saveSessionsList = function saveSessionsList() { + if (typeof browser !== 'undefined' && browser.storage && browser.storage.local) { + browser.storage.local.set({ [storageKey.list]: sessions }); + } +}; + +const internalInit = function internalInit() { + if (typeof browser === 'undefined' || !browser.storage || !browser.storage.local) return; + + browser.storage.local.get([storageKey.list]).then(function (obj) { + if (obj[storageKey.list]) { + sessions = obj[storageKey.list]; + updateStorage(); + } + + // console.log(sessions); + saveSessionsList(); + + browser.storage.local.get([storageKey.active]).then(function (data) { + var session = data[storageKey.active]; + if (session) { + session.windows = Object.values(session.windows); + delete session.active; + sessions.push(session); + saveSessionsList(); + } + }); + + activeSession.setConfig(config); + }); +}; + +const onConfigChanged = function onConfigChanged(object) { + if (object[storageKey.config]) { + config = object[storageKey.config].newValue; + activeSession.setConfig(config); + } +}; + +export const SessionAutoSave = { + activeSession: activeSession, + init: function () { + if (typeof browser === 'undefined' || !browser.storage || !browser.storage.local) return; + + browser.storage.local.get(storageKey.config).then( + function onSuccess(obj) { + if (obj[storageKey.config]) { + config = obj[storageKey.config]; + internalInit(); + } else { + browser.storage.local + .set({ + [storageKey.config]: config + }) + .then(internalInit); + } + }, + function onError() { + internalInit(); + } + ); + + try { + if (browser.runtime && browser.runtime.onMessage) { + browser.runtime.onMessage.addListener(function (message) { + if (message && message.event == 'history.deleteActive') { + activeSession.clear(); + } + }); + } + } catch (err) { + console.warn('browser.runtime.onMessage.addListener failed in session-history', err); + } + + try { + if (browser.storage && browser.storage.onChanged) { + browser.storage.onChanged.addListener(onConfigChanged); + } + } catch (err) { + console.warn('browser.storage.onChanged.addListener failed in session-history', err); + } + } +}; diff --git a/src/background/session-management.js b/src/background/session-management.js new file mode 100644 index 0000000..1b4e4ba --- /dev/null +++ b/src/background/session-management.js @@ -0,0 +1,405 @@ +'use strict'; + +import { FaviconService } from './favicon-service'; +import { BookmarkProperties } from '../common/bookmark-properties'; + +export const SessionManagement = (function SessionManagement() { + var lazyLoadingKey = 'restore.lazy.loading'; + var lazyLoading = false; + var windowsTabsToKill = []; + + var reverseOrderKey = 'restore.reverse.order'; + var reverseOrder = false; + + function getTabInfo(url) { + return { + url: url + }; + } + + function createTab(options, info, onSuccess, onError) { + if (typeof browser === 'undefined' || !browser.tabs) return; + + var tryCreate = function (withContainer) { + var tabOptions = Object.assign({}, options); + + if (withContainer && info.container) { + tabOptions.cookieStoreId = info.container; + } + + return browser.tabs.create(tabOptions).then( + async function (tabInfo) { + if (info.discard) { + browser.tabs.discard(tabInfo.id); + } + + if (info.group) { + setTabsGroup([tabInfo.id], info.group.title, info.group.color); + } + + if (onSuccess) onSuccess(tabInfo); + }, + function (err) { + if (withContainer) { + // Container not available — retry without it + tryCreate(false); + } else { + if (onError) onError(err); + else console.error('createTab error:', err); + } + } + ); + }; + + tryCreate(info.container != null); + } + + var createGroup = async function createGroup(tabs, title, color) { + const groupId = await browser.tabs.group({ tabIds: tabs }); + await browser.tabGroups.update(groupId, { title, color }); + }; + + var setTabsGroup = async function setTabsGroup(tabs, title, color) { + const group = await browser.tabGroups.query({ title: title, color: color }); + let groupId = null; + if (group.length > 0) { + await browser.tabs.group({ tabIds: tabs, groupId: group[0].id }); + } else { + groupId = await browser.tabs.group({ tabIds: tabs }); + await browser.tabGroups.update(groupId, { title, color }); + } + + if (groupId) { + browser.tabGroups.update(groupId, { collapsed: false }); + } + }; + + var setLazyLoading = function setLazyLoading(state) { + if (typeof browser === 'undefined' || !browser.tabs) return; + lazyLoading = state; + }; + + var initConfig = function initConfig() { + if (typeof browser === 'undefined' || !browser.storage || !browser.storage.local) return; + + browser.storage.local.get(lazyLoadingKey).then(function (obj) { + setLazyLoading(obj[lazyLoadingKey]); + }); + + browser.storage.local.get(reverseOrderKey).then(function (obj) { + reverseOrder = obj[reverseOrderKey] == true; + }); + }; + + var openTab = async function openTab(options, info) { + var checkFavicon = info.favicon === true ? FaviconService.checkTabFaviconSaved : () => {}; + + switch (info.mode) { + case 'newTab': { + createTab(options, info, checkFavicon); + break; + } + + case 'newWindow': { + const windowOptions = { + url: options.url, + cookieStoreId: options.cookieStoreId + }; + + browser.windows.create(windowOptions).then(function (windowInfo) { + const tab = windowInfo.tabs[0]; + if (options.pinned) { + browser.tabs.update(tab.id, { pinned: true }); + } + + if (info.group) { + createGroup([tab.id], info.group.title, info.group.color); + } + + checkFavicon(tab); + }); + break; + } + + case 'activeTab': { + const tabs = await browser.tabs.query({ active: true, windowId: browser.windows.WINDOW_ID_CURRENT }); + const tab = await browser.tabs.get(tabs[0].id); + + if ( + (options.cookieStoreId == undefined && tab.cookieStoreId == BookmarkProperties.defaultContainer) || + tab.cookieStoreId === options.cookieStoreId + ) { + delete options.cookieStoreId; + browser.tabs.update(tab.id, options).then(checkFavicon); + } else { + createTab(options, info, checkFavicon); + } + break; + } + + default: + break; + } + }; + + var checkReverseOrder = function checkReverseOrder(bookmarks) { + if (reverseOrder === true) { + bookmarks.reverse(); + } + }; + + // After all tabs are created, group them according to their saved group metadata. + // Groups are keyed by their title+color so that separate groups with the same + // name are treated as one group within the restored window. + async function assignTabGroups(pendingTabs, windowId) { + if (typeof browser === 'undefined' || !browser.tabGroups) return; + + // Collect unique groups keyed by "title|color" + const groupMap = new Map(); // key -> { title, color, tabIds[] } + + for (const { tabPromise, groupTitle, groupColor } of pendingTabs) { + if (!groupTitle && !groupColor) continue; + const key = (groupTitle || '') + '|' + (groupColor || 'grey'); + if (!groupMap.has(key)) { + groupMap.set(key, { title: groupTitle || '', color: groupColor || 'grey', tabIds: [] }); + } + try { + const tabInfo = await tabPromise; + if (tabInfo) { + groupMap.get(key).tabIds.push(tabInfo.id); + } + } catch (e) { + // Tab creation failed; skip + } + } + + for (const { title, color, tabIds } of groupMap.values()) { + if (tabIds.length === 0) continue; + try { + const groupId = await browser.tabs.group({ tabIds, createProperties: { windowId } }); + await browser.tabGroups.update(groupId, { title, color }); + } catch (e) { + console.error('Error assigning tab group during restore:', e); + } + } + } + + var createTabsFromBookmarks = async function createTabsFromBookmarks(bookmarks, windowId) { + checkReverseOrder(bookmarks); + + const pendingTabs = []; + + bookmarks.forEach(function (bookmark) { + const info = BookmarkProperties.parseTitle(bookmark.title); + + // Wrap createTab in a Promise so we can await the created tab ID + const tabPromise = new Promise((resolve) => { + createTab( + { + url: bookmark.url, + active: false, + windowId: windowId, + pinned: info.properties.pinned ? true : false + }, + { + discard: lazyLoading, + container: info.properties.container + }, + (tabInfo) => { + FaviconService.checkTabFaviconSaved(tabInfo); + resolve(tabInfo); + }, + () => resolve(null) + ); + }); + + if (info.properties.group) { + pendingTabs.push({ + tabPromise, + groupTitle: info.properties.group.title, + groupColor: info.properties.group.color + }); + } + }); + + await assignTabGroups(pendingTabs, windowId); + }; + + var createTabsFromHistoryTabs = async function createTabsFromHistoryTabs(tabs, windowId) { + checkReverseOrder(tabs); + + const pendingTabs = []; + + tabs.forEach(function (tab) { + const tabPromise = new Promise((resolve) => { + createTab( + { + url: tab.url, + active: false, + windowId: windowId, + pinned: tab.pinned ? true : false + }, + { + discard: lazyLoading, + container: tab.cookieStoreId + }, + (tabInfo) => resolve(tabInfo), + () => resolve(null) + ); + }); + + pendingTabs.push({ + tabPromise, + groupTitle: tab.groupTitle, + groupColor: tab.groupColor + }); + }); + + await assignTabGroups(pendingTabs, windowId); + }; + + var restoreSession = function restoreSession(folderID, options) { + if (typeof browser === 'undefined' || !browser.bookmarks) return; + + browser.bookmarks.getChildren(folderID).then( + function (bookmarks) { + if (options && options.newWindow) { + createSessionWindow((windowID) => createTabsFromBookmarks(bookmarks, windowID)).then( + function (createdWindow) { + if (createdWindow) { + if (options.closeAll) { + browser.windows + .getAll({ + populate: true, + windowTypes: ['normal'] + }) + .then(function (windows) { + windows.forEach(function (mozWidnow) { + if (mozWidnow.id != createdWindow.id) { + browser.windows.remove(mozWidnow.id); + } + }); + }); + } + + if (options.closeWindow) { + browser.windows.remove(options.closeWindow); + } + } + }, + function onError() { + console.error('strage error'); + } + ); + } else { + createTabsFromBookmarks(bookmarks, browser.windows.WINDOW_ID_CURRENT); + } + }, + function fail() { + console.error('Error getting bookmarks from folder ID: ', folderID); + } + ); + }; + + var createSessionWindow = function createSessionWindow(callback) { + var action = new Promise((resolve, reject) => { + if (typeof browser === 'undefined' || !browser.windows) return reject(); + + browser.windows.create({}).then(function (mozWindow) { + resolve(mozWindow); + var windowID = mozWindow.id; + windowsTabsToKill[windowID] = mozWindow.tabs[0].id; + callback(windowID); + }); + }); + return action; + }; + + var restoreSessions = function restoreSessions(windows) { + windows.forEach(function (bookmarks) { + createSessionWindow((windowID) => createTabsFromBookmarks(bookmarks, windowID)); + }); + }; + + var restoreHistorySession = function restoreHistorySession(windows) { + windows.forEach(function (tabs) { + createSessionWindow((windowID) => createTabsFromHistoryTabs(tabs, windowID)); + }); + }; + + // ************************************************************************ + // Events + + function init() { + if (typeof browser === 'undefined') return; + + try { + if (browser.runtime && browser.runtime.onMessage) { + browser.runtime.onMessage.addListener(function (message) { + switch (message.event) { + case 'restore-session': + restoreSession(message.bookmarkID, message.options); + break; + + case 'restore-sessions': + restoreSessions(message.windows); + break; + + case 'restore-history-session': + restoreHistorySession(message.windows); + break; + + case 'save-favicon': + FaviconService.checkTabFaviconSaved(message.tab, false); + break; + + case 'open-tab': + openTab(message.options, message.info); + break; + } + }); + } + } catch (err) { + console.warn('browser.runtime.onMessage.addListener failed in session-management', err); + } + + try { + if (browser.storage && browser.storage.onChanged) { + browser.storage.onChanged.addListener(function onChange(object) { + if (object[lazyLoadingKey]) { + setLazyLoading(object[lazyLoadingKey].newValue); + } + if (object[reverseOrderKey]) { + reverseOrder = object[reverseOrderKey].newValue == true; + } + }); + } + } catch (err) { + console.warn('browser.storage.onChanged.addListener failed in session-management', err); + } + + try { + if (browser.tabs && browser.tabs.onCreated) { + browser.tabs.onCreated.addListener(function (tab) { + var killTabID = windowsTabsToKill[tab.windowId]; + if (killTabID != undefined && killTabID != tab.id) { + browser.tabs.remove(killTabID); + delete windowsTabsToKill[tab.windowId]; + } + }); + } + } catch (err) { + console.warn('browser.tabs.onCreated.addListener failed in session-management', err); + } + + initConfig(); + } + + // ************************************************************************ + // Public API + + return { + getTabInfo: getTabInfo, + init: init + }; +})(); diff --git a/src/common/bookmark-properties.js b/src/common/bookmark-properties.js new file mode 100644 index 0000000..e59dce4 --- /dev/null +++ b/src/common/bookmark-properties.js @@ -0,0 +1,68 @@ +'use strict'; + +// ***************************************************************************** +// API + +/* + * Bookmark Properties + */ + +export class BookmarkProperties { + // ------------------------------------------------------------------------ + // Public properties + + static defaultContainer = 'firefox-default'; + + static encode(tab) { + var properties = {}; + + if (tab.cookieStoreId && tab.cookieStoreId !== BookmarkProperties.defaultContainer) { + properties.container = tab.cookieStoreId; + } + + if (tab.groupId !== undefined && tab.groupId > -1) { + properties.group = { + title: tab.groupTitle, + color: tab.groupColor + }; + } + + if (tab.pinned) { + properties.pinned = 1; + } + + // If properties object is empty, don't add the marker to the title + if (Object.keys(properties).length === 0) { + return ''; + } + + return ' [' + JSON.stringify(properties) + ']'; + } + + static decode(encoded) { + try { + return JSON.parse(encoded); + } catch { + return []; + } + } + + static parseTitle(title) { + const match = title.match(/\s\[\{.+\}\]$/); + + if (match) { + var properties = this.decode(match[0]); + if (Array.isArray(properties) && properties.length == 1) { + return { + title: title.substring(0, match.index), + properties: properties[0] + }; + } + } + + return { + title: title, + properties: {} + }; + } +} diff --git a/src/common/services/firefox-containers.js b/src/common/services/firefox-containers.js new file mode 100644 index 0000000..dd1954c --- /dev/null +++ b/src/common/services/firefox-containers.js @@ -0,0 +1,63 @@ +'use strict'; + +import { DomElem } from '../../scripts/utils/dom'; + +// ***************************************************************************** +// API + +/* + * Bookmark Properties + */ + +export class FirefoxContainers { + static containers = []; + static domContainers = {}; + + // ------------------------------------------------------------------------ + // Public properties + + static async loadContainers() { + if (typeof browser === 'undefined' || !browser.contextualIdentities) return; + + const identities = await browser.contextualIdentities.query({}); + FirefoxContainers.onGot(identities); + } + + static onGot(contexts) { + FirefoxContainers.containers = contexts; + + // create dom containers + for (const container of contexts) { + const domContainer = DomElem('div', { class: 'container' }); + domContainer.style.backgroundColor = container.colorCode; + + const icon = DomElem('div', { class: 'icon' }); + icon.style.background = 'url("' + container.iconUrl + '")'; + + const name = DomElem('div', { class: 'name' }); + name.textContent = container.name; + + domContainer.appendChild(icon); + domContainer.appendChild(name); + + FirefoxContainers.domContainers[container.cookieStoreId] = domContainer; + } + } + + static onError(error) { + console.error(error); + } + + static getContainerElement(cookieStoreId) { + var node = FirefoxContainers.domContainers[cookieStoreId]; + if (node) { + return node.cloneNode(true); + } + + return null; + } + + static getContainerInfo(cookieStoreId) { + return FirefoxContainers.containers.find((container) => container.cookieStoreId === cookieStoreId); + } +} diff --git a/src/common/services/tab-groups.js b/src/common/services/tab-groups.js new file mode 100644 index 0000000..9139d8d --- /dev/null +++ b/src/common/services/tab-groups.js @@ -0,0 +1,84 @@ +'use strict'; + +import { DomElem } from '../../scripts/utils/dom'; +import { WindowEvents } from '../../scripts/utils/global-events'; + +// ***************************************************************************** +// API + +export class BrowserTabGroups { + static groups = new Map(); + static domGroups = {}; + + // ------------------------------------------------------------------------ + // Public properties + + static async initTabGroups() { + try { + if (!browser.tabGroups) return; + + const groups = await browser.tabGroups.query({}); + BrowserTabGroups.onGot(groups); + + // Add listeners for real-time updates while UI is open + browser.tabGroups.onCreated?.addListener(() => BrowserTabGroups.loadGroups()); + browser.tabGroups.onUpdated?.addListener(() => BrowserTabGroups.loadGroups()); + browser.tabGroups.onRemoved?.addListener(() => BrowserTabGroups.loadGroups()); + } catch (error) { + console.warn('TabGroups API not available or error:', error); + } + } + + static async loadGroups() { + try { + const groups = await browser.tabGroups.query({}); + BrowserTabGroups.onGot(groups); + } catch (error) { + console.warn('TabGroups API not available or error:', error); + } + } + + static onGot(groups) { + for (const group of groups) { + BrowserTabGroups.groups.set(group.id, group); + } + + WindowEvents.emitLocal('TabGroupsLoaded'); + } + + static getGroup(groupId) { + return BrowserTabGroups.groups.get(groupId); + } + + static getGroupElement(title, color) { + const tabGroup = DomElem('div', { class: 'tab-group-tag' }); + tabGroup.setAttribute('color', color || 'grey'); + + const name = DomElem('div', { class: 'name' }); + name.textContent = title && title.length > 0 ? title : 'Group'; + + tabGroup.appendChild(name); + + return tabGroup; + } + + static async createGroup(tabs, title, color) { + const groupId = await browser.tabs.group({ tabIds: tabs }); + await browser.tabGroups.update(groupId, { title, color }); + } + + static async setTabsGroup(tabs, title, color) { + const group = await browser.tabGroups.query({ title: title, color: color }); + let groupId = null; + if (group.length > 0) { + await browser.tabs.group({ tabIds: tabs, groupId: group[0].id }); + } else { + groupId = await browser.tabs.group({ tabIds: tabs }); + await browser.tabGroups.update(groupId, { title, color }); + } + + if (groupId) { + browser.tabGroups.update(groupId, { collapsed: false }); + } + } +} diff --git a/src/entrypoints/background.js b/src/entrypoints/background.js new file mode 100644 index 0000000..1887a64 --- /dev/null +++ b/src/entrypoints/background.js @@ -0,0 +1,5 @@ +import { initBackground } from '../background/init'; + +export default defineBackground(() => { + initBackground(); +}); diff --git a/src/entrypoints/scripts/session-sync.js b/src/entrypoints/scripts/session-sync.js new file mode 100644 index 0000000..4f93f4a --- /dev/null +++ b/src/entrypoints/scripts/session-sync.js @@ -0,0 +1,2 @@ +import '../../assets/session-sync.css'; +import '../../scripts/app.js'; diff --git a/src/entrypoints/session-sync.html b/src/entrypoints/session-sync.html new file mode 100644 index 0000000..2a15b7b --- /dev/null +++ b/src/entrypoints/session-sync.html @@ -0,0 +1,13 @@ + + + + + Session Sync + + + + +
+ + + diff --git a/src/scripts/3rd/pubsub.js b/src/scripts/3rd/pubsub.js new file mode 100644 index 0000000..bd2b0d4 --- /dev/null +++ b/src/scripts/3rd/pubsub.js @@ -0,0 +1,215 @@ +'use strict'; + +// Modified version of https://github.com/mroderick/PubSubJS to return different PubSub objects + +export function PubSub() { + var messages = {}, + lastUid = -1; + + function hasKeys(obj) { + var key; + + for (key in obj) { + if (obj.hasOwnProperty(key)) { + return true; + } + } + return false; + } + + /** + * Returns a function that throws the passed exception, for use as argument for setTimeout + * @param { Object } ex An Error object + */ + function throwException(ex) { + return function reThrowException() { + throw ex; + }; + } + + function callSubscriberWithDelayedExceptions(subscriber, message, data) { + try { + // subscriber( message, data ); + subscriber(data); + } catch (ex) { + setTimeout(throwException(ex), 0); + } + } + + function callSubscriberWithImmediateExceptions(subscriber, message, data) { + // subscriber( message, data ); + subscriber(data); + } + + function deliverMessage(originalMessage, matchedMessage, data, immediateExceptions) { + var subscribers = messages[matchedMessage], + callSubscriber = immediateExceptions ? callSubscriberWithImmediateExceptions : callSubscriberWithDelayedExceptions, + s; + + if (!messages.hasOwnProperty(matchedMessage)) { + return; + } + + for (s in subscribers) { + if (subscribers.hasOwnProperty(s)) { + callSubscriber(subscribers[s], originalMessage, data); + } + } + } + + function createDeliveryFunction(message, data, immediateExceptions) { + return function deliverNamespaced() { + var topic = String(message), + position = topic.lastIndexOf('.'); + + // deliver the message as it is now + deliverMessage(message, message, data, immediateExceptions); + + // trim the hierarchy and deliver message to each level + while (position !== -1) { + topic = topic.substr(0, position); + position = topic.lastIndexOf('.'); + deliverMessage(message, topic, data); + } + }; + } + + function messageHasSubscribers(message) { + var topic = String(message), + found = Boolean(messages.hasOwnProperty(topic) && hasKeys(messages[topic])), + position = topic.lastIndexOf('.'); + + while (!found && position !== -1) { + topic = topic.substr(0, position); + position = topic.lastIndexOf('.'); + found = Boolean(messages.hasOwnProperty(topic) && hasKeys(messages[topic])); + } + + return found; + } + + function publish(message, data, sync, immediateExceptions) { + var deliver = createDeliveryFunction(message, data, immediateExceptions), + hasSubscribers = messageHasSubscribers(message); + + if (!hasSubscribers) { + return false; + } + + if (sync === true) { + deliver(); + } else { + setTimeout(deliver, 0); + } + return true; + } + + /** + * PubSub.publish( message[, data] ) -> Boolean + * - message (String): The message to publish + * - data: The data to pass to subscribers + * Publishes the the message, passing the data to it's subscribers + **/ + this.publish = function (message, data) { + return publish(message, data, false, this.immediateExceptions); + }; + + /** + * PubSub.publishSync( message[, data] ) -> Boolean + * - message (String): The message to publish + * - data: The data to pass to subscribers + * Publishes the the message synchronously, passing the data to it's subscribers + **/ + this.publishSync = function (message, data) { + return publish(message, data, true, this.immediateExceptions); + }; + + /** + * PubSub.subscribe( message, func ) -> String + * - message (String): The message to subscribe to + * - func (Function): The function to call when a new message is published + * Subscribes the passed function to the passed message. Every returned token is unique and should be stored if + * you need to unsubscribe + **/ + this.subscribe = function (message, func) { + if (typeof func !== 'function') { + return false; + } + + // message is not registered yet + if (!messages.hasOwnProperty(message)) { + messages[message] = {}; + } + + // forcing token as String, to allow for future expansions without breaking usage + // and allow for easy use as key names for the 'messages' object + var token = 'uid_' + String(++lastUid); + messages[message][token] = func; + + // return token for unsubscribing + return token; + }; + + /* Public: Clears all subscriptions + */ + this.clearAllSubscriptions = function clearSubscriptions() { + messages = {}; + }; + + /* Public: removes subscriptions. + * When passed a token, removes a specific subscription. + * When passed a function, removes all subscriptions for that function + * When passed a topic, removes all subscriptions for that topic (hierarchy) + * + * value - A token, function or topic to unsubscribe. + * + * Examples + * + * // Example 1 - unsubscribing with a token + * var token = PubSub.subscribe('mytopic', myFunc); + * PubSub.unsubscribe(token); + * + * // Example 2 - unsubscribing with a function + * PubSub.unsubscribe(myFunc); + * + * // Example 3 - unsubscribing a topic + * PubSub.unsubscribe('mytopic'); + */ + this.unsubscribe = function (value) { + var isTopic = typeof value === 'string' && messages.hasOwnProperty(value), + isToken = !isTopic && typeof value === 'string', + isFunction = typeof value === 'function', + result = false, + m, + message, + t; + + if (isTopic) { + delete messages[value]; + return; + } + + for (m in messages) { + if (messages.hasOwnProperty(m)) { + message = messages[m]; + + if (isToken && message[value]) { + delete message[value]; + result = value; + // tokens are unique, so we can just stop here + break; + } else if (isFunction) { + for (t in message) { + if (message.hasOwnProperty(t) && message[t] === value) { + delete message[t]; + result = true; + } + } + } + } + } + + return result; + }; + this.messages = messages; +} diff --git a/src/scripts/app.js b/src/scripts/app.js new file mode 100644 index 0000000..3e946b0 --- /dev/null +++ b/src/scripts/app.js @@ -0,0 +1,60 @@ +'use strict'; + +// ------------------------------------------------------------------------ +// Modules + +import { AppConfig } from './config'; +import { WindowEvents, GlobalEvents } from './utils/global-events'; +import { SessionSyncUI } from './session-sync-ui'; +import { SessionSyncModel } from './components/session-sync-model'; +import { BookmarkManager } from './session/bookmarks'; +import { FirefoxContainers } from '../common/services/firefox-containers'; +import { BrowserTabGroups } from '../common/services/tab-groups'; + +const sessionRoot = 'SessionSync'; + +// ------------------------------------------------------------------------ +// API + +const findSessionSync = function findSessionSync(callback) { + BookmarkManager.searchBookmarks({ title: sessionRoot }).then( + function success(bookmarks) { + if (bookmarks.length == 0) { + BookmarkManager.createBookmark({ + title: sessionRoot, + parentId: 'menu________' + }).then(callback, function error() { + BookmarkManager.createBookmark({ + title: sessionRoot + }).then(callback); + }); + } else { + callback(bookmarks[0]); + } + }, + function fail() {} + ); +}; + +const listSessions = function listSessions() { + BookmarkManager.getFolderBookmarks(AppConfig.get('storageID'), function (sessions) { + SessionSyncModel.sessions = sessions; + WindowEvents.emit(document, 'ListSessions'); + }); +}; + +(function init() { + SessionSyncUI(); + + findSessionSync(function (bookmark) { + AppConfig.set('storageID', bookmark.id, function () { + listSessions(true); + }); + }); + + AppConfig.init(); + FirefoxContainers.loadContainers(); + BrowserTabGroups.initTabGroups(); + + GlobalEvents.on('update-sessions', listSessions.bind(null, false, false)); +})(); diff --git a/src/scripts/components/clipboard.js b/src/scripts/components/clipboard.js new file mode 100644 index 0000000..450f108 --- /dev/null +++ b/src/scripts/components/clipboard.js @@ -0,0 +1,33 @@ +'use strict'; + +// ------------------------------------------------------------------------ +// Modules + +import { HTMLCreator } from '../utils/dom'; +import { WindowEvents } from '../utils/global-events'; + +// ------------------------------------------------------------------------ +// API + +export function Clipboard() { + var DomElem = HTMLCreator(); + + var input = DomElem('input', { id: 'clipboard' }); + document.body.appendChild(input); + + // ------------------------------------------------------------------------ + // Events + + function copyText(text) { + input.value = text; + input.select(); + document.execCommand('copy'); + } + + WindowEvents.on(document, 'CopyToClipboard', copyText); + + // ------------------------------------------------------------------------ + // Public data + + this.DOMRoot = input; +} diff --git a/src/scripts/components/config-panel.js b/src/scripts/components/config-panel.js new file mode 100644 index 0000000..1b3800c --- /dev/null +++ b/src/scripts/components/config-panel.js @@ -0,0 +1,380 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +// App +import { AppConfig } from '../config'; +import { SessionHistory } from './session-history'; + +// Utils +import { WindowEvents, GlobalEvents } from '../utils/global-events'; +import { HTMLCreator } from '../utils/dom'; +import * as DOMComponent from '../utils/components'; + +// ***************************************************************************** +// Custom Modules + +/* + * Config panel + */ + +export function ConfigPanel() { + // Create DomHealper + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var panel = DomElem('div', { class: 'config-panel' }); + + // Close button + var closeBtn = DomElem('div', { class: 'css-close-button' }); + panel.appendChild(closeBtn); + // ------------------------------------------------------------------------ + // Methods + + function RangeOptionConfig(options) { + var optionRG = new DOMComponent.RangeControl({ + value: AppConfig.get(options.key), + description: options.name, + step: options.step ? options.step : 1, + minValue: options.minValue ? options.minValue : 1, + maxValue: options.maxValue ? options.maxValue : 1, + onChange: function (value) { + AppConfig.set(options.key, value); + if (options.callback) { + options.callback(value); + } + } + }); + options.parent.addItem(optionRG.DOMRoot); + AppConfig.onChange(options.key, function (value) { + optionRG.setValue(value, false); + }); + } + + function ToggleOptionConfig(options) { + var isSwitch = options.type === 'switch'; + var Toggle = DOMComponent.ToggleButton; + if (isSwitch) { + Toggle = DOMComponent.ToggleSwitch; + options.onState = 'Enabled'; + options.offState = 'Disabled'; + } + + var toggleBtn = new Toggle({ + state: AppConfig.get(options.key), + description: options.name, + onState: options.onState, + offState: options.offState, + callback: function (value) { + AppConfig.set(options.key, value); + if (options.callback) { + options.callback(value); + } + } + }); + + options.parent.addItem(toggleBtn.DOMRoot); + AppConfig.onChange(options.key, function (value) { + if (isSwitch) { + toggleBtn.setState(value); + } else { + toggleBtn.setValue(value, false); + } + }); + } + + function ConfigSection(options) { + var sectionContainer = DomElem('div', { class: 'section-container' }); + var sectionTitle = DomElem('div', { class: 'section-title' }); + var title = DomElem('div', { class: 'title' }); + var dropIcon = DomElem('div', { class: 'drop-icon' }); + var sectionBody = DomElem('div', { class: 'section-body' }); + + if (options.id) { + sectionContainer.setAttribute('id', options.id); + } + + title.textContent = options.title; + sectionTitle.appendChild(dropIcon); + sectionTitle.appendChild(title); + + sectionContainer.appendChild(sectionTitle); + sectionContainer.appendChild(sectionBody); + + sectionTitle.addEventListener('click', function () { + // manual mode + var isCollapsed = sectionContainer.hasAttribute('collapsed'); + if (isCollapsed) { + sectionContainer.removeAttribute('collapsed'); + } else { + sectionBody.style.height = sectionBody.clientHeight + 'px'; + sectionContainer.setAttribute('collapsed', ''); + } + }); + + this.container = sectionContainer; + this.addItem = function (item) { + sectionBody.appendChild(item); + }; + } + + // ------------------------ + // Section: UI scaling + + (function () { + var section = new ConfigSection({ title: 'UI Scaling' }); + panel.appendChild(section.container); + + if (AppConfig.isPanel()) { + RangeOptionConfig({ + parent: section, + name: 'Panel width (px)', + key: 'style.panel.width', + minValue: 500, + maxValue: 800, + step: 5 + }); + RangeOptionConfig({ + parent: section, + name: 'Panel height (px)', + key: 'style.panel.height', + minValue: 400, + maxValue: 600, + step: 5 + }); + } + RangeOptionConfig({ + parent: section, + name: 'Header bar', + key: 'style.scale.header', + minValue: 10, + maxValue: 50 + }); + RangeOptionConfig({ parent: section, name: 'Toolbar', key: 'style.scale.toolbar', minValue: 10, maxValue: 50 }); + RangeOptionConfig({ + parent: section, + name: 'Session list', + key: 'style.scale.sessions', + minValue: 10, + maxValue: 50 + }); + RangeOptionConfig({ + parent: section, + name: 'Bookmarks list', + key: 'style.scale.bookmarks', + minValue: 10, + maxValue: 50 + }); + })(); + + // ------------------------ + // Section: General settings + (function () { + var section = new ConfigSection({ title: 'Theme Options' }); + panel.appendChild(section.container); + + ToggleOptionConfig({ + parent: section, + name: 'Grayscale Icons', + key: 'theme.toolbar.grayscale', + type: 'switch' + }); + }); + + // ------------------------ + // Section: General settings + (function () { + var section = new ConfigSection({ title: 'Session Management' }); + panel.appendChild(section.container); + + ToggleOptionConfig({ + parent: section, + name: 'Lazy loading restore', + key: 'restore.lazy.loading', + type: 'switch' + }); + + ToggleOptionConfig({ + parent: section, + name: 'Reverse restore order', + key: 'restore.reverse.order', + type: 'switch' + }); + })(); + + // ------------------------ + // Section: General settings + (function () { + var section = new ConfigSection({ title: 'General' }); + panel.appendChild(section.container); + + ToggleOptionConfig({ + parent: section, + name: 'Left click', + key: 'bookmark.click.newTab', + onState: 'New tab', + offState: 'Same tab' + }); + + ToggleOptionConfig({ + parent: section, + name: 'Middle click', + key: 'bookmark.middleClick.newTab', + onState: 'New tab', + offState: 'Same tab' + }); + + ToggleOptionConfig({ + parent: section, + name: 'Slim scroll bar', + key: 'scroll.bar.slim', + type: 'switch' + }); + + ToggleOptionConfig({ + parent: section, + type: 'switch', + name: 'Context menu icons', + key: 'context.menu.icons' + }); + + ToggleOptionConfig({ + parent: section, + name: 'Favicon service', + key: 'services.favicon.enabled', + type: 'switch' + }); + })(); + + // ------------------------ + // Section: Hotkeys + (function () { + if (typeof browser === 'object' && browser.commands && browser.commands.update == undefined) return; + + var section = new ConfigSection({ title: 'Hotkeys' }); + panel.appendChild(section.container); + + function HotkeyOptionConfig(command) { + var button = new DOMComponent.ActionButton({ + value: command.shortcut, + description: command.description, + callback: function () { + WindowEvents.emit(document, 'ChangeHotkey', { + title: command.description, + shortcut: button.getValue(), + callback: function (value) { + button.setValue(value); + command.shortcut = value; + GlobalEvents.emit('hotkey.update', command); + } + }); + } + }); + + return button.DOMRoot; + } + + AppConfig.onChange('config.hotkey.init', function (commands) { + commands.forEach(function (command) { + var action = HotkeyOptionConfig(command); + section.addItem(action); + }); + }); + })(); + + // ------------------------ + // Section: History settings + (function () { + var section = new ConfigSection({ title: 'Auto-save History', id: 'history-config' }); + panel.appendChild(section.container); + + SessionHistory.getConfig(function (historyConfig) { + if (!historyConfig) return; + + // Context menu icons + var contextMenuIconsTB = new DOMComponent.ToggleSwitch({ + state: historyConfig.enabled, + description: 'Auto-save sessions', + onState: 'Enabled', + offState: 'Disabled', + callback: function (value) { + historyConfig.enabled = value; + SessionHistory.updateConfig(historyConfig); + if (value == false) { + WindowEvents.emit(document, 'ShowSyncList', { update: true }); + } + WindowEvents.emit(document, 'SetUIState', { history: value }); + } + }); + section.addItem(contextMenuIconsTB.DOMRoot); + + // Context menu icons + var autoSavePrivateTB = new DOMComponent.ToggleSwitch({ + state: historyConfig.savePrivate, + description: 'Save private windows', + onState: 'Enabled', + offState: 'Disabled', + callback: function (value) { + historyConfig.savePrivate = value; + SessionHistory.updateConfig(historyConfig); + } + }); + section.addItem(autoSavePrivateTB.DOMRoot); + + // Auto-save interval + var saveIntervalRG = new DOMComponent.RangeControl({ + value: historyConfig.interval, + description: 'Save interval (seconds)', + minValue: 10, + onChange: function (value) { + historyConfig.interval = value; + SessionHistory.updateConfig(historyConfig); + } + }); + section.addItem(saveIntervalRG.DOMRoot); + + // Auto-save slots + var saveSlotsRG = new DOMComponent.RangeControl({ + value: historyConfig.savingSlots, + description: 'Saving entries (max)', + minValue: 2, + onChange: function (value) { + historyConfig.savingSlots = value; + SessionHistory.updateConfig(historyConfig); + } + }); + section.addItem(saveSlotsRG.DOMRoot); + + // Auto-save expire time + var expireAfterRG = new DOMComponent.RangeControl({ + value: historyConfig.expireTimeHours, + description: 'Delete older than [...] hours', + minValue: 1, + onChange: function (value) { + historyConfig.expireTimeHours = value; + SessionHistory.updateConfig(historyConfig); + } + }); + section.addItem(expireAfterRG.DOMRoot); + + // Events + WindowEvents.emit(document, 'SetUIState', { history: historyConfig.enabled }); + }); + })(); + + // ------------------------------------------------------------------------ + // Events + + closeBtn.addEventListener('click', function () { + WindowEvents.emit(document, 'SetUIState', { config: undefined }); + }); + + // ------------------------------------------------------------------------ + // Public properties + + this.document = document; + this.DOMRoot = panel; +} diff --git a/src/scripts/components/notification-system.js b/src/scripts/components/notification-system.js new file mode 100644 index 0000000..eeddbd1 --- /dev/null +++ b/src/scripts/components/notification-system.js @@ -0,0 +1,44 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { HTMLCreator } from '../utils/dom'; +import { WindowEvents } from '../utils/global-events'; + +// ***************************************************************************** +// NotificationSystem + +export function NotificationSystem() { + var DomElem = HTMLCreator(); + + var panel = DomElem('div', { class: 'notification-system' }); + var info = DomElem('div', { class: 'info' }); + var warning = DomElem('div', { class: 'warning' }); + + panel.appendChild(info); + panel.appendChild(warning); + + // ------------------------------------------------------------------------ + // Events + + var infoTimeout = 0; + + var closeInfoBox = function closeInfoBox() { + info.removeAttribute('active'); + }; + + var showInfo = function showInfo(options) { + info.textContent = options.message; + info.setAttribute('active', ''); + clearTimeout(infoTimeout); + infoTimeout = setTimeout(closeInfoBox, options.timeout ? options.timeout : 1500); + }; + + WindowEvents.on(document, 'Notification', showInfo); + + // ------------------------------------------------------------------------ + // Public data + + this.DOMRoot = panel; +} diff --git a/src/scripts/components/overlay-system.js b/src/scripts/components/overlay-system.js new file mode 100644 index 0000000..29abba3 --- /dev/null +++ b/src/scripts/components/overlay-system.js @@ -0,0 +1,57 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { AppConfig } from '../config'; +import { HTMLCreator } from '../utils/dom'; + +// ***************************************************************************** +// API + +/* + * Session Hotkeys + */ + +export function OverlaySystem() { + // Create DomHealper + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var overlay = DomElem('div', { id: 'overlay' }); + + // ------------------------------------------------------------------------ + // Events + + var state = false; + + function setState(options) { + if (AppConfig.isPanel()) { + state = options.state; + overlay.setAttribute('state', state); + + if (options.zIndex) { + overlay.style.zIndex = options.zIndex; + } + + if (state == false) { + overlay.removeAttribute('style'); + } + } + } + + function autoHide() { + if (state == true) setState(false); + } + + // document.addEventListener('click', autoHide); + + // WindowEvents.on(document, 'OverlaySystem', setState); + + // ------------------------------------------------------------------------ + // Public properties + + this.DOMRoot = overlay; +} diff --git a/src/scripts/components/session-bookmark-events.js b/src/scripts/components/session-bookmark-events.js new file mode 100644 index 0000000..c36d12b --- /dev/null +++ b/src/scripts/components/session-bookmark-events.js @@ -0,0 +1,391 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { AppConfig } from '../config'; +import { WindowEvents } from '../utils/global-events'; +import { AutoScroll } from '../utils/auto-scroll'; +import { DragContext } from '../utils/drag-context'; +import { SessionSyncModel } from './session-sync-model'; + +import { SessionManager } from '../session/management'; +import { BookmarkManager } from '../session/bookmarks'; +import { HistoryManager } from '../session/history'; + +// ***************************************************************************** +// API + +// ---------------------------------------------------------------------------- +// Session Events - delegated + +export function SessionBookmarkEvents(container) { + var startDragEvent = null; + var trackingTelemetry = new Date(); + var offsetRect = {}; + var bookmarkContext = null; + var bookmarkHoverContextID = 0; + var SyncModel = SessionSyncModel.getModel(typeof document !== 'undefined' ? document : null); + + function getAttribute(target, attribute) { + try { + return target.getAttribute(attribute); + } catch (err) { + console.error(err); + return null; + } + } + + function getTargetType(e) { + return getAttribute(e.target, 'type'); + } + + function getSessionID(e) { + return getAttribute(e.target, 'sessionID'); + } + + function getBookmarkID(e) { + return getAttribute(e.target, 'bookmarkID'); + } + + function getTabURL(e) { + return getAttribute(e.target, 'url'); + } + + function getTabID(e) { + var ID = getAttribute(e.target, 'tabID'); + return ID ? ID | 0 : null; + } + + function getWindowID(e) { + var ID = getAttribute(e.target, 'windowID'); + return ID ? ID | 0 : null; + } + + // -------------------------------------------------------------- + // Session level + + var bookmarkDblClick = function sessionDblClick(e) { + if (SyncModel.state.session == 'restore') { + var bookmarkID = getBookmarkID(e); + if (bookmarkID != null && typeof document !== 'undefined') { + WindowEvents.emit(document, 'BookmarkCtxMenu-EditBookmark', bookmarkID); + } + } + }; + + var mouseOver = function mouseOver(e) { + if (!e.target.hasAttribute('over')) { + return; + } + + if (typeof document === 'undefined') return; + + switch (SyncModel.state.session) { + // Bookmark entry + case 'restore': { + var bookmarkID = getBookmarkID(e); + if (!bookmarkID || bookmarkHoverContextID == bookmarkID) return; + + bookmarkHoverContextID = bookmarkID; + WindowEvents.emit(document, 'ShowUrlBar', SessionSyncModel.bookmarks[bookmarkID].url); + return; + } + + // Window tab + case 'current': { + var tabID = getTabID(e); + if (tabID != undefined) { + let url = getTabURL(e); + WindowEvents.emit(document, 'ShowUrlBar', url); + } + return; + } + + // History tab + case 'history': { + let url = getTabURL(e); + WindowEvents.emit(document, 'ShowUrlBar', url); + return; + } + } + }; + + var mouseLeave = function mouseLeave() { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'HideUrlBar'); + } + }; + + // -------------------------------------------------------------- + // Mouse Events + + var trackDragging = function _mouseMove(e) { + var delta = Math.abs(startDragEvent.clientX - e.clientX) > 5 || Math.abs(startDragEvent.clientY - e.clientY) > 5; + if (delta) { + DragContext.setContext(bookmarkContext); + trackingTelemetry = new Date(); + + offsetRect = container.parentElement.getBoundingClientRect(); + offsetRect.relativeY = offsetRect.y - container.parentElement.offsetTop; + offsetRect.relativeX = offsetRect.x - container.parentElement.offsetLeft; + + bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.relativeY + 'px'; + bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.relativeX + 'px'; + bookmarkContext.DOMRoot.setAttribute('dragging', ''); + + // Events + window.addEventListener('mousemove', mouseDragging); + window.removeEventListener('mousemove', trackDragging); + } + }; + + var cancelDragging = function cancelDragging(e) { + AutoScroll.stop(); + + bookmarkContext.DOMRoot.setAttribute('dragging', 'drop'); + bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.y + container.scrollTop + 'px'; + bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.x + 'px'; + + setTimeout(function () { + bookmarkContext.DOMRoot.removeAttribute('dragging'); + bookmarkContext.restorePosition(); + }, 50); + window.removeEventListener('mousemove', mouseDragging); + + DragContext.clearContext(); + }; + + var mouseDragging = function mouseMove(e) { + if (this != window) return; + + var deltaTime = new Date() - trackingTelemetry; + if (deltaTime < 16) { + return; + } + + var tryScroll = e.clientX > offsetRect.left && e.clientX < offsetRect.right; + + if (tryScroll) { + if (e.clientY < offsetRect.top) { + AutoScroll.scroll(container, e.clientY - offsetRect.top); + } else if (e.clientY > offsetRect.bottom) { + AutoScroll.scroll(container, e.clientY - offsetRect.bottom); + } else { + AutoScroll.stop(); + } + } else { + AutoScroll.stop(); + } + + bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.relativeY + 'px'; + bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.relativeX + 'px'; + trackingTelemetry = new Date(); + }; + + var mouseUp = function _mouseUp(e) { + switch (SyncModel.state.session) { + // if restore session preview + case 'restore': { + var bookmarkID = getBookmarkID(e); + + // Test if changing + if (bookmarkID != null) { + if (bookmarkContext.bookmarkID == bookmarkID) { + // Preview the content if a click event was registered (mouse-up on the same DOM node) + if (!DragContext.hasContext()) { + var mark = SessionSyncModel.bookmarks[bookmarkID]; + BookmarkManager.openBookmark(mark.url, { + title: mark.title, + mode: BookmarkManager.getOpenMode(0) + }); + } + } else { + if (SessionSyncModel.bookmarks[bookmarkID] != undefined) { + SessionSyncModel.moveBookmark(bookmarkContext.bookmarkID, bookmarkID); + } + } + } else { + if (getTargetType(e) === 'folder') { + var sessionID = getSessionID(e); + if (sessionID != null && SessionSyncModel.bookmarks[sessionID] != undefined) { + SessionSyncModel.moveBookmarkTo(bookmarkContext.bookmarkID, sessionID); + } + } + } + break; + } + + case 'current': { + let tabID = getTabID(e); + let tabContext = SyncModel.tabs[tabID]; + + if (tabContext) { + if (tabContext != bookmarkContext) { + SessionManager.moveTab(bookmarkContext.tab.id, tabContext.tab.index, tabContext.tab.windowId); + } else { + if (tabID) { + SessionManager.activateTab(tabID); + var windowID = getWindowID(e); + if (windowID) { + SessionManager.activateWindow(windowID); + } + } + } + } else { + if (getTargetType(e) === 'folder') { + var savingSessionID = getSessionID(e); + if (savingSessionID != null) { + BookmarkManager.createBookmarkFromTab(bookmarkContext.tab, savingSessionID).then(function () { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'Notification', { + message: 'Saved' + }); + } + }); + } + } + } + } + } + + if (DragContext.hasContext()) { + cancelDragging(e); + } else { + window.removeEventListener('mousemove', trackDragging); + } + + window.removeEventListener('mouseup', mouseUp); + }; + + var bookmarkMouseDown = function _bookmarkMouseDown(e) { + var target = e.target; + if (DragContext.hasContext()) { + cancelDragging(e); + return; + } + + switch (SyncModel.state.session) { + // if restore session preview + case 'restore': { + var bookmarkID = getBookmarkID(e); + if (bookmarkID == null) { + return; + } + + bookmarkContext = SyncModel.getBookmark(bookmarkID); + + if (e.button == 0) { + startDragEvent = e; + window.addEventListener('mousemove', trackDragging); + window.addEventListener('mouseup', mouseUp); + return; + } + + // Middle Click + if (e.button == 1) { + var mark = SessionSyncModel.bookmarks[bookmarkID]; + BookmarkManager.openBookmark(mark.url, { + title: mark.title, + mode: BookmarkManager.getOpenMode(1) + }); + return; + } + + return; + } + + // If active session preview + case 'current': { + if (e.button == 0) { + let tabID = getTabID(e); + if (tabID != undefined) { + bookmarkContext = SyncModel.tabs[tabID]; + if (bookmarkContext != undefined) { + startDragEvent = e; + window.addEventListener('mousemove', trackDragging); + window.addEventListener('mouseup', mouseUp); + return; + } + } else { + let windowID = getWindowID(e); + if (windowID) { + SessionManager.activateWindow(windowID); + } + } + } + return; + } + + // if history session preview + case 'history': { + if (target.hasAttribute('url')) { + switch (e.button) { + case 0: + case 1: { + let windowID = getWindowID(e); + let tabID = getTabID(e); + let tab = HistoryManager.getHistoryTab(windowID, tabID); + + BookmarkManager.openHistoryTab(tab, BookmarkManager.getOpenMode(e.button)); + + break; + } + } + } else { + if (e.button == 0) { + let windowID = getWindowID(e); + if (windowID != undefined && typeof document !== 'undefined') { + WindowEvents.emit(document, 'HistorySessionRestoreWindow', windowID); + } + } + } + return; + } + } + }; + + var bookmarkContextMenu = function bookmarkContextMenu(e) { + if (typeof document === 'undefined') return; + + switch (SyncModel.state.session) { + case 'restore': { + var bookmarkID = getBookmarkID(e); + if (bookmarkID == null) return; + + WindowEvents.emit(document, 'BookmarkCtxMenu-Open', { + context: bookmarkID, + event: e + }); + break; + } + + case 'history': { + let url = getTabURL(e); + if (url) { + WindowEvents.emit(document, 'HistoryMarksCtxMenu-Open', { + context: e, + event: e + }); + } + } + } + }; + + // -------------------------------------------------------------- + // App Events + + // ------------------------------------------------------------------------ + // Init Code Events + + container.addEventListener('scroll', function () { + AppConfig.set('state.scrollTop.' + SyncModel.state.session, container.scrollTop); + }); + container.addEventListener('contextmenu', bookmarkContextMenu); + container.addEventListener('mousedown', bookmarkMouseDown); + + container.addEventListener('mouseover', mouseOver); + container.addEventListener('mouseleave', mouseLeave); + + container.addEventListener('dblclick', bookmarkDblClick); +} diff --git a/src/scripts/components/session-bookmark.js b/src/scripts/components/session-bookmark.js new file mode 100644 index 0000000..5cdb693 --- /dev/null +++ b/src/scripts/components/session-bookmark.js @@ -0,0 +1,289 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { HTMLCreator } from '../utils/dom'; +import { AutoScroll } from '../utils/auto-scroll'; +import { BookmarkManager } from '../session/bookmarks'; +import { BookmarkProperties } from '../../common/bookmark-properties'; +import { FirefoxContainers } from '../../common/services/firefox-containers'; +import { BrowserTabGroups } from '../../common/services/tab-groups'; + +// ***************************************************************************** +// API + +const bookmarkOffset = 2.2; // height + 2 in em + +function setTabFavicon(faviconNode, faviconURL) { + faviconNode.style.background = 'url("' + faviconURL + '")'; + faviconNode.style.backgroundRepeat = 'no-repeat'; + faviconNode.style.backgroundPosition = 'center center'; + faviconNode.style.backgroundSize = 'contain'; +} + +function configureHoverItem(item, url) { + item.setAttribute('over', ''); + item.setAttribute('url', url); +} + +/* + * Session Bookmark + * Used for interacting with a Firefox bookmark item + */ + +export class SessionBookmark { + constructor(bookmark) { + const DomElem = HTMLCreator(); + + // Decode bookmark title + var info = BookmarkProperties.parseTitle(bookmark.title); + + const box = DomElem('div', { class: 'bookmark' }); + + const favicon = DomElem('div', { class: 'favicon' }); + favicon.setAttribute('bookmarkID', bookmark.id); + favicon.setAttribute('over', ''); + + const title = DomElem('div', { class: 'title' }); + title.setAttribute('bookmarkID', bookmark.id); + title.setAttribute('over', ''); + title.textContent = info.title; + + const propertiesBox = DomElem('div', { class: 'properties' }); + propertiesBox.setAttribute('over', ''); + + if (bookmark.pinned || info.properties.pinned) { + const pin = DomElem('div', { class: 'pin' }); + propertiesBox.appendChild(pin); + } + + if (info.properties.group) { + const groupTag = BrowserTabGroups.getGroupElement(info.properties.group.title, info.properties.group.color); + if (groupTag) { + propertiesBox.appendChild(groupTag); + } + } + + if (info.properties.container) { + const container = FirefoxContainers.getContainerElement(info.properties.container); + if (container) { + propertiesBox.appendChild(container); + } + } + + box.appendChild(favicon); + box.appendChild(title); + box.appendChild(propertiesBox); + + BookmarkManager.getFaviconUrl(bookmark.url, (url) => { + setTabFavicon(favicon, url); + }); + + this.bookmark = bookmark; + this.properties = info.properties; + this.DOMtitle = title; + this.DOMfavicon = favicon; + this.bookmarkID = bookmark.id; + this.DOMRoot = box; + } + + setTitle(title) { + this.DOMtitle.textContent = title; + } + + restorePosition() { + this.DOMRoot.style.left = '0px'; + this.DOMRoot.style.top = this.position * bookmarkOffset + 'em'; + } + + setVirtualPosition(position) { + this.position = position; + this.DOMRoot.style.top = position * bookmarkOffset + 'em'; + } + + remove() { + const box = this.DOMRoot; + if (box.parentElement) { + box.parentElement.removeChild(box); + } + } + + highlight() { + this.DOMRoot.setAttribute('highlight', 'true'); + setTimeout(() => { + this.DOMRoot.removeAttribute('highlight'); + }, 2000); + + const offsetStep = this.DOMRoot.clientHeight; + const parentNode = this.DOMRoot.parentElement; + const offset = this.DOMRoot.offsetTop - parentNode.clientHeight / 2 + offsetStep; + AutoScroll.scrollTo(parentNode, offset, 0.25); + } +} + +/* + * SessionWindow + * Used as separator for different windows + */ +export class SessionWindow { + constructor(windowID, windowIndex, index) { + const DomElem = HTMLCreator(); + + const box = DomElem('div', { class: 'window-separator' }); + box.style.top = index * bookmarkOffset + 'em'; + + const title = DomElem('div', { class: 'title' }); + title.textContent = 'Window ' + (windowIndex + 1); + title.setAttribute('windowID', windowID); + + box.appendChild(title); + + this.DOMRoot = box; + } +} + +/* + * SessionTab + * Used for displaying tabs from the current window + */ +export class SessionTab { + constructor(tab, indexOffset) { + const DomElem = HTMLCreator(); + + const box = DomElem('div', { class: 'bookmark' }); + box.style.top = (tab.index + indexOffset) * bookmarkOffset + 'em'; + box.setAttribute('url', tab.url); + + const favicon = DomElem('div', { class: 'favicon' }); + favicon.setAttribute('tabID', tab.id); + favicon.setAttribute('windowID', tab.windowId); + configureHoverItem(favicon, tab.url); + + const title = DomElem('div', { class: 'title' }); + title.setAttribute('tabID', tab.id); + title.setAttribute('windowID', tab.windowId); + title.textContent = tab.title; + configureHoverItem(title, tab.url); + + if (tab.favIconUrl) { + setTabFavicon(favicon, tab.favIconUrl); + } + + const propertiesBox = DomElem('div', { class: 'properties' }); + + if (tab.pinned) { + const pin = DomElem('div', { class: 'pin' }); + propertiesBox.appendChild(pin); + } + + if (tab.cookieStoreId) { + const container = FirefoxContainers.getContainerElement(tab.cookieStoreId); + if (container) { + propertiesBox.appendChild(container); + } + } + + if (tab.groupId !== undefined && tab.groupId > -1) { + const group = BrowserTabGroups.getGroup(tab.groupId); + const groupTag = BrowserTabGroups.getGroupElement(group?.title, group?.color); + if (groupTag) { + propertiesBox.appendChild(groupTag); + } + } + + box.appendChild(favicon); + box.appendChild(title); + box.appendChild(propertiesBox); + + this.tab = tab; + this.position = tab.index + indexOffset; + this.DOMRoot = box; + } + + restorePosition() { + this.DOMRoot.style.left = '0px'; + this.DOMRoot.style.top = this.position * bookmarkOffset + 'em'; + } +} + +/* + * HistoryWindow + * Used as separator for different windows + */ +export class HistoryWindow { + constructor(sessionWindowInfo, windowID, index) { + const DomElem = HTMLCreator(); + + const box = DomElem('div', { class: 'history-window-separator' }); + box.style.top = index * bookmarkOffset + 'em'; + box.setAttribute('windowID', windowID); + + const title = DomElem('div', { class: 'title' }); + title.setAttribute('windowID', windowID); + title.textContent = 'Window ' + windowID; + + if (!sessionWindowInfo.active) { + title.textContent += ' (closed)'; + } + + box.appendChild(title); + + this.DOMRoot = box; + } +} + +/* + * HistoryTab + * Used for displaying tabs from the history windows + */ +export class HistoryTab { + constructor(tab, indexOffset) { + const DomElem = HTMLCreator(); + + const box = DomElem('div', { class: 'bookmark' }); + box.style.top = (tab.index + indexOffset) * bookmarkOffset + 'em'; + box.setAttribute('url', tab.url); + + const favicon = DomElem('div', { class: 'favicon' }); + configureHoverItem(favicon, tab.url); + + const title = DomElem('div', { class: 'title' }); + configureHoverItem(title, tab.url); + title.textContent = tab.title; + title.setAttribute('windowID', tab.windowId); + title.setAttribute('tabID', tab.id); + + BookmarkManager.getFaviconUrl(tab.url, (url) => { + setTabFavicon(favicon, url); + }); + + const propertiesBox = DomElem('div', { class: 'properties' }); + propertiesBox.setAttribute('over', ''); + + if (tab.cookieStoreId) { + const container = FirefoxContainers.getContainerElement(tab.cookieStoreId); + if (container) { + propertiesBox.appendChild(container); + } + } + + if (tab.groupId > -1) { + const groupTag = BrowserTabGroups.getGroupElement(tab.groupTitle, tab.groupColor); + if (groupTag) { + propertiesBox.appendChild(groupTag); + } + } + + if (tab.pinned) { + const pin = DomElem('div', { class: 'pin' }); + propertiesBox.appendChild(pin); + } + + box.appendChild(favicon); + box.appendChild(title); + box.appendChild(propertiesBox); + + this.DOMRoot = box; + } +} diff --git a/src/scripts/components/session-container.js b/src/scripts/components/session-container.js new file mode 100644 index 0000000..d7d50d4 --- /dev/null +++ b/src/scripts/components/session-container.js @@ -0,0 +1,411 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +// App +import { AppConfig } from '../config'; +import { SessionBookmark, SessionTab, SessionWindow, HistoryWindow, HistoryTab } from './session-bookmark'; +import { SessionBookmarkEvents } from './session-bookmark-events'; +import { SessionSyncModel } from './session-sync-model'; +import { HistoryManager } from '../session/history'; + +// Utils +import { HTMLCreator } from '../utils/dom'; +import { WindowEvents, GlobalEvents } from '../utils/global-events'; + +// Components +import { BookmarkManager } from '../session/bookmarks'; +import { SessionManager } from '../session/management'; +import { SessionHistory } from './session-history'; + +// ***************************************************************************** +// API + +/* + * SessionContainer UI + * Panel for listing session information (Firefox Bookmark Folder) and children bookmarks + */ + +export class SessionContainer { + constructor() { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Init UI + + var container = DomElem('div', { class: 'session-container' }); + container.style.fontSize = AppConfig.get('style.scale.bookmarks') + 'px'; + + var bookmarks = DomElem('div', { class: 'bookmarks' }); + container.appendChild(bookmarks); + + // ------------------------------------------------------------------------ + // User events + + function isValidSessionID(ID) { + return ( + SessionSyncModel.bookmarks[ID] !== undefined && + SessionSyncModel.bookmarks[ID].type === 'folder' && + SessionSyncModel.bookmarks[ID].parentId == AppConfig.get('storageID') + ); + } + + function restoreHistorySession(sessionInfo) { + var list = []; + sessionInfo.windows.forEach((historyWindow) => { + list.push(historyWindow.tabs); + }); + SessionManager.loadHistoryTabsNewWindow(list); + } + + if (typeof document !== 'undefined') { + WindowEvents.on(document, 'HistorySessionSave', this.saveHistorySession.bind(this)); + + WindowEvents.on(document, 'HistorySessionRestore', (index) => { + SessionHistory.getHistorySession(index, (sessionInfo) => { + restoreHistorySession(sessionInfo); + }); + }); + + WindowEvents.on(document, 'HistorySessionRestoreWindow', (windowID) => { + if (this.SyncModel.state.session === 'history') { + if (this.selectedHistorySession.active == true) { + var activeSessionWindows = Object.values(this.selectedHistorySession.windows); + if (activeSessionWindows[windowID].active) { + SessionManager.activateWindow(activeSessionWindows[windowID].id); + } else { + SessionManager.loadHistoryTabsNewWindow([activeSessionWindows[windowID].tabs]); + } + } else { + SessionManager.loadHistoryTabsNewWindow([this.selectedHistorySession.windows[windowID].tabs]); + } + } + }); + + WindowEvents.on(document, 'MenuRestoreClick', () => { + if (isValidSessionID(this.activeSessionID)) { + SessionManager.restoreSession(this.activeSessionID); + } + }); + + WindowEvents.on(document, 'MenuRestoreNewWindow', () => { + if (isValidSessionID(this.activeSessionID)) { + SessionManager.restoreNewWindow(this.activeSessionID); + } + + // history session + if (this.SyncModel.state.session === 'history') { + restoreHistorySession(this.selectedHistorySession); + } + }); + + WindowEvents.on(document, 'ReplaceSessionAll', () => { + if (isValidSessionID(this.activeSessionID)) { + SessionManager.replaceSessionAll(this.activeSessionID); + } + }); + + WindowEvents.on(document, 'ReplaceSession', () => { + if (isValidSessionID(this.activeSessionID)) { + SessionManager.replaceSession(this.activeSessionID); + } + }); + + WindowEvents.on(document, 'MenuMergeSessions', (e) => { + if (isValidSessionID(this.activeSessionID)) { + WindowEvents.emit(document, 'ConfirmBox-Open', { + event: e, + message: 'Merge selected session and active window session?', + callback: () => { + SessionManager.mergeSessions(this.activeSessionID); + } + }); + } + }); + + WindowEvents.on(document, 'MenuReplaceSession', (e) => { + if (isValidSessionID(this.activeSessionID)) { + WindowEvents.emit(document, 'ConfirmBox-Open', { + event: e, + message: 'Overwrite the selected session with active window session?', + callback: () => { + SessionManager.overwriteSession(this.activeSessionID); + } + }); + } + }); + + WindowEvents.on(document, 'MenuAddCurrentTab', () => { + if (isValidSessionID(this.activeSessionID)) { + this.bookmarkCurrentTab(); + } + }); + + WindowEvents.on(document, 'MenuSaveSession', () => { + // do not save if the session is already saved + if (isValidSessionID(this.activeSessionID)) { + return; + } + + switch (this.SyncModel.state.session) { + case 'current': + SessionManager.saveActiveSession(); + break; + + case 'history': + this.saveHistorySession(); + break; + } + }); + + WindowEvents.on(document, 'EditSelectedSession', () => { + WindowEvents.emit(document, 'SessionContextMenu-EditSession', this.activeSessionID); + }); + + WindowEvents.on(document, 'ShowCurrentSession', this.showCurrentSession.bind(this)); + + WindowEvents.on(document, 'ShowHistorySession', this.showHistorySession.bind(this)); + + WindowEvents.on(document, 'ViewSession', this.previewSession.bind(this)); + + WindowEvents.on(document, 'SessionDescriptionChanged', (sessionID) => { + if (this.activeSessionID == sessionID) { + this.updateSessionInfo(); + } + }); + + WindowEvents.on(document, 'TabGroupsLoaded', () => { + if (this.SyncModel.state.session == 'current') { + this.showCurrentSession(); + } + }); + } + + GlobalEvents.on('UpdateCurrentSession', () => { + if (this.SyncModel.state.session == 'current') { + this.showCurrentSession(); + } + }); + + AppConfig.onChange('style.scale.bookmarks', function (size) { + container.style.fontSize = size + 'px'; + }); + + AppConfig.onChange('session.save', () => { + if (this.SyncModel.state.session == 'current') { + this.showCurrentSession(); + } + }); + + // Tracked events + + SessionBookmarkEvents(bookmarks); + + var updateTimeout = null; + function updateOnTabEvent() { + if (SessionManager.tabTracking()) { + if (updateTimeout == null) { + updateTimeout = setTimeout(function () { + updateTimeout = null; + GlobalEvents.emit('UpdateCurrentSession'); + }, 1000); + } + } + } + + // Tab updates tracking + if (typeof browser !== 'undefined' && browser.tabs) { + var trackedEvents = ['onUpdated', 'onCreated', 'onDetached', 'onMoved', 'onAttached', 'onRemoved']; + trackedEvents.forEach((eventType) => { + browser.tabs[eventType].addListener(updateOnTabEvent); + }); + } + + // ------------------------------------------------------------------------ + // Public data + + this.DOMBookmarks = bookmarks; + this.DOMRoot = container; + + this.SyncModel = SessionSyncModel.getModel(typeof document !== 'undefined' ? document : null); + } + + setUIState(state, headerTitle, sessionID) { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'SetUIState', { session: state }); + WindowEvents.emit(document, 'SetSessionDescription', headerTitle); + } + + this.DOMBookmarks.textContent = ''; + this.activeSessionID = sessionID; + this.selectedHistorySession = undefined; + } + + updateSessionInfo() { + var session = SessionSyncModel.bookmarks[this.activeSessionID]; + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'SetSessionDescription', session.title); + WindowEvents.emit(document, 'SetSessionInfo', session); + } + } + + showCurrentSession() { + var DOMBookmarks = this.DOMBookmarks; + this.setUIState('current', 'Active session', 0); + + SessionManager.getCurrentWindow((mozWindow) => { + // Get non-private windows + if (mozWindow.incognito == true || AppConfig.get('session.save').allWindows == false) { + mozWindow.tabs.forEach((tab) => { + let sessionTab = new SessionTab(tab, 0, -1); + this.SyncModel.tabs[sessionTab.tab.id] = sessionTab; + DOMBookmarks.appendChild(sessionTab.DOMRoot); + }); + + this.restoreScrollTop('current'); + } else { + // Get non-private windows + var tabIndex = 0; + var windowIndex = 0; // display index in UI - ignores private windows + + SessionManager.getAllWindows((windows) => { + for (let mozWindow of windows) { + if (!mozWindow.incognito) { + // Add window separator + var sessionWindow = new SessionWindow(mozWindow.id, windowIndex, tabIndex); + DOMBookmarks.appendChild(sessionWindow.DOMRoot); + tabIndex++; + + var globalOffset = tabIndex; + + // Add window tabs + for (let i in mozWindow.tabs) { + let tab = mozWindow.tabs[i]; + let sessionTab = new SessionTab(tab, globalOffset); + this.SyncModel.tabs[sessionTab.tab.id] = sessionTab; + DOMBookmarks.appendChild(sessionTab.DOMRoot); + tabIndex++; + } + + windowIndex++; + } + } + + this.restoreScrollTop('current'); + }); + } + }); + } + + showHistorySession(sessionInfo) { + var newSession = this.selectedHistorySession != undefined && this.selectedHistorySession != sessionInfo; + + this.setUIState('history', new Date(sessionInfo.lastSave).toLocaleString(), -1); + this.selectedHistorySession = sessionInfo; + HistoryManager.selectSession(sessionInfo); + + var tabIndex = 0; + var windowIndex = 0; + + for (let windowID in sessionInfo.windows) { + // Add window separator + var sessionWindow = new HistoryWindow(sessionInfo.windows[windowID], windowIndex, tabIndex); + this.DOMBookmarks.appendChild(sessionWindow.DOMRoot); + tabIndex++; + + var globalOffset = tabIndex; + + // Add window tabs + var windowTabs = sessionInfo.windows[windowID].tabs; + for (var i in windowTabs) { + var tab = windowTabs[i]; + tab.id = i; + tab.windowId = windowID; + tab.index = i | 0; + var sessionTab = new HistoryTab(tab, globalOffset); + this.DOMBookmarks.appendChild(sessionTab.DOMRoot); + tabIndex++; + } + + windowIndex++; + } + + this.restoreScrollTop('history', newSession ? 0 : undefined); + } + + previewSession(sessionID) { + var session = SessionSyncModel.bookmarks[sessionID]; + this.setUIState('restore', '', sessionID); + + BookmarkManager.getFolderBookmarks(session.id, (marks) => { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'SetPromiseSession', { sessionID: session.id, update: false }); + } + + marks.forEach((mark) => { + var bookmark = new SessionBookmark(mark); + bookmark.setVirtualPosition(mark.index); + + SessionSyncModel.bookmarks[mark.id] = mark; + this.SyncModel.setBookmark(mark.id, bookmark); + this.DOMBookmarks.appendChild(bookmark.DOMRoot); + }); + + this.updateSessionInfo(); + this.restoreScrollTop('restore'); + }); + } + + restoreScrollTop(state, forcedValue) { + var scrollTop = AppConfig.get('state.scrollTop.' + state); + this.DOMBookmarks.scrollTop = forcedValue !== undefined ? forcedValue : scrollTop; + } + + bookmarkCurrentTab() { + // Test if already saved in this session + SessionManager.getCurrentTab((activeTab) => { + var sessionID = this.activeSessionID; + var bookmarks = SessionSyncModel.bookmarks; + + for (var i in bookmarks) { + if (bookmarks[i].parentId == sessionID && bookmarks[i].url == activeTab.url) { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'Notification', { + message: 'Already Saved' + }); + } + + var bookmarkElem = this.SyncModel.getBookmark(bookmarks[i].id); + bookmarkElem.highlight(); + return; + } + } + + // Add bookmark into the session + + BookmarkManager.createBookmarkFromTab(activeTab, sessionID).then((mark) => { + var bookmark = new SessionBookmark(mark); + + SessionSyncModel.bookmarks[mark.id] = mark; + this.SyncModel.setBookmark(mark.id, bookmark); + this.DOMBookmarks.appendChild(bookmark.DOMRoot); + + bookmark.setVirtualPosition(mark.index); + bookmark.highlight(); + }); + }); + } + + saveHistorySession(sessionIndex) { + if (sessionIndex == undefined) { + // When Save button from toolbar is pressed + SessionManager.saveHistorySession(this.selectedHistorySession); + } else { + // Save from context menu + SessionHistory.getHistorySession(sessionIndex, function (sessionInfo) { + SessionManager.saveHistorySession(sessionInfo); + }); + } + } +} diff --git a/src/scripts/components/session-filtering.js b/src/scripts/components/session-filtering.js new file mode 100644 index 0000000..007b2ce --- /dev/null +++ b/src/scripts/components/session-filtering.js @@ -0,0 +1,130 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { AppConfig } from '../config'; +import { WindowEvents, GlobalEvents } from '../utils/global-events'; +import { HTMLCreator } from '../utils/dom'; + +// ***************************************************************************** +// API + +/* + * SessionFiltering + */ + +export function SessionFiltering() { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var panel = DomElem('div', { class: 'search-bar', content: 'false', match: 'true' }); + + var createSession = DomElem('div', { class: 'create-session' }); + panel.appendChild(createSession); + + var close = DomElem('div', { class: 'close' }); + panel.appendChild(close); + + var searchInput = DomElem('input', { type: 'text', class: 'search', placeholder: 'Search' }); + panel.appendChild(searchInput); + + // ------------------------------------------------------------------------ + // Methods + + var timeOut = null; + var prevValue = ''; + + var dismiss = function dismiss() { + clearTimeout(timeOut); + if (searchInput.value.length) { + prevValue = ''; + searchInput.value = prevValue; + WindowEvents.emit(document, 'FilterSessions', searchInput.value); + } + + searchInput.blur(); + onBlur(); + + panel.setAttribute('content', searchInput.value != ''); + WindowEvents.emit(document, 'SetUIState', { 'search-box-content': searchInput.value != '' }); + }; + + var onKeyDown = function () { + if (prevValue == searchInput.value) return; + + prevValue = searchInput.value; + panel.setAttribute('match', 'false'); + panel.setAttribute('content', searchInput.value != ''); + WindowEvents.emit(document, 'SetUIState', { 'search-box-content': searchInput.value != '' }); + clearTimeout(timeOut); + timeOut = setTimeout(function () { + WindowEvents.emit(document, 'FilterSessions', searchInput.value); + }, 200); + }; + + var onFocus = function onFocus() { + panel.setAttribute('active', true); + document.addEventListener('keyup', onKeyDown); + }; + + var onBlur = function onBlur() { + if (searchInput.value == '') { + panel.setAttribute('active', false); + } + document.removeEventListener('keyup', onKeyDown); + }; + + // ------------------------------------------------------------------------ + // Events + + // Tooltip events + createSession.addEventListener('mouseover', function (e) { + WindowEvents.emit(document, 'ShowTooltip', { + node: e.target, + message: 'Create new session' + }); + }); + + createSession.addEventListener('mouseleave', function () { + WindowEvents.emit(document, 'HideTooltip'); + }); + + createSession.addEventListener('click', function () { + GlobalEvents.emit('CreateNewSession', searchInput.value); + WindowEvents.emit(document, 'HideTooltip'); + }); + + close.addEventListener('click', dismiss); + + searchInput.addEventListener('focus', onFocus); + searchInput.addEventListener('blur', onBlur); + + WindowEvents.on(document, 'FilterSessionsExactMatch', function (isMatch) { + panel.setAttribute('match', isMatch); + }); + + WindowEvents.on(document, 'FocusSessionFilter', function () { + searchInput.focus(); + }); + + // ------------------------------------------------------------------------ + // Init state + + var settingsKey = 'session.active.filter'; + + AppConfig.onChange(settingsKey, function (value) { + if (value && value.length > 0) { + searchInput.value = value; + panel.setAttribute('active', true); + onKeyDown(); + } + }); + + // ------------------------------------------------------------------------ + // Public properties + + this.DOMRoot = panel; +} diff --git a/src/scripts/components/session-folder-events.js b/src/scripts/components/session-folder-events.js new file mode 100644 index 0000000..91732b7 --- /dev/null +++ b/src/scripts/components/session-folder-events.js @@ -0,0 +1,239 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +// App +import { AppConfig } from '../config'; +import { SessionSyncModel } from './session-sync-model'; + +// Utils +import { WindowEvents } from '../utils/global-events'; +import { AutoScroll } from '../utils/auto-scroll'; +import { DragContext } from '../utils/drag-context'; + +// ***************************************************************************** +// API + +// ---------------------------------------------------------------------------- +// Session Folder Events - delegated + +export function SessionFolderEvents(container) { + var startDragEvent = null; + var trackingTelemetry; + var offsetRect = {}; + var bookmarkContext = null; + var SyncModel = SessionSyncModel.getModel(typeof document !== 'undefined' ? document : null); + + function getAttribute(target, attribute) { + try { + return target.getAttribute(attribute); + } catch (err) { + console.error(err); + return null; + } + } + + function getBookmarkID(e) { + return getAttribute(e.target, 'sessionID'); + } + + // -------------------------------------------------------------- + // Drag Events + + var trackDragging = function _mouseMove(e) { + var delta = Math.abs(startDragEvent.clientX - e.clientX) > 5 || Math.abs(startDragEvent.clientY - e.clientY) > 5; + if (delta) { + DragContext.setContext(bookmarkContext); + trackingTelemetry = new Date(); + + offsetRect = container.parentElement.parentElement.getBoundingClientRect(); + offsetRect.relativeY = offsetRect.y - container.parentElement.offsetTop; + offsetRect.relativeX = offsetRect.x - container.parentElement.offsetLeft; + + bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.relativeY + 'px'; + bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.relativeX + 'px'; + bookmarkContext.DOMRoot.setAttribute('dragging', ''); + + // Events + window.addEventListener('mousemove', mouseDragging); + window.removeEventListener('mousemove', trackDragging); + } + }; + + var cancelDragging = function cancelDragging(e) { + AutoScroll.stop(); + + bookmarkContext.DOMRoot.setAttribute('dragging', 'drop'); + bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.y + container.scrollTop + 'px'; + bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.x + 'px'; + + setTimeout(function () { + bookmarkContext.DOMRoot.removeAttribute('dragging'); + bookmarkContext.restorePosition(); + }, 50); + window.removeEventListener('mousemove', mouseDragging); + + DragContext.clearContext(); + }; + + var mouseDragging = function mouseMove(e) { + if (this != window) return; + var deltaTime = new Date() - trackingTelemetry; + if (deltaTime < 16) { + return; + } + + var tryScroll = e.clientX > offsetRect.left && e.clientX < offsetRect.right; + + if (tryScroll) { + if (e.clientY < offsetRect.top) { + AutoScroll.scroll(container, e.clientY - offsetRect.top); + } else if (e.clientY > offsetRect.bottom) { + AutoScroll.scroll(container, e.clientY - offsetRect.bottom); + } else { + AutoScroll.stop(); + } + } else { + AutoScroll.stop(); + } + + bookmarkContext.DOMRoot.style.top = e.clientY - offsetRect.relativeY + 'px'; + bookmarkContext.DOMRoot.style.left = e.clientX - offsetRect.relativeX + 'px'; + trackingTelemetry = new Date(); + }; + + var mouseUp = function _mouseUp(e) { + // Test if changing + if (e.target.className == 'folder') { + var sessionID = getBookmarkID(e); + + // Preview the content if a click event was registered (mouse-up on the same DOM node) + if (bookmarkContext.bookmarkID == sessionID) { + if (!DragContext.hasContext()) { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'SelectSyncSession', bookmarkContext); + } + } + } else { + if (AppConfig.get('session.sorting') === 'position-asc') { + SessionSyncModel.moveBookmark(bookmarkContext.bookmarkID, sessionID); + } else { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'Notification', { + message: 'Rearage function available only when items are sorted ascending by position', + timeout: 3000 + }); + } + } + } + } + + if (DragContext.hasContext()) { + cancelDragging(e); + } else { + window.removeEventListener('mousemove', trackDragging); + } + + window.removeEventListener('mouseup', mouseUp); + }; + + var folderMouseDown = function _folderMouseDown(e) { + if (DragContext.hasContext()) { + cancelDragging(e); + return; + } + + var sessionID = getBookmarkID(e); + bookmarkContext = SyncModel.getBookmark(sessionID); + + if (e.button == 0) { + startDragEvent = e; + window.addEventListener('mousemove', trackDragging); + window.addEventListener('mouseup', mouseUp); + return; + } + }; + + var folderContextMenu = function folderContextMenu(e) { + if (typeof document === 'undefined') return; + + if (bookmarkContext) { + var contextMenu = undefined; + switch (bookmarkContext.bookmark.type) { + case 'folder': + contextMenu = 'SessionContextMenu'; + break; + + case 'bookmark': + contextMenu = 'SessionContextBookmarkMenu'; + break; + + case 'separator': + contextMenu = 'SessionContextSeparatorMenu'; + break; + + default: + return; + } + + WindowEvents.emit(document, contextMenu + '-Open', { + context: bookmarkContext.bookmark.id, + event: e + }); + } else { + WindowEvents.emit(document, 'SessionListMenu-Open', { event: e }); + } + }; + + var sessionDblClick = function sessionDblClick(e) { + if (typeof document === 'undefined') return; + + if (bookmarkContext) { + switch (bookmarkContext.bookmark.type) { + case 'folder': + WindowEvents.emit(document, 'SessionContextMenu-EditSession', bookmarkContext.bookmarkID); + break; + + case 'bookmark': + WindowEvents.emit(document, 'BookmarkCtxMenu-EditBookmark', bookmarkContext.bookmarkID); + break; + } + } + }; + + // ------------------------------------------------------------------------ + // Init Code Events + + container.addEventListener('dblclick', sessionDblClick); + container.addEventListener('mousedown', folderMouseDown); + container.addEventListener('contextmenu', folderContextMenu); +} + +// ---------------------------------------------------------------------------- +// History Session Folder Events - delegated + +export function SessionHistoryEvents(container) { + var onMouseDown = function onMouseDown(e) { + if (e.button != 2) return; + + var target = e.target; + if (target.className == 'history-node') { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'HistorySessionCtxMenu-Open', { + context: e.target, + event: e + }); + } + } else { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'HistoryListCtxMenu-Open', { event: e, context: e }); + } + } + }; + + // ------------------------------------------------------------------------ + // Init Code Events + + container.addEventListener('mousedown', onMouseDown); +} diff --git a/src/scripts/components/session-folder.js b/src/scripts/components/session-folder.js new file mode 100644 index 0000000..0830264 --- /dev/null +++ b/src/scripts/components/session-folder.js @@ -0,0 +1,110 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +// Utils +import { HTMLCreator } from '../utils/dom'; + +// ***************************************************************************** +// API + +/* + * Session Folder + * Used for interacting with a Firefox bookmark item + */ +export class SessionFolder { + static folderOffset = 2.7; // em; + + constructor(bookmark) { + const DomElem = HTMLCreator(); + const folder = DomElem('div', { class: 'folder', type: bookmark.type }); + folder.setAttribute('sessionID', bookmark.id); + + this.DOMRoot = folder; + this.visible = true; + this.bookmarkID = bookmark.id; + this.updateFrom(bookmark); + } + + updateFrom(bookmark) { + this.bookmark = bookmark; + this.bookmark.title_lowercase = this.bookmark.title.toLowerCase(); + this.setTitle(bookmark.title); + } + + isVisible() { + return this.visible; + } + + setVisible() { + this.visible = true; + this.DOMRoot.removeAttribute('filter'); + } + + match(expression) { + const name = this.bookmark.title_lowercase; + if (name.indexOf(expression) !== -1) { + this.visible = true; + this.DOMRoot.removeAttribute('filter'); + return true; + } + + this.visible = false; + this.DOMRoot.setAttribute('filter', ''); + return false; + } + + restorePosition() { + this.DOMRoot.style.left = '0px'; + this.DOMRoot.style.top = this.position * SessionFolder.folderOffset + 'em'; + } + + setVirtualPosition(position) { + if (this.position === position) return; + + this.position = position; + this.DOMRoot.style.top = (position * SessionFolder.folderOffset).toFixed(2) + 'em'; + } + + setTitle(title) { + this.DOMRoot.textContent = title; + } + + remove() { + const box = this.DOMRoot; + if (box.parentElement) { + box.parentElement.removeChild(box); + } + } +} + +export const SessionFolderSortBy = { + 'name-asc': (a, b) => a.bookmark.title_lowercase > b.bookmark.title_lowercase, + 'name-desc': (a, b) => a.bookmark.title_lowercase < b.bookmark.title_lowercase, + 'position-asc': (a, b) => a.bookmark.index > b.bookmark.index, + 'position-desc': (a, b) => a.bookmark.index < b.bookmark.index, + 'date-asc': (a, b) => a.bookmark.dateAdded > b.bookmark.dateAdded, + 'date-desc': (a, b) => a.bookmark.dateAdded < b.bookmark.dateAdded +}; + +/* + * SessionHistoryFolder + * Used as separator for different windows + */ +export class SessionHistoryFolder { + constructor(sessionInfo, index) { + const DomElem = HTMLCreator(); + const folder = DomElem('div', { class: 'history-node' }); + + folder.textContent = new Date(sessionInfo.lastSave).toLocaleString(); + folder.setAttribute('index', index); + + this.DOMRoot = folder; + this.sessionInfo = sessionInfo; + } + + setTitle(title) { + this.DOMRoot.textContent = title; + } +} diff --git a/src/scripts/components/session-header-bar.js b/src/scripts/components/session-header-bar.js new file mode 100644 index 0000000..aa2b7d8 --- /dev/null +++ b/src/scripts/components/session-header-bar.js @@ -0,0 +1,328 @@ +'use strict'; + +// ------------------------------------------------------------------------ +// Modules + +import { AppConfig } from '../config'; +import { SessionSyncModel } from './session-sync-model'; + +// Utils +import * as JSUtils from '../utils/general'; +import { HTMLCreator } from '../utils/dom'; +import { WindowEvents } from '../utils/global-events'; + +// ------------------------------------------------------------------------ +// API + +export function SessionHeaderBar() { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var headerBar = DomElem('div', { class: 'header-bar' }); + headerBar.style.fontSize = AppConfig.get('style.scale.header') + 'px'; + + // Display information about the selected session + var description = DomElem('div', { class: 'description' }); + headerBar.appendChild(description); + + // Active session button + var activeSessionBtn = DomElem('div', { class: 'active-session-btn', tooltip: 'Active session' }); + headerBar.appendChild(activeSessionBtn); + + // Header Menu + var headerMenu = CreateHeaderMenu(); + headerBar.appendChild(headerMenu); + + // ------------------------------------------------------------------------ + // Events + + AppConfig.onChange('style.scale.header', function (value) { + headerBar.style.fontSize = value + 'px'; + }); + + activeSessionBtn.addEventListener('click', function () { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'ShowCurrentSession'); + } + }); + + if (typeof document !== 'undefined') { + WindowEvents.on(document, 'SetSessionDescription', function (value) { + description.textContent = value; + }); + } + + // Tooltip events + headerBar.addEventListener('mouseover', function (e) { + if (e.target.hasAttribute('tooltip')) { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'ShowTooltip', { + node: e.target, + message: e.target.getAttribute('tooltip') + }); + } + } else { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'HideTooltip'); + } + } + }); + + headerBar.addEventListener('mouseleave', function () { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'HideTooltip'); + } + }); + + // ------------------------------------------------------------------------ + // Public properties + + this.DOMRoot = headerBar; +} + +var CreateHeaderMenu = function CreateHeaderMenu() { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var headerMenu = DomElem('div', { class: 'header-menu' }); + headerMenu.setAttribute('active', 'false'); + + var menuArea = DomElem('div', { class: 'menu-area' }); + headerMenu.appendChild(menuArea); + + var menuRow1 = DomElem('div', { class: 'menu-row' }); + var menuRow2 = DomElem('div', { class: 'menu-row' }); + var menuRow3 = DomElem('div', { class: 'menu-row' }); + menuArea.appendChild(menuRow1); + menuArea.appendChild(menuRow2); + menuArea.appendChild(menuRow3); + + // ------------------------------------------------------------------------ + // Methods + + function MenuButton(options) { + var button = DomElem('div', { class: 'menu-button' }); + + var icon = DomElem('div', { class: 'icon' }); + var title = DomElem('div', { class: 'title' }); + + title.textContent = options.title; + + var url = 'url("images/' + options.icon + '")'; + icon.style.backgroundImage = url; + + // Events + var callback = typeof JSUtils.getValidFunction === 'function' ? JSUtils.getValidFunction(options.callback) : options.callback; + button.addEventListener('click', callback); + + button.appendChild(icon); + button.appendChild(title); + return button; + } + + var openConfig = MenuButton({ + title: 'Options', + icon: 'icons/gear.png', + callback: function () { + if (typeof document !== 'undefined') { + var model = SessionSyncModel.getModel(document); + var state = model.state['config'] == undefined ? 'on' : undefined; + WindowEvents.emit(document, 'SetUIState', { config: state }); + } + } + }); + + var openTutorial = MenuButton({ + title: 'Tutorial', + icon: 'icons/brightness.png', + callback: function () { + if (typeof browser !== 'undefined' && browser.runtime) { + browser.runtime.sendMessage({ event: 'session_sync_tutorial' }); + } + } + }); + + var leaveFeedback = MenuButton({ + title: 'Feedback', + icon: 'icons/mail.png', + callback: function () { + if (typeof browser !== 'undefined' && browser.runtime) { + browser.runtime.sendMessage({ event: 'session_sync_leave_feedback' }); + } + } + }); + + var exportImport = MenuButton({ + title: 'Export/Import', + icon: 'icons/booklet.png', + callback: function () { + if (typeof browser !== 'undefined' && browser.tabs) { + browser.tabs.create({ + url: 'home/home.html#export-import', + active: true + }); + } + } + }); + + var buttonDetachInTab = MenuButton({ + title: 'Tab View', + icon: 'icons/document.png', + callback: function () { + if (typeof browser !== 'undefined' && browser.runtime) { + browser.runtime.sendMessage({ event: 'session_sync_detach_tab' }); + } + } + }); + + var buttonDetachInWindow = MenuButton({ + title: 'Window View', + icon: 'icons/browser.png', + callback: function () { + if (typeof browser !== 'undefined' && browser.runtime) { + browser.runtime.sendMessage({ event: 'session_sync_detach_window' }); + } + } + }); + + var githubPage = MenuButton({ + title: 'Dev page', + icon: 'github-logo.png', + callback: function () { + if (typeof browser !== 'undefined' && browser.runtime) { + browser.runtime.sendMessage({ event: 'session_sync_open_github' }); + } + } + }); + + menuRow1.appendChild(openConfig); + menuRow2.appendChild(openTutorial); + menuRow2.appendChild(leaveFeedback); + menuRow2.appendChild(githubPage); + menuRow3.appendChild(exportImport); + menuRow3.appendChild(buttonDetachInTab); + menuRow3.appendChild(buttonDetachInWindow); + + // ------------------------------------------------------------------------ + // Dev actions + + var DebugMode = { + init: AppConfig.devMode(), + manualTrigger: 0, + triggerDate: new Date(), + action: function () { + if (this.init == false) { + if (new Date() - this.triggerDate < 5000) { + this.manualTrigger++; + if (this.manualTrigger > 10) { + this.init = true; + initDevMode(); + } + } else { + this.manualTrigger = 0; + } + } + } + }; + + function initDevMode() { + var devRow1 = DomElem('div', { class: 'menu-row' }); + + var activeTabInfo = MenuButton({ + title: 'Tab Info', + icon: 'icons/document.png', + callback: function () { + if (typeof browser !== 'undefined' && browser.tabs) { + browser.tabs + .query({ active: true, windowId: browser.windows.WINDOW_ID_CURRENT }) + .then((tabs) => browser.tabs.get(tabs[0].id)) + .then((tab) => { + console.log(tab); + }); + } + } + }); + + var localStorage = MenuButton({ + title: 'Local Storage', + icon: 'icons/bookshelf.png', + callback: function () { + if (typeof browser !== 'undefined' && browser.storage) { + browser.storage.local.get().then(function (data) { + console.log(data); + }); + } + } + }); + + var clearFaviconCache = MenuButton({ + title: 'Clear Favicons', + icon: 'icons/x.png', + callback: function () { + if (typeof browser !== 'undefined' && browser.storage) { + browser.storage.local.get().then(function (data) { + for (var key in data) { + if (key.startsWith('@favIconUrl')) { + browser.storage.local.remove(key); + } + } + }); + } + } + }); + + devRow1.appendChild(activeTabInfo); + devRow1.appendChild(localStorage); + devRow1.appendChild(clearFaviconCache); + menuArea.appendChild(devRow1); + } + + if (AppConfig.devMode()) { + initDevMode(); + } + + // ------------------------------------------------------------------------ + // Events + + var closeMenu = function closeMenu() { + headerMenu.setAttribute('active', '0'); + }; + + var closeOnMiss = function closeOnMiss(e) { + if (e && (e.target == headerMenu || e.target == menuArea)) { + return; + } + closeMenu(); + if (typeof document !== 'undefined') { + document.removeEventListener('click', closeOnMiss); + } + }; + + headerMenu.addEventListener('click', function (e) { + if (e && e.target == headerMenu) { + var state = headerMenu.getAttribute('active') | 0; + var newState = 1 - state; + headerMenu.setAttribute('active', newState); + if (newState == 1 && typeof document !== 'undefined') { + document.addEventListener('click', closeOnMiss); + } + + DebugMode.action(); + } + }); + + if (typeof document !== 'undefined') { + WindowEvents.on(document, 'CloseUI', function () { + closeMenu(); + }); + } + + // ------------------------------------------------------------------------ + // Public + + return headerMenu; +}; diff --git a/src/scripts/components/session-history.js b/src/scripts/components/session-history.js new file mode 100644 index 0000000..989ba16 --- /dev/null +++ b/src/scripts/components/session-history.js @@ -0,0 +1,114 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { WindowEvents, GlobalEvents } from '../utils/global-events'; + +// ***************************************************************************** +// API + +export const SessionHistory = (function SessionHistory() { + var storageKey = { + config: 'history.config', + active: 'history.active', + list: 'history.sessions' + }; + + var sessions = []; + + var init = function init() { + if (typeof browser !== 'undefined' && browser.storage) { + browser.storage.local.get(storageKey.list).then(function (obj) { + if (obj[storageKey.list]) { + sessions = obj[storageKey.list]; + } + }); + } + }; + + var getFullHistory = function getFullHistory(callback) { + if (typeof browser !== 'undefined' && browser.storage) { + browser.storage.local.get(storageKey.active).then(function (obj) { + callback(sessions, obj[storageKey.active]); + }); + } + }; + + var getHistorySession = function getHistorySession(index, callback) { + if (index >= 0 && index < sessions.length) { + callback(sessions[index]); + } else if (typeof browser !== 'undefined' && browser.storage) { + browser.storage.local.get(storageKey.active).then(function (obj) { + callback(obj[storageKey.active]); + }); + } + }; + + var getConfig = function getConfig(callback) { + if (typeof browser !== 'undefined' && browser.storage) { + browser.storage.local.get(storageKey.config).then(function (obj) { + callback(obj[storageKey.config]); + }); + } + }; + + var updateConfig = function updateConfig(info) { + if (typeof browser !== 'undefined' && browser.storage) { + browser.storage.local.set({ [storageKey.config]: info }); + } + }; + + var updateSessions = function updateSessions(sessions) { + if (typeof browser !== 'undefined' && browser.storage) { + browser.storage.local.set({ [storageKey.list]: sessions }).then(function () { + WindowEvents.emit(document, 'ShowHistoryList'); + }); + } + }; + + var onStorageChanged = function onStorageChanged(object) { + if (object[storageKey.list]) { + init(); + } + }; + + GlobalEvents.on('HistorySessionDelete', function (index) { + if (index == -1) { + if (typeof browser !== 'undefined' && browser.runtime) { + browser.runtime.sendMessage({ event: 'history.deleteActive' }); + } + } else { + getFullHistory(function (sessions) { + if (index >= 0 && index < sessions.length) { + sessions.splice(index, 1); + updateSessions(sessions); + } + }); + } + }); + + GlobalEvents.on('HistorySessionDeleteAll', function () { + sessions = []; + updateSessions(sessions); + }); + + if (typeof browser !== 'undefined' && browser.storage) { + try { + browser.storage.onChanged.addListener(onStorageChanged); + } catch (e) { + console.warn('browser.storage.onChanged.addListener failed', e); + } + } + + // ------------------------------------------------------------------------ + // Public API + + return { + init: init, + getConfig: getConfig, + getFullHistory: getFullHistory, + getHistorySession: getHistorySession, + updateConfig: updateConfig + }; +})(); diff --git a/src/scripts/components/session-hotkey-manager.js b/src/scripts/components/session-hotkey-manager.js new file mode 100644 index 0000000..0792862 --- /dev/null +++ b/src/scripts/components/session-hotkey-manager.js @@ -0,0 +1,216 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { BookmarkManager } from '../session/bookmarks'; + +// Utils +import { DropDown } from '../utils/components'; +import { HTMLCreator } from '../utils/dom'; +import { WindowEvents } from '../utils/global-events'; + +// ***************************************************************************** +// API + +/* + * Session Hotkeys + */ + +export function SessionHotkeyManager() { + // Create DomHealper + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var overlay = DomElem('div', { class: 'hotkey-overlay' }); + var panel = DomElem('div', { class: 'hotkey-manager' }); + + var header = DomElem('div', { class: 'header' }); + header.textContent = 'Change hotkey'; + + var infoBox = DomElem('div', { class: 'info-box' }); + var message = DomElem('div', { class: 'message' }); + var url = DomElem('div', { class: 'link' }); + var supportLink = 'https://support.mozilla.org/en-US/kb/keyboard-shortcuts-perform-firefox-tasks-quickly'; + + url.textContent = 'Click to see Firefox default shortcuts'; + message.textContent = 'Default Firefox shortcuts cannot be overrided'; + infoBox.appendChild(message); + infoBox.appendChild(url); + + function addPlusSeparator(parent) { + var separator = DomElem('div', { class: 'plus' }); + separator.textContent = '+'; + parent.appendChild(separator); + } + + function HotkeyInfo(title) { + var box = DomElem('div', { class: 'hotkey-info' }); + var header = DomElem('div', { class: 'title' }); + var value = DomElem('div', { class: 'value' }); + + header.textContent = title; + box.appendChild(header); + box.appendChild(value); + + function setValue(newValue) { + value.textContent = newValue; + } + + return { + DOMRoot: box, + setValue: setValue + }; + } + + var currentHotKey = HotkeyInfo('Current'); + var newHotKey = HotkeyInfo('New'); + + panel.appendChild(header); + panel.appendChild(infoBox); + panel.appendChild(currentHotKey.DOMRoot); + panel.appendChild(newHotKey.DOMRoot); + + var shortcutConfig = DomElem('div', { class: 'shortcut-config' }); + panel.appendChild(shortcutConfig); + + // -------------------------------------------------------------------- + // Modifier key + + var modifierKey = new DropDown({ + id: 'modifier-key', + description: 'Special keys', + onChange: updateNewHotkey + }); + shortcutConfig.appendChild(modifierKey.container); + addPlusSeparator(shortcutConfig); + + modifierKey.addOption('Ctrl'); + modifierKey.addOption('Alt'); + modifierKey.addOption('Command'); + modifierKey.addOption('MacCtrl'); + modifierKey.setValue('Ctrl'); + + // -------------------------------------------------------------------- + // Optional Modifier + + var optionalModifier = new DropDown({ + id: 'optional-modifier', + description: 'Optional modifier', + onChange: updateNewHotkey + }); + shortcutConfig.appendChild(optionalModifier.container); + addPlusSeparator(shortcutConfig); + + optionalModifier.addOption('None', { value: '' }); + optionalModifier.addOption('Shift'); + optionalModifier.setValue('Shift'); + + // -------------------------------------------------------------------- + // Shortcut key + + var shortcutKey = new DropDown({ + id: 'shortcut-key', + description: 'Key', + onChange: updateNewHotkey + }); + + // Add A-Z literals + for (let i = 65; i < 91; i++) { + shortcutKey.addOption(String.fromCharCode(i)); + } + + // Add 0-9 literals + for (let i = 0; i < 10; i++) { + shortcutKey.addOption('' + i); + } + + // Add F1-F12 literals + for (let i = 1; i <= 12; i++) { + shortcutKey.addOption('F' + i); + } + + // Add special characters + shortcutKey.addOption(',', { value: 'Comma' }); + shortcutKey.addOption('.', { value: 'Period' }); + var values = ['Home', 'End', 'PageUp', 'PageDown', 'Space', 'Insert', 'Delete', 'Up', 'Down', 'Left', 'Right']; + for (let i in values) { + shortcutKey.addOption(values[i], { property: 'wide' }); + } + shortcutConfig.appendChild(shortcutKey.container); + + // -------------------------------------------------------------------- + // Action bar + + var actionBar = DomElem('div', { class: 'action-bar' }); + + var ok = DomElem('div', { class: 'button ok' }); + ok.textContent = 'Confirm'; + + var cancel = DomElem('div', { class: 'button cancel' }); + cancel.textContent = 'Cancel'; + + var flexArea1 = DomElem('div', { class: 'flex' }); + var flexArea2 = DomElem('div', { class: 'flex' }); + + actionBar.appendChild(flexArea1); + actionBar.appendChild(ok); + actionBar.appendChild(cancel); + actionBar.appendChild(flexArea2); + + panel.appendChild(actionBar); + overlay.appendChild(panel); + + // ------------------------------------------------------------------------ + // Events + + var changeCallback; + + function updateNewHotkey() { + var optional = ' + ' + (optionalModifier.getValue() ? optionalModifier.getValue() + ' + ' : ''); + newHotKey.setValue(modifierKey.getValue() + optional + shortcutKey.getValue()); + } + + function setState(state) { + overlay.setAttribute('state', state); + } + + cancel.addEventListener('click', function () { + setState(false); + }); + + ok.addEventListener('click', function () { + setState(false); + + var optional = '+' + (optionalModifier.getValue() ? optionalModifier.getValue() + '+' : ''); + changeCallback(modifierKey.getValue() + optional + shortcutKey.getValue()); + }); + + url.addEventListener('click', function () { + BookmarkManager.openBookmark(supportLink, { + mode: 'newTab' + }); + }); + + function changeHotKey(options) { + changeCallback = options.callback; + var tokens = options.shortcut.split('+'); + + ((header.textContent = options.title), currentHotKey.setValue(tokens.join(' + '))); + newHotKey.setValue(''); + modifierKey.setValue(tokens[0]); + optionalModifier.setValue(tokens.length == 3 ? tokens[1] : ''); + shortcutKey.setValue(tokens.length == 3 ? tokens[2] : tokens[1]); + + setState(true); + } + + WindowEvents.on(document, 'ChangeHotkey', changeHotKey); + + // ------------------------------------------------------------------------ + // Public properties + + this.DOMRoot = overlay; +} diff --git a/src/scripts/components/session-hotkeys.js b/src/scripts/components/session-hotkeys.js new file mode 100644 index 0000000..565b314 --- /dev/null +++ b/src/scripts/components/session-hotkeys.js @@ -0,0 +1,66 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +// Utils +import { WindowEvents } from '../utils/global-events'; + +// ***************************************************************************** +// API + +/* + * Session Hotkeys + */ + +export function SessionHotkeys() { + // ------------------------------------------------------------------------ + // Events + + var onKeyDown = function (e) { + switch (e.keyCode) { + // Key: F1 + case 112: { + WindowEvents.emit(document, 'SessionScrollToSelected'); + break; + } + + // Key: F2 + case 113: { + WindowEvents.emit(document, 'EditSelectedSession'); + break; + } + + // Key: F3 + case 114: { + e.preventDefault(); + WindowEvents.emit(document, 'FocusSessionFilter'); + break; + } + + // Key: F6 + case 117: { + e.preventDefault(); + WindowEvents.emit(document, 'DeleteHistorySessions'); + break; + } + + // Key: CTRL + F + case 70: { + if (e.ctrlKey == true && e.shiftKey == false && e.altKey == false) { + e.preventDefault(); + WindowEvents.emit(document, 'FocusSessionFilter'); + } + break; + } + } + }; + + // ------------------------------------------------------------------------ + // Events + + document.addEventListener('keydown', onKeyDown); + + // ------------------------------------------------------------------------ + // Public properties +} diff --git a/src/scripts/components/session-list.js b/src/scripts/components/session-list.js new file mode 100644 index 0000000..d81fb6b --- /dev/null +++ b/src/scripts/components/session-list.js @@ -0,0 +1,448 @@ +'use strict'; + +// ************************************************************************ +// Modules + +import { AppConfig } from '../config'; + +import { HTMLCreator } from '../utils/dom'; +import { WindowEvents, GlobalEvents } from '../utils/global-events'; +import { AutoScroll } from '../utils/auto-scroll'; + +import { SessionFolder, SessionHistoryFolder, SessionFolderSortBy } from './session-folder'; +import { SessionFolderEvents, SessionHistoryEvents } from './session-folder-events'; +import { SessionSyncModel } from './session-sync-model'; +import { SessionHistory } from './session-history'; + +// ************************************************************************ +// API + +/* + * Session List + * UI Module for interacting with all the session + */ + +export class SessionList { + constructor() { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Init UI + + // Session Area + var root = DomElem('div', { class: 'sessions' }); + + var resizeHandle = DomElem('div', { class: 'resize-handle' }); + root.appendChild(resizeHandle); + + // History list + var historyList = this.createHistoryList(); + root.appendChild(historyList); + + // Session list + var sessionList = DomElem('div', { class: 'session-list' }); + root.appendChild(sessionList); + + // ------------------------------------------------------------------------ + // Events + + AppConfig.onChange('style.sessions.list.width', function (width) { + root.style.width = width + 'px'; + }); + + AppConfig.onChange('style.scale.sessions', function (value) { + root.style.fontSize = value + 'px'; + }); + + var startResize = function startResize(e) { + var startPos = e.clientX; + var initWidth = root.clientWidth; + resizeHandle.setAttribute('resizing', 'true'); + + function eventMove(e) { + var deltaX = e.clientX - startPos; + var newWidth = initWidth + deltaX; + AppConfig.set('style.sessions.list.width', newWidth); + } + + function evenUp() { + resizeHandle.removeAttribute('resizing'); + document.removeEventListener('mouseup', evenUp); + document.removeEventListener('mousemove', eventMove); + } + + document.addEventListener('mouseup', evenUp); + document.addEventListener('mousemove', eventMove); + }; + + resizeHandle.addEventListener('mousedown', startResize); + + if (typeof document !== 'undefined') { + WindowEvents.on(document, 'ListSessions', this.setSessions.bind(this)); + + WindowEvents.on( + document, + 'SetPromiseSession', + function (sessionInfo) { + this.promiseInfo = sessionInfo; + WindowEvents.emit(document, 'ShowSyncList', { update: sessionInfo.update }); + }.bind(this) + ); + + WindowEvents.on( + document, + 'SelectSyncSession', + function (context) { + this.selectSyncSession(context, false); + }.bind(this) + ); + + WindowEvents.on(document, 'FilterSessions', this.filterSessions.bind(this)); + WindowEvents.on(document, 'SortSessionsBy', this.sortSessionsBy.bind(this)); + + WindowEvents.on( + document, + 'SessionsPositionChanged', + function (indices) { + if (AppConfig.get('session.sorting') == 'position-asc' && AppConfig.get('session.active.filter').length == 0) { + var len = indices.length; + for (var i = 0; i < len; i++) { + var node = this.SyncModel.getBookmark(indices[i]); + node.setVirtualPosition(SessionSyncModel.bookmarks[node.bookmarkID].index); + } + } + }.bind(this) + ); + + WindowEvents.on( + document, + 'ShowCurrentSession', + function () { + if (AppConfig.get('session.selected') != null) { + AppConfig.set('session.selected', null); + AppConfig.set('state.scrollTop.current', 0); + } + this.setState('active'); + this.setSelectedNode(undefined); + }.bind(this) + ); + + WindowEvents.on( + document, + 'ShowSyncList', + function (options) { + // Select first visible session + if (this.promiseInfo.sessionID == null) { + for (var i = 0; i < this.sessions.length; i++) { + if (this.sessions[i].isVisible()) { + this.selectSyncSession(this.sessions[i], true, false); + break; + } + } + } + + this.setState('sync'); + if (options.update == true) { + GlobalEvents.emit('update-sessions'); + } + }.bind(this) + ); + + WindowEvents.on( + document, + 'SessionDescriptionChanged', + function () { + this.sortSessionsBy(AppConfig.get('session.sorting')); + this.scrollToSelected(); + }.bind(this) + ); + + WindowEvents.on( + document, + 'SessionScrollToSelected', + function () { + this.scrollToSelected(); + }.bind(this) + ); + + GlobalEvents.on( + 'BookmarkDeleted', + function (itemID) { + if (AppConfig.get('session.selected') == itemID) { + WindowEvents.emit(this.document, 'ShowCurrentSession'); + } + }.bind(this) + ); + + WindowEvents.on( + document, + 'SelectHistorySession', + function (context) { + this.selectHistorySession(context); + }.bind(this) + ); + } + + AppConfig.onChange('session.selected', function (value) { + if (value && typeof document !== 'undefined') { + WindowEvents.emit(document, 'SetPromiseSession', { sessionID: value, update: false }); + } + }); + + // ------------------------------------------------------------------------ + // Event Initialization + + SessionFolderEvents(sessionList); + SessionHistoryEvents(historyList); + SessionHistory.init(); + + // ------------------------------------------------------------------------ + // Public data + + this.sessions = []; + this.promiseInfo = {}; + this.DOMList = sessionList; + this.DOMRoot = root; + this.document = typeof document !== 'undefined' ? document : null; + + this.SyncModel = SessionSyncModel.getModel(this.document); + } + + setState(state) { + AppConfig.set('session.view', state); + if (this.document) { + WindowEvents.emit(this.document, 'SetUIState', { list: state }); + } + } + + createHistoryList() { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var historyList = DomElem('div', { class: 'history-list' }); + + // ------------------------------------------------------------------------ + // Events + + var refreshHistoryList = () => { + var snap = AppConfig.isInitState(); + var selectedIndex = AppConfig.get('session.history.selected'); + + historyList.textContent = ''; + SessionHistory.getFullHistory((historySessions, activeSession) => { + if (activeSession) { + let activeSnapshot = new SessionHistoryFolder(activeSession, -1); + activeSnapshot.setTitle('Active snapshot'); + historyList.appendChild(activeSnapshot.DOMRoot); + } + + for (let i = historySessions.length - 1; i >= 0; i--) { + if (historySessions[i] && historySessions[i].tabCount) { + let historySession = new SessionHistoryFolder(historySessions[i], i); + historyList.appendChild(historySession.DOMRoot); + + if (i == selectedIndex) { + var node = historySession.DOMRoot; + AutoScroll.scrollTo( + historyList, + node.offsetTop - historyList.parentNode.clientHeight / 2 + node.clientHeight, + snap === true ? 0 : 0.25 + ); + } + } + } + }); + }; + + GlobalEvents.on('UpdateHistoryList', function () { + refreshHistoryList(); + }); + + WindowEvents.on(document, 'ShowHistoryList', () => { + this.setState('history'); + refreshHistoryList(); + }); + + historyList.addEventListener('click', (e) => { + if (e.target.className === 'history-node') { + this.selectHistorySession(e.target); + } + }); + + // ------------------------------------------------------------------------ + // Public data + + return historyList; + } + + selectHistorySession(node) { + this.setSelectedNode(node); + var index = node.getAttribute('index') | 0; + + SessionHistory.getHistorySession(index, (sessionInfo) => { + if (this.document) { + WindowEvents.emit(this.document, 'ShowHistorySession', sessionInfo); + } + + if (AppConfig.get('session.history.selected') != index) { + AppConfig.set('session.history.selected', index); + AppConfig.set('state.scrollTop.history', 0); + } + }); + } + + setSessions() { + this.sessions = []; + this.DOMList.textContent = ''; + + // Position for active filtering + var position = 0; + var selectedSession; + var filterExpression = AppConfig.get('session.active.filter'); + + SessionSyncModel.sessions.forEach( + function (session) { + SessionSyncModel.bookmarks[session.id] = session; + + // find if a cache instance is already created and update it + var sessionFolder = this.SyncModel.getBookmark(session.id); + if (sessionFolder instanceof SessionFolder) { + sessionFolder.updateFrom(session); + } else { + sessionFolder = new SessionFolder(session); + sessionFolder.setVirtualPosition(session.index); + this.SyncModel.setBookmark(session.id, sessionFolder); + } + + if (session.id === this.promiseInfo.sessionID) { + selectedSession = sessionFolder; + } + + if (filterExpression.length > 0) { + if (sessionFolder.match(filterExpression)) { + sessionFolder.setVirtualPosition(position); + position++; + } + } + + this.sessions.push(sessionFolder); + this.DOMList.appendChild(sessionFolder.DOMRoot); + }.bind(this) + ); + + if (filterExpression.length > 0) { + this.filterSessions(filterExpression); + } + + this.sortSessionsBy(AppConfig.get('session.sorting')); + + // Switch to History View if this was the state before closing the UI + if (AppConfig.get('session.view') == 'history' && this.document) { + WindowEvents.emit(this.document, 'ShowHistoryList'); + return; + } + + // Preview the selected session if available + if (selectedSession) { + this.selectSyncSession(selectedSession, true, AppConfig.isInitState()); + return; + } + + // Otherwise, preview the current session + AppConfig.isInitState(); + if (this.document) { + WindowEvents.emit(this.document, 'ShowCurrentSession'); + } + } + + scrollToSelected(snap) { + if (this.selectedNode == undefined) { + return; + } + + var offset = this.selectedNode.offsetTop - this.DOMList.clientHeight / 2 + this.selectedNode.clientHeight; + + AutoScroll.scrollTo(this.DOMList, offset, snap === true ? 0 : 0.25); + } + + setSelectedNode(node) { + if (this.selectedNode != undefined) { + this.selectedNode.removeAttribute('active'); + } + + this.selectedNode = node; + + if (node) { + node.setAttribute('active', 'true'); + } + } + + selectSyncSession(selectedSession, scrollTo, snap) { + if (AppConfig.get('session.selected') != selectedSession.bookmarkID) { + AppConfig.set('session.selected', selectedSession.bookmarkID); + AppConfig.set('state.scrollTop.restore', 0); + } + + this.setSelectedNode(selectedSession.DOMRoot); + + if (this.document) { + WindowEvents.emit(this.document, 'ViewSession', selectedSession.bookmarkID); + if (this.promiseInfo.edit == true) { + WindowEvents.emit(this.document, 'SessionContextMenu-EditSession', selectedSession.bookmarkID); + } + } + + // auto-scroll to the selected node + if (scrollTo) { + this.scrollToSelected(snap); + } + } + + filterSessions(expression) { + AppConfig.set('session.active.filter', expression); + + if (expression.length) { + expression = expression.toLowerCase(); + var exactMatch = false; + var expLength = expression.length; + var doc = this.document; + + var position = 0; + this.sessions.forEach(function (session) { + if (session.match(expression)) { + session.setVirtualPosition(position); + position++; + + if (!exactMatch) { + // test same length + exactMatch = session.bookmark.title_lowercase.length == expLength; + } + } + }); + + this.DOMList.parentNode.scrollTop = 0; + if (doc) { + WindowEvents.emit(doc, 'FilterSessionsExactMatch', exactMatch); + } + } else { + this.sessions.forEach(function (session) { + session.setVisible(); + }); + this.sortSessionsBy(AppConfig.get('session.sorting')); + } + } + + sortSessionsBy(method) { + AppConfig.set('session.sorting', method); + this.sessions.sort(SessionFolderSortBy[method]); + var position = 0; + this.sessions.forEach(function (session) { + if (session.isVisible()) { + session.setVirtualPosition(position); + position++; + } + }); + } +} diff --git a/src/scripts/components/session-sorting.js b/src/scripts/components/session-sorting.js new file mode 100644 index 0000000..463d2ec --- /dev/null +++ b/src/scripts/components/session-sorting.js @@ -0,0 +1,106 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +// Utils +import { AppConfig } from '../config'; +import { HTMLCreator } from '../utils/dom'; +import { WindowEvents } from '../utils/global-events'; + +// ***************************************************************************** +// API + +export function SessionSorting() { + // Create DomHealper + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // UI + + // Session ordering + var sortControl = DomElem('div', { class: 'sorting-method', tooltip: 'Sort sessions' }); + + // Sorting dropdown + var optionList = DomElem('div', { class: 'option-list' }); + + var options = { + 'name-asc': { description: 'name ↑', index: 0 }, + 'name-desc': { description: 'name ↓', index: 1 }, + 'position-asc': { description: 'position ↑', index: 2 }, + 'position-desc': { description: 'position ↓', index: 3 }, + 'date-asc': { description: 'date ↑', index: 4 }, + 'date-desc': { description: 'date ↓', index: 5 } + }; + + for (var key in options) { + var option = DomElem('div', { class: 'option', value: key }); + option.textContent = options[key].description; + optionList.appendChild(option); + } + + var dropdown = DomElem('div', { class: 'dropdown' }); + dropdown.appendChild(optionList); + sortControl.appendChild(dropdown); + + var activeButton = optionList.children[0]; + + // ------------------------------------------------------------------------ + // Events + + AppConfig.onChange('session.sorting', function (value) { + if (options[value]) { + activeButton = optionList.children[options[value].index]; + activeButton.setAttribute('active', ''); + } + }); + + optionList.addEventListener('click', function (e) { + if (e.target.className == 'option') { + if (e.target != activeButton) { + activeButton.removeAttribute('active'); + activeButton = e.target; + var value = activeButton.getAttribute('value'); + activeButton.setAttribute('active', ''); + } + WindowEvents.emit(document, 'SortSessionsBy', value); + } + }); + + var state = false; + var toggleState = function toggleState() { + state = !state; + sortControl.setAttribute('active', state); + WindowEvents.emit(document, 'OverlaySystem', { state: state, zIndex: 1 }); + }; + + document.addEventListener('click', function (e) { + if (e.target.className == 'sorting-method') { + toggleState(); + } else { + if (state == true) toggleState(); + } + }); + + // Tooltip events + sortControl.addEventListener('mouseover', function (e) { + var tooltipMsg = e.target.getAttribute('tooltip'); + if (tooltipMsg) { + WindowEvents.emit(document, 'ShowTooltip', { + node: e.target, + message: tooltipMsg + }); + } else { + WindowEvents.emit(document, 'HideTooltip'); + } + }); + + sortControl.addEventListener('mouseleave', function () { + WindowEvents.emit(document, 'HideTooltip'); + }); + + // ------------------------------------------------------------------------ + // Data + + this.DOMRoot = sortControl; +} diff --git a/src/scripts/components/session-sync-model.js b/src/scripts/components/session-sync-model.js new file mode 100644 index 0000000..3f4e422 --- /dev/null +++ b/src/scripts/components/session-sync-model.js @@ -0,0 +1,408 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { AppConfig } from '../config'; + +// Components +import { BookmarkManager } from '../session/bookmarks'; +import { WindowEvents, GlobalEvents } from '../utils/global-events'; + +// Utils +import { LimitCounter } from '../utils/general'; + +// ***************************************************************************** +// API + +export class SessionSyncDOMModel { + constructor(document) { + this.bookmarks = {}; + this.tabs = {}; + this.state = {}; + + // ------------------------------------------------------------------------ + // Events + + const OrderBookmarks = (indices) => { + var len = indices.length; + for (let i = 0; i < len; i++) { + var node = this.bookmarks[indices[i]]; + if (node) { + node.setVirtualPosition(SessionSyncModel.bookmarks[node.bookmarkID].index); + } + } + }; + + WindowEvents.on(document, 'BookmarksPositionChanged', OrderBookmarks); + + WindowEvents.on(document, 'BookmarkTitleChanged', (bookmarkID) => { + var node = this.bookmarks[bookmarkID]; + if (node) { + node.setTitle(SessionSyncModel.bookmarks[bookmarkID].title); + } + }); + + // ------------------------------------------------------------------------ + // Register + + SessionSyncModel.registerModel(document, this); + } + + getBookmark(id) { + return this.bookmarks[id]; + } + + setBookmark(id, bookmark) { + this.bookmarks[id] = bookmark; + } + + removeBookmark(id) { + if (this.bookmarks[id]) { + this.bookmarks[id].remove(); + delete this.bookmarks[id]; + } + } +} + +export const SessionSyncModel = (function SessionSync() { + // Bookmarks storage + var bookmarks = {}; + + // Per document DOM cache + var domModels = new Map(); + + // ------------------------------------------------------------------------ + // API + + var getModel = function getModel(doc) { + var model = domModels.get(doc); + if (model instanceof SessionSyncDOMModel === false) { + model = new SessionSyncDOMModel(doc); + } + return model; + }; + + var registerModel = function registerModel(doc, model) { + if (model instanceof SessionSyncDOMModel) { + domModels.set(doc, model); + } + }; + + var unregisterModel = function unregisterModel(doc) { + var model = domModels.get(doc); + if (model instanceof SessionSyncDOMModel) { + domModels.delete(doc); + } + }; + + function onActionError() { + WindowEvents.emitLocal('Notification', { + message: 'Error when updating bookmarks' + }); + } + + function isSession(parentID) { + return parentID === AppConfig.get('storageID'); + } + + // Assumes moving in the same parent directory + var moveBookmark = function moveBookmark(idA, idB) { + var parentID = bookmarks[idA].parentId; + var indexA = bookmarks[idA].index; + var indexB = bookmarks[idB].index; + var direction = indexA > indexB ? 1 : -1; + + BookmarkManager.moveItem(idA, parentID, indexA < indexB ? indexB : indexB, onSuccessMove, onActionError); + + function onSuccessMove() { + var indices = []; + var min = Math.min(indexA, indexB); + var max = Math.max(indexA, indexB); + + for (var id in bookmarks) { + var B = bookmarks[id]; + if (B.parentId == parentID && B.index > min && B.index < max) { + indices.push(B.id); + B.index += direction; + } + } + + indices.push(idA); + indices.push(idB); + bookmarks[idA].index = indexB; + bookmarks[idB].index += direction; + + WindowEvents.broadcast(isSession(parentID) ? 'SessionsPositionChanged' : 'BookmarksPositionChanged', indices); + } + }; + + // Moving a bookmark to a new session directory + var moveBookmarkTo = function moveBookmarkTo(bookmarkID, folderID) { + var parentID = bookmarks[bookmarkID].parentId; + if (parentID == folderID) { + return; + } + + BookmarkManager.moveItem(bookmarkID, folderID, undefined, onSuccess, onActionError); + + function onSuccess() { + bookmarks[bookmarkID].parentId = folderID; + + // TODO: set position based on the length of the new parent list + // TODO: trigger update for folderID + + var index = bookmarks[bookmarkID].index; + bookmarks[bookmarkID].index = -1; + + // Remove the bookmark from all UIs + domModels.forEach(function (model) { + model.removeBookmark(bookmarkID); + }); + + // Reorder all bookmarks after the removed one + var indices = []; + for (var id in bookmarks) { + var B = bookmarks[id]; + if (B.parentId == parentID && B.index > index) { + indices.push(B.id); + B.index--; + } + } + + // Trigger reordering + WindowEvents.broadcast('BookmarksPositionChanged', indices); + } + }; + + // all items must have the same parent + var deleteBookmarkList = function deleteBookmarkList(bookmarkIDs, parentID, callback) { + if (bookmarkIDs.length == 0) { + callback(); + return; + } + + var reorderList = []; + + // Get position ordered list of all bookmarks from the parrentID folder + for (var id in bookmarks) { + var B = bookmarks[id]; + if (B.parentId == parentID) { + reorderList[B.index] = B; + } + } + + var updateEvent = new LimitCounter(bookmarkIDs.length, onFinish); + + bookmarkIDs.forEach(function (bookmarkID) { + BookmarkManager.deleteItem(bookmarkID, false, onDeleteSuccess.bind(null, bookmarkID), updateEvent.advance); + }); + + function onFinish() { + // compute reordering + var indices = []; + var position = 0; + for (var i = 0; i < reorderList.length; i++) { + if (reorderList[i]) { + indices.push(reorderList[i].id); + reorderList[i].index = position; + position++; + } + } + + if (indices.length) { + // Trigger reordering + WindowEvents.broadcast('BookmarksPositionChanged', indices); + } + + callback(); + } + + function onDeleteSuccess(bookmarkID) { + var index = bookmarks[bookmarkID].index; + + // Remove the bookmark from all UIs + domModels.forEach(function (model) { + model.removeBookmark(bookmarkID); + }); + + delete reorderList[index]; + delete bookmarks[bookmarkID]; + GlobalEvents.emit('BookmarkRemoved'); + + updateEvent.advance(); + } + }; + + var deleteBookmarkItem = function deleteBookmarkItem(bookmarkID) { + var isFolder = bookmarks[bookmarkID].type === 'folder'; + BookmarkManager.deleteItem(bookmarkID, isFolder, onDeleteSuccess, onActionError); + + function onDeleteSuccess() { + var parentID = bookmarks[bookmarkID].parentId; + var index = bookmarks[bookmarkID].index; + + // Remove the bookmark from all UIs + domModels.forEach(function (model) { + model.removeBookmark(bookmarkID); + }); + + // Reorder all bookmarks after the removed one + var indices = []; + for (let id in bookmarks) { + let B = bookmarks[id]; + if (B.parentId == parentID && B.index > index) { + indices.push(B.id); + B.index--; + } + } + + // Save undo history + var undoEvent = { type: 'delete', bookmark: bookmarks[bookmarkID], items: [] }; + + if (isFolder) { + var folderID = bookmarks[bookmarkID].id; + for (let id in bookmarks) { + let B = bookmarks[id]; + if (B.parentId == folderID) { + undoEvent.items.push(Object.assign({}, B)); + } + } + } + + // Remove model references + delete bookmarks[bookmarkID]; + undoEvent.items.forEach(function (B) { + delete bookmarks[B.id]; + }); + + // Save undo event + var undoEvents = AppConfig.get('undo.events'); + undoEvents.push(undoEvent); + + AppConfig.set('undo.events', undoEvents); + GlobalEvents.emit('BookmarkDeleted', bookmarkID); + + // Trigger reordering + WindowEvents.broadcast('BookmarksPositionChanged', indices); + } + }; + + var updateBookmarkItem = function updateBookmarkItem(bookmarkID, options) { + try { + if (options.title) { + BookmarkManager.setTitle(bookmarks[bookmarkID], options.title, function () { + WindowEvents.broadcast('BookmarkTitleChanged', bookmarkID); + WindowEvents.broadcast('SessionDescriptionChanged', bookmarkID); + }); + } + + if (options.url) { + BookmarkManager.setLocation(bookmarks[bookmarkID], options.url); + } + + WindowEvents.emitLocal('Notification', { + message: 'Success' + }); + } catch (err) { + console.error(err); + WindowEvents.emitLocal('Notification', { + message: 'Error while saving bookmark' + }); + } + }; + + // ------------------------------------------------------------------------ + // Events + + var recreateBookmark = function recreateBookmark(item) { + delete item.id; + delete item.dateAdded; + delete item.dateGroupModified; + delete item.title_lowercase; + return BookmarkManager.createBookmark(item); + }; + + var undoHistoryEvent = function undoHistoryEvent() { + var sessionID = undefined; + var undoEvents = AppConfig.get('undo.events'); + + function onSuccess() { + AppConfig.set('undo.events', undoEvents); + WindowEvents.emit(document, 'SetPromiseSession', { sessionID: sessionID, update: false }); + GlobalEvents.emit('update-sessions'); + } + + if (undoEvents.length) { + var historyEvent = undoEvents.pop(); + + if (historyEvent.type == 'delete') { + switch (historyEvent.bookmark.type) { + case 'bookmark': + case 'separator': { + recreateBookmark(historyEvent.bookmark).then(function (bookmark) { + sessionID = bookmark.parentId == AppConfig.get('storageID') ? bookmark.id : bookmark.parentId; + onSuccess(); + }); + break; + } + + case 'folder': { + // Restore deleted folder + var oldID = historyEvent.bookmark.id; + + recreateBookmark(historyEvent.bookmark).then(function (node) { + sessionID = node.id; + var newID = node.id; + var updateEvent = new LimitCounter(historyEvent.items.length, onSuccess); + + // correct parent for each bookmark + if (oldID != newID) { + // correct restore event + historyEvent.items.forEach(function (bookmark) { + bookmark.parentId = newID; + recreateBookmark(bookmark).then(updateEvent.advance, updateEvent.advance); + }); + + // correct all undo events + undoEvents.forEach(function (event) { + event.items.forEach(function (bookmark) { + if (bookmark.parentId == oldID) { + bookmark.parentId = newID; + } + }); + }); + } + + if (historyEvent.items.length == 0) { + onSuccess(); + } + }); + break; + } + } + } + } + }; + + // Subscribe to management events + GlobalEvents.on('DeleteBookmarkItem', deleteBookmarkItem); + GlobalEvents.on('HistoryUndo', undoHistoryEvent); + + // ------------------------------------------------------------------------ + // Public API + + return { + getModel: getModel, + unregisterModel: unregisterModel, + registerModel: registerModel, + + domModels: domModels, + bookmarks: bookmarks, + moveBookmark: moveBookmark, + moveBookmarkTo: moveBookmarkTo, + + deleteBookmarkList: deleteBookmarkList, + updateBookmarkItem: updateBookmarkItem + }; +})(); diff --git a/src/scripts/components/session-toolbar.js b/src/scripts/components/session-toolbar.js new file mode 100644 index 0000000..b28fb9e --- /dev/null +++ b/src/scripts/components/session-toolbar.js @@ -0,0 +1,295 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +// App +import { AppConfig } from '../config'; + +// Utils +import { HTMLCreator } from '../utils/dom'; +import { WindowEvents } from '../utils/global-events'; +import * as DOMComponent from '../utils/components'; + +// ***************************************************************************** +// API + +/* + * Command bar + */ + +export function SessionToolbar() { + // Create DomHealper + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var toolbar = DomElem('div', { class: 'session-toolbar' }); + + // ------------------------------------------------------------------------ + // List settings + + var container = DomElem('div', { class: 'session-selector' }); + container.style.width = AppConfig.get('style.scale.bookmarks') + 'px'; + + var activeBtn = DomElem('div', { class: 'button active-session' }); + activeBtn.textContent = ''; + + var syncBtn = DomElem('div', { class: 'button sync' }); + syncBtn.textContent = 'Sessions'; + + var historyBtn = DomElem('div', { class: 'button history' }); + historyBtn.textContent = 'History'; + + container.appendChild(activeBtn); + container.appendChild(syncBtn); + container.appendChild(historyBtn); + toolbar.appendChild(container); + + // ------------------------------------------------------------------------ + // Session toolbar menu + + var sessionMenu = this.createMenu(); + toolbar.appendChild(sessionMenu); + + var sessionDate = DomElem('div', { class: 'session-date' }); + toolbar.appendChild(sessionDate); + + // ------------------------------------------------------------------------ + // Events + + if (typeof document !== 'undefined') { + WindowEvents.on(document, 'SetSessionInfo', function (sessionInfo) { + sessionDate.textContent = new Date(sessionInfo.dateAdded).toLocaleString(); + toolbar.setAttribute('session-type', sessionInfo.type); + }); + } + + activeBtn.addEventListener('click', function () { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'ShowCurrentSession'); + } + }); + + syncBtn.addEventListener('click', function () { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'ShowSyncList', { update: true }); + } + }); + + historyBtn.addEventListener('click', function () { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'ShowHistoryList'); + } + }); + + AppConfig.onChange('style.sessions.list.width', function (width) { + container.style.width = width + 'px'; + }); + + AppConfig.onChange('style.scale.toolbar', function (value) { + toolbar.style.fontSize = value + 'px'; + }); + + // ------------------------------------------------------------------------ + // Public properties + + this.document = typeof document !== 'undefined' ? document : null; + this.DOMRoot = toolbar; +} + +SessionToolbar.prototype.createMenu = function createMenu() { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // UI - SessionContainer menu + + var menu = DomElem('div', { class: 'menu-bar' }); + + var ActionButton = function actionButton(options) { + var button = DomElem('div', { + class: 'button ' + options.className, + 'state-current': options.state.current, + 'state-restore': options.state.restore, + 'state-history': options.state.history + }); + button.setAttribute('tooltip', options.tooltip); + + button.addEventListener('click', function (e) { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, options.event, e); + } + }); + + return button; + }; + + var save = ActionButton({ + tooltip: 'Save session', + className: 'save', + event: 'MenuSaveSession', + state: { + current: true, + history: true + } + }); + + var add = ActionButton({ + tooltip: 'Add current tab', + className: 'add', + event: 'MenuAddCurrentTab', + state: { + restore: true + } + }); + + var restore = ActionButton({ + tooltip: 'Restore', + className: 'restore', + event: 'MenuRestoreClick', + state: { + restore: true + } + }); + + var restoreW = ActionButton({ + tooltip: 'Restore (new window)', + className: 'restore-new-win', + event: 'MenuRestoreNewWindow', + state: { + restore: true, + history: true + } + }); + + var replaceSession = ActionButton({ + tooltip: 'Restore (close active)', + className: 'repalce-session', + event: 'ReplaceSession', + state: { + restore: true + } + }); + + var replaceSessionAll = ActionButton({ + tooltip: 'Restore (close all)', + className: 'repalce-session-all', + event: 'ReplaceSessionAll', + state: { + restore: true + } + }); + + var mergeSession = ActionButton({ + tooltip: 'Merge sessions', + className: 'merge-sessions', + event: 'MenuMergeSessions', + state: { + restore: true + } + }); + + var overwriteSession = ActionButton({ + tooltip: 'Overwrite session', + className: 'replace-session', + event: 'MenuReplaceSession', + state: { + restore: true + } + }); + + var separator1 = DomElem('div', { class: 'separator' }); + var separator2 = DomElem('div', { class: 'separator' }); + var saveConfig = DomElem('div', { class: 'save-config' }); + + // Pin tabs action + + var saveCfgKey = 'session.save'; + var saveCfg = AppConfig.get(saveCfgKey); + var cfgSavePinned = new DOMComponent.ToggleSwitch({ + state: saveCfg?.pinned, + tooltip: 'Save pinned tabs', + attribute: 'pin', + onState: '', + offState: '', + callback: function (value) { + saveCfg.pinned = value; + AppConfig.set(saveCfgKey, saveCfg); + } + }); + saveConfig.appendChild(cfgSavePinned.DOMRoot); + + var cfgAllWindows = new DOMComponent.ToggleSwitch({ + state: saveCfg?.allWindows, + tooltip: 'Show all windows', + attribute: 'windows', + onState: '', + offState: '', + callback: function (value) { + saveCfg.allWindows = value; + AppConfig.set(saveCfgKey, saveCfg); + } + }); + saveConfig.appendChild(cfgAllWindows.DOMRoot); + + menu.appendChild(restore); + menu.appendChild(restoreW); + menu.appendChild(replaceSession); + menu.appendChild(replaceSessionAll); + menu.appendChild(separator1); + menu.appendChild(mergeSession); + menu.appendChild(overwriteSession); + menu.appendChild(separator2); + menu.appendChild(add); + menu.appendChild(save); + menu.appendChild(saveConfig); + + // Toolbar customization + var customizeToolbar = ActionButton({ + tooltip: 'Customize Toolbar', + className: 'customize-toolbar', + event: 'OpenCustomizationToolbar', + state: { + restore: true + } + }); + + // menu.appendChild(customizeToolbar); + + // ------------------------------------------------------------------------ + // Events + + AppConfig.onChange(saveCfgKey, function (value) { + cfgSavePinned.setState(value.pinned); + cfgAllWindows.setState(value.allWindows); + }); + + // Tooltip events + menu.addEventListener('mouseover', function (e) { + var tooltipMsg = e.target.getAttribute('tooltip'); + if (tooltipMsg) { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'ShowTooltip', { + node: e.target, + message: tooltipMsg + }); + } + } else { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'HideTooltip'); + } + } + }); + + menu.addEventListener('mouseleave', function () { + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'HideTooltip'); + } + }); + + // ------------------------------------------------------------------------ + // Data + + return menu; +}; diff --git a/src/scripts/components/tooltip-system.js b/src/scripts/components/tooltip-system.js new file mode 100644 index 0000000..25f58b6 --- /dev/null +++ b/src/scripts/components/tooltip-system.js @@ -0,0 +1,41 @@ +'use strict'; + +// ***************************************************************************** +// Modules + +import { HTMLCreator } from '../utils/dom'; +import { WindowEvents } from '../utils/global-events'; + +// ***************************************************************************** +// TooltipSystem + +export function TooltipSystem() { + var DomElem = HTMLCreator(); + + var tooltip = DomElem('div', { class: 'tooltip-system' }); + + // ------------------------------------------------------------------------ + // Events + + var hideTooltip = function hideTooltip() { + tooltip.removeAttribute('active'); + }; + + var showTooltip = function showTooltip(options) { + tooltip.textContent = options.message; + tooltip.setAttribute('active', ''); + var position = options.node.getBoundingClientRect(); + + tooltip.setAttribute('active', ''); + tooltip.style.left = position.x - Math.floor((tooltip.clientWidth - options.node.clientWidth) / 2) + 'px'; + tooltip.style.top = position.y - tooltip.clientHeight + 'px'; + }; + + WindowEvents.on(document, 'ShowTooltip', showTooltip); + WindowEvents.on(document, 'HideTooltip', hideTooltip); + + // ------------------------------------------------------------------------ + // Public data + + this.DOMRoot = tooltip; +} diff --git a/src/scripts/components/trash-can.js b/src/scripts/components/trash-can.js new file mode 100644 index 0000000..f886708 --- /dev/null +++ b/src/scripts/components/trash-can.js @@ -0,0 +1,114 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +// App +import { AppConfig } from '../config'; + +import { WindowEvents, GlobalEvents } from '../utils/global-events'; +import { HTMLCreator } from '../utils/dom'; +import { DragContext } from '../utils/drag-context'; +import { SessionFolder } from './session-folder'; +import { SessionBookmark, SessionTab } from './session-bookmark'; + +// ***************************************************************************** +// API + +export function TrashCan() { + // Create DomHealper + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + var block = DomElem('div', { + class: 'trash-can', + items: 0, + dismiss: true, + tooltip: 'Drop item to delete' + }); + + var count = DomElem('div', { class: 'count', tooltip: 'Click to restore' }); + count.textContent = '0'; + block.appendChild(count); + + var dismiss = DomElem('div', { + class: 'dismiss css-close-button', + tooltip: 'Hide' + }); + + block.appendChild(dismiss); + + var configKey = { + hide: 'trashcan.hide', + hideCount: 'trashcan.hide-count', + undoEvents: 'undo.events' + }; + + // ------------------------------------------------------------------------ + // Events + + // History Undo/Redo events + count.addEventListener('click', function () { + GlobalEvents.emit('HistoryUndo'); + }); + + function updateItemCount(value) { + count.textContent = value; + block.setAttribute('items', value); + AppConfig.set(configKey.hide, value == 0 || value == AppConfig.get(configKey.hideCount)); + } + + block.addEventListener('mouseup', function (e) { + var item = DragContext.getContext(); + if (item instanceof SessionFolder || item instanceof SessionBookmark) { + block.setAttribute('action', 'drop'); + GlobalEvents.emit('DeleteBookmarkItem', item.bookmarkID); + WindowEvents.emit(document, 'ShowTooltip', { + node: e.target, + message: 'Click to restore' + }); + return; + } + + if (item instanceof SessionTab) { + GlobalEvents.emit('management.close-tab', item.tab.id); + } + }); + + dismiss.addEventListener('click', function () { + AppConfig.set(configKey.hide, true); + AppConfig.set(configKey.hideCount, AppConfig.get(configKey.undoEvents).length); + }); + + // Tooltip events + block.addEventListener('mouseover', function (e) { + WindowEvents.emit(document, 'ShowTooltip', { + node: e.target, + message: e.target.getAttribute('tooltip') + }); + }); + + block.addEventListener('mouseleave', function () { + WindowEvents.emit(document, 'HideTooltip'); + }); + + WindowEvents.on(document, 'TrashCan-Droppable', function (value) { + block.setAttribute('droppable', value); + }); + + // ------------------------------------------------------------------------ + // Events + + AppConfig.onChange(configKey.hide, function (value) { + block.setAttribute('dismiss', value); + }); + + AppConfig.onChange(configKey.undoEvents, function (value) { + updateItemCount(value.length); + }); + + // ------------------------------------------------------------------------ + // Public properties + + this.DOMRoot = block; +} diff --git a/src/scripts/components/url-bar.js b/src/scripts/components/url-bar.js new file mode 100644 index 0000000..4e23570 --- /dev/null +++ b/src/scripts/components/url-bar.js @@ -0,0 +1,80 @@ +'use strict'; + +// ------------------------------------------------------------------------ +// Modules + +import { HTMLCreator } from '../utils/dom'; +import { WindowEvents } from '../utils/global-events'; + +// ------------------------------------------------------------------------ +// API + +export function UrlBar() { + var DomElem = HTMLCreator(); + + var url_bar = DomElem('div', { class: 'url-bar' }); + var url_arrow = DomElem('div', { class: 'arrow' }); + var url_value = DomElem('div', { class: 'url' }); + var copy_feedback = DomElem('div', { class: 'copy-feedback' }); + var input = DomElem('input', { class: 'copy-input' }); + + copy_feedback.textContent = 'Copied'; + url_arrow.textContent = 'url'; + url_bar.appendChild(input); + url_bar.appendChild(url_arrow); + url_bar.appendChild(url_value); + url_bar.appendChild(copy_feedback); + + // ------------------------------------------------------------------------ + // Events + + var timeoutID = 0; + var feedbackTimeoutID = 0; + + function clearTimeID() { + clearTimeout(timeoutID); + } + + function showURLInfoBar(url) { + clearTimeout(timeoutID); + url_value.textContent = url; + url_bar.setAttribute('data-active', 'true'); + url_bar.addEventListener('mouseenter', clearTimeID); + url_bar.addEventListener('mouseleave', hideURLInfoBar); + } + + function hideURLInfoBar() { + timeoutID = setTimeout(function () { + url_bar.removeAttribute('data-active'); + url_bar.removeEventListener('mouseleave', clearTimeID); + url_bar.removeEventListener('mouseenter', hideURLInfoBar); + }, 1000); + } + + function copyURLToClipboard() { + input.value = url_value.textContent; + input.select(); + document.execCommand('copy'); + + clearTimeout(feedbackTimeoutID); + copy_feedback.setAttribute('active', ''); + feedbackTimeoutID = setTimeout(function () { + copy_feedback.removeAttribute('active'); + }, 1500); + } + + WindowEvents.on(document, 'ShowUrlBar', showURLInfoBar); + WindowEvents.on(document, 'HideUrlBar', hideURLInfoBar); + WindowEvents.on(document, 'UrlBar-CopyURL', copyURLToClipboard); + + url_bar.addEventListener('click', copyURLToClipboard); + + // ------------------------------------------------------------------------ + // Public data + + this.DOMRoot = url_bar; +} + +UrlBar.prototype.setAutoHideDelay = function setAutoHideDelay(miliseconds) { + this.hide_delay = miliseconds; +}; diff --git a/src/scripts/config.js b/src/scripts/config.js new file mode 100644 index 0000000..0ee47ea --- /dev/null +++ b/src/scripts/config.js @@ -0,0 +1,183 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { PubSub } from './3rd/pubsub'; + +// Utils +import { GlobalEvents } from './utils/global-events'; + +// ***************************************************************************** +// API + +const Config = (function () { + const defaults = { + 'style.panel.width': 800, + 'style.panel.height': 600, + 'style.sessions.list.width': 220, + + 'style.scale.toolbar': 12, + 'style.scale.header': 18, + 'style.scale.sessions': 12, + 'style.scale.bookmarks': 14, + + 'state.scrollTop.current': 0, + 'state.scrollTop.restore': 0, + 'state.scrollTop.history': 0, + + 'session.save': { + pinned: false, + allWindows: true + }, + + 'session.view': undefined, + 'session.sorting': 'position-asc', + 'session.active.filter': '', + 'session.selected': null, + 'session.history.selected': null, + 'services.favicon.enabled': true, + + 'bookmark.click.newTab': false, + 'bookmark.middleClick.newTab': true, + 'restore.lazy.loading': true, + 'restore.reverse.order': false, + + 'context.menu.icons': true, + 'scroll.bar.slim': true, + 'trashcan.hide': true, + 'trashcan.hide-count': 0, + 'undo.events': [], + troubleshooting: false, + 'theme.toolbar.grayscale': false + }; + + var initState = true; + var initConfig = true; + var localSettings = defaults; + var configEvents = new PubSub(); + + // ------------------------------------------------------------------------ + // API + + var setupConfig = function setupConfig() { + browser.storage.local.get(null).then(function (obj) { + for (let key in defaults) { + if (obj.hasOwnProperty(key)) { + localSettings[key] = obj[key]; + } else { + localSettings[key] = defaults[key]; + } + configEvents.publish(key, localSettings[key]); + } + initConfig = false; + configEvents.publish('config.init', localSettings); + }); + }; + + var set = function set(key, value, onSuccess) { + if (typeof value != 'object' && localSettings[key] == value) { + if (typeof onSuccess === 'function') { + onSuccess(); + } + return; + } + + browser.storage.local.set({ [key]: value }).then( + function success() { + localSettings[key] = value; + // console.log('[Change config]', key, value); + configEvents.publish(key, value); + if (typeof onSuccess === 'function') { + onSuccess(); + } + }, + function onError(error) { + console.error(error); + } + ); + }; + + var get = function get(key) { + if (localSettings[key] === undefined) { + console.error('[ERROR] Settings key [' + key + '] was not found!'); + } + return localSettings[key]; + }; + + var isInitState = function isInitState() { + if (initState == true) { + initState = false; + return true; + } + return false; + }; + + var isPanel = function isPanel() { + if (typeof browser === 'undefined' || !browser.extension || !browser.extension.getViews) { + return false; + } + return browser.extension.getViews({ type: 'popup' }).includes(window); + }; + + var devMode = function devMode() { + return false; + }; + + var isAddonContext = function isAddonContext() { + return typeof browser === 'object'; + }; + + var onChange = function onChange(topic, callback) { + configEvents.subscribe(topic, callback); + }; + + // ------------------------------------------------------------------------ + // Startup + + var init = function init() { + setupConfig(); + + if (isAddonContext()) { + browser.commands.getAll().then(function (commands) { + configEvents.publish('config.hotkey.init', commands); + }); + } + }; + + if (isAddonContext()) { + // console.log(''); + // console.log('------------------------------------------'); + + // var manifest = browser.runtime.getManifest(); + // console.log(manifest); + // console.log(manifest.name + ' v' + manifest.version); + // console.log('------------------------------------------'); + + setupConfig(); + initConfig = false; + + GlobalEvents.on('hotkey.update', function (command) { + browser.commands.update({ + name: command.name, + shortcut: command.shortcut + }); + }); + } + + // ------------------------------------------------------------------------ + // Public API + + return { + set: set, + get: get, + init: init, + onChange: onChange, + devMode: devMode, + isPanel: isPanel, + isInitState: isInitState, + isAddonContext: isAddonContext + }; +})(); + +export const AppConfig = Config; diff --git a/src/scripts/session-sync-ui.js b/src/scripts/session-sync-ui.js new file mode 100644 index 0000000..9f45227 --- /dev/null +++ b/src/scripts/session-sync-ui.js @@ -0,0 +1,471 @@ +'use strict'; + +// ************************************************************************ +// Modules + +// App +import { AppConfig } from './config'; + +// Utils +import { HTMLCreator } from './utils/dom'; +import { WindowEvents, GlobalEvents } from './utils/global-events'; + +import { ContextMenu } from './utils/context-menu'; +import { ConfirmBox } from './utils/confirm-box'; +import { FieldEditWdiget } from './utils/field-edit-widget'; + +// Components +import { Clipboard } from './components/clipboard'; +import { UrlBar } from './components/url-bar'; +import { TrashCan } from './components/trash-can'; +import { TooltipSystem } from './components/tooltip-system'; +import { SessionHotkeys } from './components/session-hotkeys'; +import { NotificationSystem } from './components/notification-system'; +import { ConfigPanel } from './components/config-panel'; +import { OverlaySystem } from './components/overlay-system'; + +import { SessionHotkeyManager } from './components/session-hotkey-manager'; +import { SessionSorting } from './components/session-sorting'; +import { SessionFiltering } from './components/session-filtering'; + +import { SessionHeaderBar } from './components/session-header-bar'; +import { SessionList } from './components/session-list'; +import { SessionContainer } from './components/session-container'; +import { SessionSyncModel } from './components/session-sync-model'; +import { SessionToolbar } from './components/session-toolbar'; + +// Bookmarks +import { SessionManager } from './session/management'; +import { BookmarkManager } from './session/bookmarks'; + +// ************************************************************************ +// UI initilization + +export const SessionSyncUI = function SessionSyncUI() { + var DomElem = HTMLCreator(); + + document.body.setAttribute('panel', AppConfig.isPanel()); + document.body.setAttribute('scrollbar', AppConfig.get('scroll.bar.slim')); + var content = document.getElementById('body'); + + // ------------------------------------------------------------------------ + // UI Modules + + var configPanel = new ConfigPanel(); + content.appendChild(configPanel.DOMRoot); + + var tooltipSystem = new TooltipSystem(); + content.appendChild(tooltipSystem.DOMRoot); + + var notificationSystem = new NotificationSystem(); + content.appendChild(notificationSystem.DOMRoot); + + var hotekyManager = new SessionHotkeyManager(); + content.appendChild(hotekyManager.DOMRoot); + + var overlaySystem = new OverlaySystem(); + content.appendChild(overlaySystem.DOMRoot); + + // ------------------------------------------------------------- + // Create the UI + var centralArea = DomElem('div', { class: 'sessions-area' }); + + // ------------------------------------------------------------- + // Clipboard + + new Clipboard(); + + // ------------------------------------------------------------- + // GlobalHotkeys + + new SessionHotkeys(); + + // -------------------------------------------------------------------- + // Center area + + var headerBar = new SessionHeaderBar(); + var toolbar = new SessionToolbar(); + var sessionList = new SessionList(); + var searchBar = new SessionFiltering(); + var sessionContainer = new SessionContainer(); + var sessionSorting = new SessionSorting(); + + headerBar.DOMRoot.appendChild(searchBar.DOMRoot); + sessionList.DOMRoot.appendChild(sessionSorting.DOMRoot); + + var body = DomElem('div', { class: 'sessions-area-body' }); + body.appendChild(sessionList.DOMRoot); + body.appendChild(sessionContainer.DOMRoot); + + var resizeHandle = DomElem('div', { class: 'panel-resize-handle' }); + + // ------------------------------------------------------------- + // Trash can + + var trashCan = new TrashCan(); + centralArea.appendChild(trashCan.DOMRoot); + + // -------------------------------------------------------------------- + // URL bar + + var urlBar = new UrlBar(); + headerBar.DOMRoot.appendChild(urlBar.DOMRoot); + + centralArea.appendChild(resizeHandle); + centralArea.appendChild(headerBar.DOMRoot); + centralArea.appendChild(toolbar.DOMRoot); + centralArea.appendChild(body); + content.appendChild(centralArea); + + // ------------------------------------------------------------------------ + // Context Menues + + // Bookmark Item Context Menu + var BM = new ContextMenu({ name: 'BookmarkCtxMenu' }); + BM.addMenuEntry({ value: 'Open (new tab)', event: 'OpenInNewTab', icon: 'new-tab' }); + if (AppConfig.isPanel()) { + BM.addMenuEntry({ value: 'Open (current tab)', event: 'OpenInActiveTab', icon: 'same-tab' }); + } + BM.addMenuEntry({ value: 'Open (new window)', event: 'OpenInNewWindow', icon: 'new-window' }); + BM.addMenuEntry({ value: 'Copy URL', event: 'CopyURL', icon: 'copy' }); + BM.addMenuEntry({ value: 'Copy Title', event: 'CopyTitle', icon: 'copy' }); + BM.addMenuEntry({ value: 'Edit', event: 'EditBookmark', icon: 'edit' }); + BM.addMenuEntry({ value: 'Delete', globalEvent: 'DeleteBookmarkItem', icon: 'delete', separator: 'top' }); + content.appendChild(BM.DOMRoot); + + // Session Context Menu + var SM = new ContextMenu({ name: 'SessionContextMenu' }); + SM.addMenuEntry({ value: 'Restore', event: 'RestoreSession', icon: 'same-window' }); + SM.addMenuEntry({ value: 'Restore (new window)', event: 'RestoreInNewWindow', icon: 'new-window' }); + SM.addMenuEntry({ value: 'Restore (close active)', event: 'ReplaceSession', icon: 'replace-current' }); + SM.addMenuEntry({ value: 'Restore (close all)', event: 'ReplaceSessionAll', icon: 'replace-all' }); + SM.addMenuEntry({ value: 'Edit', event: 'EditSession', icon: 'edit' }); + SM.addMenuEntry({ value: 'Delete', globalEvent: 'DeleteBookmarkItem', icon: 'delete' }); + SM.addMenuEntry({ + value: 'Create new session', + callback: SessionManager.createNewSession, + icon: 'new-session', + separator: 'top' + }); + SM.addMenuEntry({ value: 'Insert separator', callback: SessionManager.createSeparator, icon: 'new-session' }); + content.appendChild(SM.DOMRoot); + + var SCBM = new ContextMenu({ name: 'SessionContextBookmarkMenu' }); + SCBM.addMenuEntry({ value: 'Edit', event: 'EditBookmark', icon: 'edit' }); + SCBM.addMenuEntry({ value: 'Delete', globalEvent: 'DeleteBookmarkItem', icon: 'delete' }); + SCBM.addMenuEntry({ + value: 'Create new session', + callback: SessionManager.createNewSession, + icon: 'new-session', + separator: 'top' + }); + SCBM.addMenuEntry({ value: 'Insert separator', callback: SessionManager.createSeparator, icon: 'new-session' }); + content.appendChild(SCBM.DOMRoot); + + var SCSM = new ContextMenu({ name: 'SessionContextSeparatorMenu' }); + SCSM.addMenuEntry({ value: 'Delete', globalEvent: 'DeleteBookmarkItem', icon: 'delete' }); + SCSM.addMenuEntry({ + value: 'Create new session', + callback: SessionManager.createNewSession, + icon: 'new-session', + separator: 'top' + }); + SCSM.addMenuEntry({ value: 'Insert separator', callback: SessionManager.createSeparator, icon: 'new-session' }); + content.appendChild(SCSM.DOMRoot); + + var SLM = new ContextMenu({ name: 'SessionListMenu' }); + SLM.addMenuEntry({ value: 'Create new session', callback: SessionManager.createNewSession, icon: 'new-session' }); + content.appendChild(SLM.DOMRoot); + + // History List Context Menu + var confirmDeleteAll = function confirmDeleteAll(context, event) { + WindowEvents.emit(document, 'ConfirmBox-Open', { + event: event, + message: 'Clear history?', + callback: function () { + GlobalEvents.emit('HistorySessionDeleteAll'); + } + }); + }; + + var HSML = new ContextMenu({ name: 'HistoryListCtxMenu' }); + HSML.addMenuEntry({ value: 'Delete All', callback: confirmDeleteAll, icon: 'delete' }); + content.appendChild(HSML.DOMRoot); + + // History Items Context Menu + + var confirmDeleteOne = function confirmDeleteOne(context, event) { + WindowEvents.emit(document, 'ConfirmBox-Open', { + event: event, + message: 'Confirm delete?', + callback: function () { + GlobalEvents.emit('HistorySessionDelete', context.getAttribute('index')); + } + }); + }; + + var HSM = new ContextMenu({ name: 'HistorySessionCtxMenu' }); + HSM.addMenuEntry({ value: 'Save', event: 'Save', icon: 'heart' }); + HSM.addMenuEntry({ value: 'Restore (new window)', event: 'Restore', icon: 'new-window' }); + HSM.addMenuEntry({ value: 'Delete', callback: confirmDeleteOne, icon: 'delete', separator: 'top' }); + content.appendChild(HSM.DOMRoot); + + // History Context Menu + var HBM = new ContextMenu({ name: 'HistoryMarksCtxMenu' }); + HBM.addMenuEntry({ value: 'Open (new tab)', event: 'OpenInNewTab', icon: 'new-tab' }); + if (AppConfig.isPanel()) { + HBM.addMenuEntry({ value: 'Open (current tab)', event: 'OpenInActiveTab', icon: 'same-tab' }); + } + HBM.addMenuEntry({ value: 'Open (new window)', event: 'OpenInNewWindow', icon: 'new-window' }); + HBM.addMenuEntry({ value: 'Copy URL', event: 'CopyURL', icon: 'copy' }); + content.appendChild(HBM.DOMRoot); + + // Field edit widgets + var BEP = new FieldEditWdiget({ name: 'BookmarkEditWidget' }); + BEP.addField({ label: 'Name', name: 'title' }); + BEP.addField({ label: 'Location', name: 'url' }); + // BEP.addField({label: 'Description', name: 'desc'}); + centralArea.appendChild(BEP.DOMRoot); + + // Field edit Info + var SEP = new FieldEditWdiget({ name: 'SessionFolderEditWidget' }); + SEP.addField({ label: 'Title', name: 'title' }); + // SEP.addField({label: 'Description', name: 'desc'}); + centralArea.appendChild(SEP.DOMRoot); + + // Confirm Box + var CB = new ConfirmBox({ name: 'ConfirmBox' }); + content.appendChild(CB.DOMRoot); + + // ------------------------------------------------------------------------ + // Panel States + + var UI = content.parentElement; + function setUIState(props) { + var model = SessionSyncModel.getModel(document); + for (var key in props) { + model.state[key] = props[key]; + props[key] !== undefined ? UI.setAttribute(key, props[key]) : UI.removeAttribute(key); + } + } + + // ------------------------------------------------------------------------ + // App Events + + WindowEvents.on(document, 'SetUIState', setUIState); + + WindowEvents.on(document, 'CopyURL', function (url) { + WindowEvents.emit(document, 'ShowUrlBar', url); + WindowEvents.emit(document, 'UrlBar-CopyURL'); + }); + + WindowEvents.on(document, 'CopyText', function () { + WindowEvents.emit(document, 'UrlBar-CopyURL'); + }); + + // SessionContextMenu events + + WindowEvents.on(document, 'SessionContextMenu-EditSession', function (sessionID) { + if (SessionSyncModel.bookmarks[sessionID]) { + var type = SessionSyncModel.bookmarks[sessionID].type; + switch (type) { + case 'folder': + WindowEvents.emit(document, 'SessionFolderEditWidget-Invoke', { + context: sessionID, + fields: { + title: SessionSyncModel.bookmarks[sessionID].title + } + }); + break; + + case 'bookmark': + WindowEvents.emit(document, 'BookmarkCtxMenu-EditBookmark', sessionID); + break; + } + } + }); + + WindowEvents.on(document, 'SessionContextMenu-RestoreSession', SessionManager.restoreSession); + WindowEvents.on(document, 'SessionContextMenu-RestoreInNewWindow', SessionManager.restoreNewWindow); + WindowEvents.on(document, 'SessionContextMenu-ReplaceSession', SessionManager.replaceSession); + WindowEvents.on(document, 'SessionContextMenu-ReplaceSessionAll', SessionManager.replaceSessionAll); + + // BookmarksCtxMenu events + + function InvokeBookmarkEdit(bookmarkID) { + WindowEvents.emit(document, 'BookmarkEditWidget-Invoke', { + context: bookmarkID, + fields: { + title: SessionSyncModel.bookmarks[bookmarkID].title, + url: SessionSyncModel.bookmarks[bookmarkID].url + } + }); + } + + WindowEvents.on(document, 'SessionContextBookmarkMenu-EditBookmark', InvokeBookmarkEdit); + WindowEvents.on(document, 'BookmarkCtxMenu-EditBookmark', InvokeBookmarkEdit); + + WindowEvents.on(document, 'BookmarkCtxMenu-CopyURL', function (bookmarkID) { + WindowEvents.emit(document, 'CopyURL', SessionSyncModel.bookmarks[bookmarkID].url); + }); + + WindowEvents.on(document, 'BookmarkCtxMenu-CopyTitle', function (bookmarkID) { + WindowEvents.emit(document, 'CopyToClipboard', SessionSyncModel.bookmarks[bookmarkID].title); + }); + + WindowEvents.on(document, 'BookmarkCtxMenu-OpenInNewTab', function (bookmarkID) { + var mark = SessionSyncModel.bookmarks[bookmarkID]; + BookmarkManager.openBookmark(mark.url, { + title: mark.title, + mode: 'newTab' + }); + }); + + WindowEvents.on(document, 'BookmarkCtxMenu-OpenInActiveTab', function (bookmarkID) { + var mark = SessionSyncModel.bookmarks[bookmarkID]; + BookmarkManager.openBookmark(mark.url, { + title: mark.title, + mode: 'activeTab' + }); + }); + + WindowEvents.on(document, 'BookmarkCtxMenu-OpenInNewWindow', function (bookmarkID) { + var mark = SessionSyncModel.bookmarks[bookmarkID]; + BookmarkManager.openBookmark(mark.url, { + title: mark.title, + mode: 'newWindow' + }); + }); + + // HistoryMarksCtxMenu events + + WindowEvents.on(document, 'HistoryMarksCtxMenu-CopyURL', function (url) { + WindowEvents.emit(document, 'CopyURL', url); + }); + + WindowEvents.on(document, 'HistoryMarksCtxMenu-OpenInNewTab', function (e) { + BookmarkManager.openHistoryTabFromContext(e, 'newTab'); + }); + + WindowEvents.on(document, 'HistoryMarksCtxMenu-OpenInActiveTab', function (e) { + BookmarkManager.openHistoryTabFromContext(e, 'activeTab'); + }); + + WindowEvents.on(document, 'HistoryMarksCtxMenu-OpenInNewWindow', function (e) { + BookmarkManager.openHistoryTabFromContext(e, 'newWindow'); + }); + + // HistorySessionCtxMenu events + + WindowEvents.on(document, 'HistorySessionCtxMenu-Save', function (context) { + WindowEvents.emit(document, 'HistorySessionSave', context.getAttribute('index')); + }); + + WindowEvents.on(document, 'HistorySessionCtxMenu-Restore', function (context) { + WindowEvents.emit(document, 'HistorySessionRestore', context.getAttribute('index')); + }); + + WindowEvents.on(document, 'HistorySessionCtxMenu-Delete', function (context) { + GlobalEvents.emit('HistorySessionDelete', context.getAttribute('index')); + }); + + // Edit events + + function updateBookmarkInfo(data) { + SessionSyncModel.updateBookmarkItem(data.context, data.fields); + } + + WindowEvents.on(document, 'SessionFolderEditWidget-Save', updateBookmarkInfo); + WindowEvents.on(document, 'BookmarkEditWidget-Save', updateBookmarkInfo); + + var startResize = function startResize(e) { + var startPosX = e.clientX; + var startPosY = e.clientY; + var initWidth = document.body.clientWidth; + var initHeight = document.body.clientHeight; + + resizeHandle.setAttribute('resizing', 'true'); + WindowEvents.emit(document, 'SetUIState', { resize: 'true' }); + + function eventMove(e) { + var deltaX = e.clientX - startPosX; + var deltaY = e.clientY - startPosY; + + var newWidth = Math.max(Math.min(initWidth - deltaX, 800), 500); + var newHeight = Math.max(Math.min(initHeight + deltaY, 600), 400); + + // This is required only for when document body is changing at the same time + // such is the case with Webextensions panels + initWidth = newWidth; + + AppConfig.set('style.panel.width', newWidth); + AppConfig.set('style.panel.height', newHeight); + } + + function evenUp() { + WindowEvents.emit(document, 'SetUIState', { resize: undefined }); + resizeHandle.removeAttribute('resizing'); + document.removeEventListener('mouseup', evenUp); + document.removeEventListener('mousemove', eventMove); + } + + document.addEventListener('mouseup', evenUp); + document.addEventListener('mousemove', eventMove); + }; + + function setPanelWidth(width) { + document.body.style.width = width + 'px'; + } + + function setPanelHeight(height) { + document.body.style.height = height + 'px'; + } + + function setScrollbarType(value) { + document.body.setAttribute('scrollbar', value ? 'slim' : 'wide'); + } + + if (AppConfig.isPanel()) { + // Apply immediately from current config (handles Chrome popup where onChange + // events from setupConfig fire before these listeners are registered) + setPanelWidth(AppConfig.get('style.panel.width')); + setPanelHeight(AppConfig.get('style.panel.height')); + + AppConfig.onChange('style.panel.width', setPanelWidth); + AppConfig.onChange('style.panel.height', setPanelHeight); + + resizeHandle.addEventListener('mousedown', startResize); + } else { + window.addEventListener('resize', function () { + var newWidth = Math.max(window.innerWidth, 500); + var newHeight = Math.max(window.innerHeight, 400); + + browser.storage.local.set({ + 'style.window.detach': { + // left: window.screenX, + // top: window.screenY, + width: newWidth, + height: newHeight + } + }); + }); + document.body.style.minWidth = '500px'; + } + + function preventDefault(e) { + e.preventDefault(); + e.stopPropagation(); + } + + // Handle scrollbar setting + AppConfig.onChange('scroll.bar.slim', setScrollbarType); + + document.body.addEventListener('contextmenu', preventDefault); + document.body.addEventListener('mousedown', function (e) { + // Middle Click + if (e.button == 1) preventDefault(e); + }); + + // ------------------------------------------------------------------------ + // App Init +}; diff --git a/src/scripts/session/bookmarks.js b/src/scripts/session/bookmarks.js new file mode 100644 index 0000000..f843057 --- /dev/null +++ b/src/scripts/session/bookmarks.js @@ -0,0 +1,247 @@ +'use strict'; + +// ************************************************************************ +// Modules + +import { AppConfig } from '../config'; +import { BookmarkProperties } from '../../common/bookmark-properties'; +import { HistoryManager } from './history'; + +// ************************************************************************ +// API + +export const BookmarkManager = (function BookmarkManager() { + var getHostName = function getHostName(url) { + try { + return new URL(url).hostname; + } catch (e) { + return ''; + } + }; + + var getFaviconUrlFromStorage = function getFaviconUrlFromStorage(favIconUrl, callback) { + var key = '@favIconUrl:' + getHostName(favIconUrl); + return browser.storage.local.get(key).then(function (obj) { + if (obj[key]) { + callback(obj[key]); + } + }); + }; + + var getFaviconUrl = function getFaviconUrl(url, callback) { + if (!url) return callback(''); + + if (import.meta.env.BROWSER === 'firefox') { + getFaviconUrlFromStorage(url, callback); + return; + } + + const faviconUrl = browser.runtime.getURL(`_favicon/?pageUrl=${encodeURIComponent(url)}&size=32`); + callback(faviconUrl); + return; + }; + + var getFolderBookmarks = function getFolderBookmarks(folderID, callback) { + var result = browser.bookmarks.getChildren(folderID); + result.then(callback, function fail() { + console.error('Error getting bookmarks from folder ID: ', folderID); + }); + }; + + var searchBookmarks = function searchBookmarks(keyword) { + return browser.bookmarks.search(keyword); + }; + + var getOpenMode = function getOpenMode(index) { + var buttons = ['click', 'middleClick']; + var state = AppConfig.get('bookmark.' + buttons[index] + '.newTab'); + return state ? 'newTab' : 'activeTab'; + }; + + var createBookmarkFromTab = async function createBookmarkFromTab(tab, parentId) { + var url = tab.url; + browser.runtime.sendMessage({ event: 'save-favicon', tab: tab }); + + var title = tab.title; + + if (tab.groupId !== undefined && tab.groupId > -1 && !tab.groupTitle && typeof browser !== 'undefined' && browser.tabGroups) { + try { + const group = await browser.tabGroups.get(tab.groupId); + tab.groupTitle = group.title; + tab.groupColor = group.color; + } catch (e) { + console.error('Error fetching tab group info', e); + } + } + + if (tab.cookieStoreId || tab.pinned || (tab.groupId !== undefined && tab.groupId > -1)) { + title += BookmarkProperties.encode(tab); + } + + return browser.bookmarks.create({ + url: url, + title: title, + parentId: parentId + }); + }; + + var createBookmark = function createBookmark(options) { + // parentId possible values + // root________ + // menu________ + // toolbar_____ + // unfiled_____ + + return browser.bookmarks.create(options); + }; + + var openBookmark = function openBookmark(url, info) { + var options = { + url: url + }; + + info.favicon = true; + + if (AppConfig.isPanel() == false) { + options.active = false; + if (info.mode === 'activeTab') { + info.mode = 'newTab'; + } + } + + if (info.title) { + var bookmarkInfo = BookmarkProperties.parseTitle(info.title); + if (bookmarkInfo.properties.container) { + options.cookieStoreId = bookmarkInfo.properties.container; + } + + if (bookmarkInfo.properties.pinned) { + options.pinned = true; + } + + if (bookmarkInfo.properties.group) { + info.group = bookmarkInfo.properties.group; + } + } + + browser.runtime.sendMessage({ event: 'open-tab', options: options, info: info }); + }; + + function getTabID(e) { + var ID = e.target.getAttribute('tabID'); + return ID ? ID | 0 : null; + } + + function getWindowID(e) { + var ID = e.target.getAttribute('windowID'); + return ID ? ID | 0 : null; + } + + var openHistoryTabFromContext = function openHistoryTabFromContext(e, mode) { + let windowID = getWindowID(e); + let tabID = getTabID(e); + let tab = HistoryManager.getHistoryTab(windowID, tabID); + if (tab) { + openHistoryTab(tab, mode); + } + }; + + var openHistoryTab = function openHistoryTab(tab, mode) { + var options = { + url: tab.url + }; + + var info = { mode: mode }; + + if (AppConfig.isPanel() == false) { + options.active = false; + if (mode === 'activeTab') { + mode = 'newTab'; + } + } + + if (tab.cookieStoreId) { + options.cookieStoreId = tab.cookieStoreId; + } + + if (tab.pinned) { + options.pinned = true; + } + + if (tab.groupId) { + info.group = { + title: tab.groupTitle, + color: tab.groupColor + }; + } + + browser.runtime.sendMessage({ event: 'open-tab', options: options, info: info }); + }; + + var setTitle = function setTitle(bookmark, title, callback) { + browser.bookmarks + .update(bookmark.id, { + title: title + }) + .then( + function success() { + bookmark.title = title; + bookmark.title_lowercase = title.toLowerCase(); + if (typeof callback === 'function') callback(true); + }, + function error() { + throw 'error'; + } + ); + }; + + var setLocation = function setLocation(bookmark, url) { + browser.bookmarks + .update(bookmark.id, { + url: url + }) + .then( + function success() { + bookmark.url = url; + }, + function error() { + throw 'error'; + } + ); + }; + + var moveItem = function moveItem(itemID, newParentID, index, callback, errorCallback) { + browser.bookmarks + .move(itemID, { + parentId: newParentID, + index: index + }) + .then(callback, errorCallback); + }; + + var deleteItem = function deleteItem(itemID, isFolder, callback, errorCallback) { + if (isFolder) { + browser.bookmarks.removeTree(itemID).then(callback, errorCallback); + } else { + browser.bookmarks.remove(itemID).then(callback, errorCallback); + } + }; + + return { + getFaviconUrl: getFaviconUrl, + getOpenMode: getOpenMode, + openBookmark: openBookmark, + openHistoryTab: openHistoryTab, + openHistoryTabFromContext: openHistoryTabFromContext, + createBookmark: createBookmark, + createBookmarkFromTab: createBookmarkFromTab, + searchBookmarks: searchBookmarks, + + setTitle: setTitle, + setLocation: setLocation, + moveItem: moveItem, + deleteItem: deleteItem, + + getFolderBookmarks: getFolderBookmarks + }; +})(); diff --git a/src/scripts/session/history.js b/src/scripts/session/history.js new file mode 100644 index 0000000..4fecbbd --- /dev/null +++ b/src/scripts/session/history.js @@ -0,0 +1,26 @@ +'use strict'; + +// ************************************************************************ +// API + +export const HistoryManager = (function HistoryManager() { + var selectedSessions; + + var selectSession = function selectSession(sessionInfo) { + selectedSessions = sessionInfo; + }; + + var getSelectedSession = function getSelectedSession() { + return selectedSessions; + }; + + var getHistoryTab = function getHistoryTab(windowID, tabID) { + return selectedSessions.windows[windowID].tabs[tabID]; + }; + + return { + selectSession: selectSession, + getSelectedSession: getSelectedSession, + getHistoryTab: getHistoryTab + }; +})(); diff --git a/src/scripts/session/management.js b/src/scripts/session/management.js new file mode 100644 index 0000000..31ae197 --- /dev/null +++ b/src/scripts/session/management.js @@ -0,0 +1,383 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { AppConfig } from '../config'; +import { BookmarkManager } from './bookmarks'; +import { WindowEvents, GlobalEvents } from '../utils/global-events'; +import { LimitCounter } from '../utils/general'; +import { SessionSyncModel } from '../components/session-sync-model'; + +// ***************************************************************************** + +export const SessionManager = (function SessionManager() { + var trackTabs = true; + + var getCurrentTab = function getCurrentTab(callback) { + browser.tabs.query({ active: true, windowId: browser.windows.WINDOW_ID_CURRENT }).then(function (tabs) { + callback(tabs[0]); + }); + }; + + var getWindowTab = function getWindowTab(tabIndex, windowId, callback) { + browser.tabs.query({ index: tabIndex, windowId: windowId }).then(function (tabs) { + callback(tabs[0]); + }); + }; + + var getCurrentWindow = function getCurrentWindow(callback) { + browser.windows.getCurrent({ populate: true }).then(callback); + }; + + var getAllWindows = function getAllWindows(callback) { + browser.windows + .getAll({ + populate: true, + windowTypes: ['normal'] + }) + .then(callback); + }; + + var tabTracking = function tabTracking() { + return trackTabs; + }; + + var moveTab = function moveTab(tabID, newIndex, windowID) { + trackTabs = false; + browser.tabs + .move([tabID], { + index: newIndex, + windowId: windowID + }) + .then( + function () { + GlobalEvents.emit('UpdateCurrentSession'); + trackTabs = true; + }, + function onError() { + trackTabs = true; + } + ); + }; + + var activateTab = function activateTab(tabID) { + browser.tabs.update(tabID, { + active: true + }); + }; + + var closeTab = function closeTab(tabID) { + browser.tabs.remove(tabID); + }; + + var activateWindow = function activateWindow(windowID) { + browser.windows.update(windowID, { + focused: true + }); + }; + + var saveActiveSession = function saveActiveSession() { + getCurrentWindow(function (mozWindow) { + var sessionID = 0; + var sessionCount = 0; + + function updateOnSave() { + WindowEvents.emit(document, 'SetPromiseSession', { + update: true, + sessionID: sessionID, + edit: sessionCount == 1 + }); + } + + // Get non-private windows + if (mozWindow.incognito == true || AppConfig.get('session.save').allWindows == false) { + var sessionTitle = new Date().toLocaleString(); + saveWindowSession( + { + title: sessionTitle, + tabs: mozWindow.tabs + }, + function onSuccess(folder) { + sessionID = folder.id; + sessionCount++; + updateOnSave(); + } + ); + } else { + // Get all non private windows + getAllWindows(function (windows) { + // Save windows as separate sessions + var date = new Date(); + + windows.forEach(function (mozWindow) { + if (mozWindow.incognito == false) { + sessionCount++; + } + }); + + var windowID = 0; + var updateEvent = new LimitCounter(sessionCount, updateOnSave); + + windows.forEach(function (mozWindow) { + if (mozWindow.incognito == false) { + windowID++; + let sessionTitle = date.toLocaleString() + (sessionCount > 1 ? ' #' + windowID : ''); + saveWindowSession( + { + title: sessionTitle, + tabs: mozWindow.tabs + }, + function (folder) { + sessionID = folder.id; + updateEvent.advance(); + } + ); + } + }); + }); + } + }); + }; + + var mergeSessions = function mergeSessions(sessionID) { + // Exclude all pages that are already saved + getCurrentWindow(function (mozWindow) { + var toSave = {}; + + mozWindow.tabs.forEach((tab) => { + let url = tab.url; + toSave[url] = tab; + }); + + // Exclude all pages that are already saved + BookmarkManager.getFolderBookmarks(sessionID, function (bookmarks) { + bookmarks.forEach(function (bookmark) { + delete toSave[bookmark.url]; + }); + + var updateEvent = new LimitCounter(Object.keys(toSave).length, function () { + WindowEvents.emit(document, 'ViewSession', sessionID); + }); + + // TODO - reverse saving order + var savingList = []; + for (let key in toSave) { + let tab = toSave[key]; + savingList.push(tab); + } + + for (let i = savingList.length - 1; i >= 0; i--) { + var tab = savingList[i]; + BookmarkManager.createBookmarkFromTab(tab, sessionID).then(updateEvent.advance, updateEvent.advance); + } + }); + }); + }; + + var overwriteSession = function overwriteSession(sessionID) { + var toRemove = []; + + BookmarkManager.getFolderBookmarks(sessionID, function (bookmarks) { + // Remove all bookmarks from the current session + bookmarks.forEach(function (bookmark) { + toRemove.push(bookmark.id); + }); + + SessionSyncModel.deleteBookmarkList(toRemove, sessionID, function () { + getCurrentWindow(function (mozWindow) { + var updateFinish = new LimitCounter(mozWindow.tabs.length, function () { + WindowEvents.emit(document, 'ViewSession', sessionID); + }); + + // TODO - add option to discard duplicate tabs and or pinned tabs + + for (let i = mozWindow.tabs.length - 1; i >= 0; i--) { + var tab = mozWindow.tabs[i]; + BookmarkManager.createBookmarkFromTab(tab, sessionID).then(updateFinish.advance, updateFinish.advance); + } + }); + }); + }); + }; + + var saveHistorySession = function saveHistorySession(sessionInfo) { + var windowCount = sessionInfo.windows.length; + + var windowID = 0; + + function onSave(bookmark) { + WindowEvents.emit(document, 'SetPromiseSession', { + sessionID: bookmark.id, + edit: false, + update: true + }); + } + + // save all history windows + for (var key in sessionInfo.windows) { + var date = new Date(sessionInfo.lastSave); + var sessionTitle = date.toLocaleString() + (windowCount > 1 ? ' #' + windowID : ''); + windowID++; + + saveWindowSession( + { + title: sessionTitle, + tabs: sessionInfo.windows[key].tabs + }, + onSave + ); + } + }; + + var saveWindowSession = function saveWindowSession(sessionInfo, callback) { + BookmarkManager.createBookmark({ + title: sessionInfo.title, + parentId: AppConfig.get('storageID') + }).then(function (folder) { + var trackSaving = new LimitCounter(sessionInfo.tabs.length, function () { + callback(folder); + }); + + // TODO - add option to discard duplicate tabs + + var savePinned = AppConfig.get('session.save').pinned; + + for (let i = sessionInfo.tabs.length - 1; i >= 0; i--) { + let tab = sessionInfo.tabs[i]; + if (tab.pinned && savePinned == false) { + trackSaving.offsetLimit(-1); + } else { + BookmarkManager.createBookmarkFromTab(tab, folder.id).then(trackSaving.advance, trackSaving.advance); + } + } + }); + }; + + var restoreSession = function restoreSession(folderID) { + browser.runtime.sendMessage({ event: 'restore-session', bookmarkID: folderID }); + }; + + var restoreNewWindow = function restoreNewWindow(folderID) { + var options = { + newWindow: true + }; + browser.runtime.sendMessage({ event: 'restore-session', bookmarkID: folderID, options: options }); + }; + + var restoreAdvanced = function restoreAdvanced(folderID, options) { + browser.runtime.sendMessage({ event: 'restore-session', bookmarkID: folderID, options: options }); + }; + + var replaceSession = function replaceSession(folderID) { + getCurrentWindow(function (mozWindow) { + var options = { + newWindow: true, + closeWindow: mozWindow.id + }; + restoreAdvanced(folderID, options); + }); + }; + + var replaceSessionAll = function replaceSessionAll(folderID) { + var options = { + newWindow: true, + closeAll: true + }; + restoreAdvanced(folderID, options); + }; + + var loadBookmarksNewWindow = function loadBookmarksNewWindow(windows) { + browser.runtime.sendMessage({ event: 'restore-sessions', windows: windows }); + }; + + var loadHistoryTabsNewWindow = function loadHistoryTabsNewWindow(windows) { + browser.runtime.sendMessage({ event: 'restore-history-session', windows: windows }); + }; + + var createSeparator = function createSeparator(folderID) { + browser.bookmarks.get(folderID).then( + function (marks) { + BookmarkManager.createBookmark({ + index: marks[0].index + 1, + type: 'separator', + parentId: AppConfig.get('storageID') + }).then(function success(separator) { + WindowEvents.emit(document, 'SetPromiseSession', { sessionID: separator.id, update: true }); + }); + }, + function onError(msg) { + console.error(msg); + } + ); + }; + + var createSession = function createSession(title, index) { + BookmarkManager.createBookmark({ + index: index, + title: title, + parentId: AppConfig.get('storageID') + }).then( + function success(session) { + WindowEvents.emit(document, 'SetPromiseSession', { + sessionID: session.id, + edit: true, + update: true + }); + }, + function error() {} + ); + }; + + var createNewSession = function createNewSession(folderID) { + var sessionTitle = new Date().toLocaleString(); + if (folderID) { + browser.bookmarks.get(folderID).then(function (marks) { + createSession(sessionTitle, marks[0].index + 1); + }); + } else { + createSession(sessionTitle); + } + }; + + // Events + GlobalEvents.on('CreateNewSession', function (name) { + var title = name ? name : new Date().toLocaleString(); + createSession(title); + }); + + // Events + GlobalEvents.on('management.close-tab', function (tabID) { + trackTabs = false; + browser.tabs.remove(tabID).then(function () { + GlobalEvents.emit('UpdateCurrentSession'); + trackTabs = true; + }); + }); + + // Public API + return { + moveTab: moveTab, + closeTab: closeTab, + activateTab: activateTab, + tabTracking: tabTracking, + activateWindow: activateWindow, + getWindowTab: getWindowTab, + getCurrentTab: getCurrentTab, + getCurrentWindow: getCurrentWindow, + getAllWindows: getAllWindows, + restoreSession: restoreSession, + replaceSession: replaceSession, + restoreAdvanced: restoreAdvanced, + replaceSessionAll: replaceSessionAll, + restoreNewWindow: restoreNewWindow, + loadBookmarksNewWindow: loadBookmarksNewWindow, + loadHistoryTabsNewWindow: loadHistoryTabsNewWindow, + mergeSessions: mergeSessions, + overwriteSession: overwriteSession, + saveActiveSession: saveActiveSession, + createSeparator: createSeparator, + createNewSession: createNewSession, + saveHistorySession: saveHistorySession + }; +})(); diff --git a/src/scripts/utils/action-popup.js b/src/scripts/utils/action-popup.js new file mode 100644 index 0000000..42a864e --- /dev/null +++ b/src/scripts/utils/action-popup.js @@ -0,0 +1,95 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { HTMLCreator } from './dom'; +import { WindowEvents } from './global-events'; + +// ***************************************************************************** +// ActionPopup + +export function ActionPopup(options) { + var DomElem = HTMLCreator(); + + var panel = DomElem('div', { class: 'action-popoup' }); + + var info = DomElem('div', { class: 'info' }); + var controls = DomElem('div', { class: 'controls' }); + + var ok = DomElem('div', { class: 'button ok' }); + ok.textContent = 'Confirm'; + + var cancel = DomElem('div', { class: 'button cancel' }); + cancel.textContent = 'Cancel'; + + var flexArea1 = DomElem('div', { class: 'flex' }); + var flexArea2 = DomElem('div', { class: 'flex' }); + + controls.appendChild(flexArea1); + controls.appendChild(ok); + controls.appendChild(cancel); + controls.appendChild(flexArea2); + panel.appendChild(info); + panel.appendChild(controls); + + var callback; + var defaultCallback = function defaultCallback() {}; + + // ------------------------------------------------------------------------ + // Events + + ok.addEventListener('click', function () { + callback(); + hidePanel(); + }); + + cancel.addEventListener('click', function () { + hidePanel(); + }); + + var openPanel = function openPanel(options) { + panel.setAttribute('data-active', 'true'); + panel.style.left = options.event.pageX + 'px'; + panel.style.top = options.event.pageY + 'px'; + + if (typeof document !== 'undefined') { + document.addEventListener('mousedown', blurMenu); + } + + info.textContent = options.message; + setCallback(options.callback); + }; + + var hidePanel = function hidePanel() { + panel.removeAttribute('data-active'); + if (typeof document !== 'undefined') { + document.removeEventListener('mousedown', blurMenu); + } + callback = defaultCallback; + }; + + var blurMenu = function hideWhenClickOutside(e) { + var target = e.target; + while (target) { + if (target === panel) return; + target = target.parentElement; + } + hidePanel(); + }; + + function setCallback(func) { + callback = typeof func === 'function' ? func : defaultCallback; + } + + // Register custom events + if (typeof document !== 'undefined') { + WindowEvents.on(document, options.name + '-Open', openPanel); + WindowEvents.on(document, 'ActionPopup-Hide', hidePanel); + } + + // ------------------------------------------------------------------------ + // Public properties + + this.DOMRoot = panel; +} diff --git a/src/scripts/utils/auto-scroll.js b/src/scripts/utils/auto-scroll.js new file mode 100644 index 0000000..6cce8ff --- /dev/null +++ b/src/scripts/utils/auto-scroll.js @@ -0,0 +1,83 @@ +'use strict'; + +// ***************************************************************************** +// AutoScroll + +export const AutoScroll = (function AutoScroll() { + var autoScroll = false; + var prevScroll = 0; + var scrollSpeed = 0; + var scrollingNode; + var scrollTarget; + var scrollDir = 0; + + function scrollingAnimation() { + if (autoScroll == false) { + return; + } + + prevScroll = scrollingNode.scrollTop; + scrollingNode.scrollTop += scrollSpeed; + + if (scrollingNode.scrollTop != prevScroll) { + if (scrollTarget != undefined) { + if (scrollDir * scrollingNode.scrollTop > scrollTarget) { + scrollingNode.scrollTop = scrollDir * scrollTarget; + autoScroll = false; + return; + } + } + if (typeof requestAnimationFrame !== 'undefined') { + requestAnimationFrame(scrollingAnimation); + } + } else { + autoScroll = false; + } + } + + // scroll a DOM node with a specified speed + function scroll(node, speed) { + scrollSpeed = speed / 10; + + if (autoScroll == true) { + return; + } + + scrollTarget = undefined; + autoScroll = true; + scrollingNode = node; + if (typeof requestAnimationFrame !== 'undefined') { + requestAnimationFrame(scrollingAnimation); + } + } + + function stop() { + autoScroll = false; + } + + function scrollTo(node, offset, time) { + if (time == 0) { + node.scrollTop = offset; + return; + } + + if (autoScroll == true) { + return; + } + + autoScroll = true; + scrollingNode = node; + scrollSpeed = (offset - node.scrollTop) / (time * 60); + scrollDir = scrollSpeed > 0 ? 1 : -1; + scrollTarget = offset * scrollDir; + if (typeof requestAnimationFrame !== 'undefined') { + requestAnimationFrame(scrollingAnimation); + } + } + + return { + scroll: scroll, + scrollTo: scrollTo, + stop: stop + }; +})(); diff --git a/src/scripts/utils/components.js b/src/scripts/utils/components.js new file mode 100644 index 0000000..1675fdf --- /dev/null +++ b/src/scripts/utils/components.js @@ -0,0 +1,348 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { HTMLCreator } from './dom'; +import * as JSUtils from './general'; + +// ***************************************************************************** +// API + +/* + * Toggle switch + */ + +export function ToggleSwitch(options) { + var DomElem = HTMLCreator(); + + var state = options.state; + + var entry = DomElem('div', { class: 'comp-toggle-switch' }); + var title = DomElem('div', { class: 'label' }); + + if (options.attribute) { + entry.setAttribute(options.attribute, ''); + } + + if (options.description) { + title.textContent = options.description; + } + + var button = DomElem('div', { class: 'switch' }); + button.setAttribute('state', state); + button.textContent = state ? options.onState : options.offState; + + if (options.tooltip) { + entry.setAttribute('tooltip', options.tooltip); + title.setAttribute('tooltip', options.tooltip); + button.setAttribute('tooltip', options.tooltip); + } + + entry.appendChild(title); + entry.appendChild(button); + + function setState(newState) { + if (state == newState) return; + + state = newState; + options.callback(state); + button.setAttribute('state', state); + button.textContent = state ? options.onState : options.offState; + } + + entry.addEventListener('click', function () { + setState(!state); + }); + + this.setState = setState; + this.DOMRoot = entry; +} + +export function ToggleButton(options) { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var state = options.state; + var callback = JSUtils.getValidFunction(options.callback); + + var container = DomElem('div', { class: 'comp-toggle-button' }); + var info = DomElem('div', { class: 'label' }); + info.textContent = options.description; + + var button = DomElem('div', { class: 'button' }); + button.setAttribute('state', state); + button.textContent = state ? options.onState : options.offState; + + container.appendChild(info); + container.appendChild(button); + + // ------------------------------------------------------------------------ + // Events + + var toggle = function toggle() { + setValue(!state, true); + }; + + var setValue = function setValue(value, trigger) { + if (value != state) { + state = value; + button.setAttribute('state', state); + button.textContent = state ? options.onState : options.offState; + if (trigger) { + callback(state); + } + } + }; + + button.addEventListener('click', toggle); + + // ------------------------------------------------------------------------ + // Public properties + + this.toggle = toggle; + this.setValue = setValue; + this.DOMRoot = container; +} + +/* + * Action Button + */ + +export function ActionButton(options) { + var DomElem = HTMLCreator(); + + var entry = DomElem('div', { class: 'comp-button' }); + var title = DomElem('div', { class: 'label' }); + + if (options.description) { + title.textContent = options.description; + } + + var button = DomElem('div', { class: 'button' }); + button.textContent = options.value; + + if (options.tooltip) { + entry.setAttribute('tooltip', options.tooltip); + title.setAttribute('tooltip', options.tooltip); + button.setAttribute('tooltip', options.tooltip); + } + + entry.appendChild(title); + entry.appendChild(button); + + function setValue(value) { + button.textContent = value; + } + + function getValue() { + return button.textContent; + } + + button.addEventListener('click', function () { + options.callback(); + }); + + this.setValue = setValue; + this.getValue = getValue; + this.DOMRoot = entry; +} + +/* + * RageControl + */ + +export function RangeControl(options) { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var value = options.value; + var step = options.step ? options.step : 1; + var callback = JSUtils.getValidFunction(options.onChange); + + var root = DomElem('div', { class: 'comp-range-control' }); + + var sectionInfo = DomElem('div', { class: 'label' }); + sectionInfo.textContent = options.description; + + var container = DomElem('div', { class: 'range-input' }); + var incButton = DomElem('div', { class: 'button inc' }); + var decButton = DomElem('div', { class: 'button dec' }); + var valueBox = DomElem('input', { type: 'text', class: 'value' }); + valueBox.value = value; + + root.appendChild(sectionInfo); + root.appendChild(container); + container.appendChild(decButton); + container.appendChild(valueBox); + container.appendChild(incButton); + + var setValue = function setValue(newValue, trigger) { + // revent update trigger with the same value + if (value == newValue) { + return; + } + + // restrict to specified range + if (options.minValue > newValue) { + newValue = options.minValue; + } + if (options.maxValue < newValue) { + newValue = options.maxValue; + } + + if (newValue % step != 0) { + newValue -= newValue % step; + } + + // update value + var diff = newValue - value; + value = newValue; + valueBox.value = newValue; + + if (trigger) { + callback(newValue, diff); + } + }; + + var onChange = function onChange() { + var newValue = valueBox.value | 0; + if (parseInt(valueBox.value) != newValue) { + valueBox.value = value; + return; + } + + setValue(newValue, true); + }; + + valueBox.addEventListener('change', function () { + onChange(true); + }); + + incButton.addEventListener('click', function () { + setValue(value + step, true); + }); + + decButton.addEventListener('click', function () { + setValue(value - step, true); + }); + + // ------------------------------------------------------------------------ + // Public properties + + this.DOMRoot = root; + this.setValue = setValue; +} + +export function DropDown(options) { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + var container = DomElem('div', { class: 'comp-dropdown' }); + if (options.id) { + container.setAttribute('id', options.id); + } + + var info = DomElem('div', { class: 'label' }); + info.textContent = options.description; + + var select = DomElem('div', { class: 'select' }); + var selectValue = DomElem('div', { class: 'select-value' }); + selectValue.textContent = options.value; + + var dropList = DomElem('div', { class: 'drop-list', visible: 'false' }); + + container.appendChild(info); + container.appendChild(select); + select.appendChild(selectValue); + select.appendChild(dropList); + + function addOption(label, options) { + if (options == undefined) options = {}; + + var option = DomElem('div', { class: 'option' }); + option.textContent = label; + option.setAttribute('value', options.value != undefined ? options.value : label); + + if (options.property) { + option.setAttribute(options.property, ''); + } + + if (options.selected === true) { + setSelected(option); + } + + dropList.appendChild(option); + } + + // ------------------------------------------------------------------------ + // Events + + var selectedOption; + var visible = false; + var callback = JSUtils.getValidFunction(options.onChange); + + var setValue = function (value, trigger) { + for (var i = 0; i < dropList.children.length; i++) { + if (dropList.children[i].getAttribute('value') == value) { + setSelected(dropList.children[i], trigger); + return; + } + } + }; + + var getValue = function () { + return selectedOption.getAttribute('value'); + }; + + var setSelected = function setSelected(node, trigger) { + if (selectedOption) selectedOption.removeAttribute('selected'); + + selectedOption = node; + + if (selectedOption) { + var value = selectedOption.getAttribute('value'); + selectValue.textContent = selectedOption.textContent; + container.setAttribute('value', value); + selectedOption.setAttribute('selected', ''); + if (trigger) { + callback(value); + } + } + }; + + var closeDropdown = function closeDropdown(e) { + e.stopPropagation(); + visible = false; + dropList.setAttribute('visible', visible); + document.removeEventListener('click', closeDropdown); + }; + + select.addEventListener('click', function (e) { + e.stopPropagation(); + visible = !visible; + dropList.setAttribute('visible', visible); + document.addEventListener('click', closeDropdown); + }); + + dropList.addEventListener('click', function (e) { + if (e.target.className == 'option') { + setSelected(e.target, true); + closeDropdown(e); + } + }); + + // ------------------------------------------------------------------------ + // Public properties + + this.container = container; + this.addOption = addOption; + this.setValue = setValue; + this.getValue = getValue; +} diff --git a/src/scripts/utils/confirm-box.js b/src/scripts/utils/confirm-box.js new file mode 100644 index 0000000..c19e236 --- /dev/null +++ b/src/scripts/utils/confirm-box.js @@ -0,0 +1,89 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { HTMLCreator } from './dom'; +import { WindowEvents } from './global-events'; + +// ***************************************************************************** +// Bookmark Confirm Box + +export function ConfirmBox(options) { + var DomElem = HTMLCreator(); + + var panel = DomElem('div', { class: 'confirm-box' }); + + var info = DomElem('div', { class: 'info' }); + var controls = DomElem('div', { class: 'controls' }); + + var ok = DomElem('div', { class: 'button ok' }); + ok.textContent = 'Confirm'; + + var cancel = DomElem('div', { class: 'button cancel' }); + cancel.textContent = 'Cancel'; + + var flexArea1 = DomElem('div', { class: 'flex' }); + var flexArea2 = DomElem('div', { class: 'flex' }); + + controls.appendChild(flexArea1); + controls.appendChild(ok); + controls.appendChild(cancel); + controls.appendChild(flexArea2); + panel.appendChild(info); + panel.appendChild(controls); + + var callback; + var defaultCallback = function defaultCallback() {}; + + // ------------------------------------------------------------------------ + // Events + + ok.addEventListener('click', function () { + callback(); + hidePanel(); + }); + + cancel.addEventListener('click', function () { + hidePanel(); + }); + + var openPanel = function openPanel(options) { + panel.setAttribute('data-active', 'true'); + panel.style.left = options.event.pageX + 'px'; + panel.style.top = options.event.pageY + 'px'; + + document.addEventListener('mousedown', blurMenu); + + info.textContent = options.message; + setCallback(options.callback); + }; + + var hidePanel = function hidePanel() { + panel.removeAttribute('data-active'); + document.removeEventListener('mousedown', blurMenu); + callback = defaultCallback; + }; + + var blurMenu = function hideWhenClickOutside(e) { + var target = e.target; + while (target) { + if (target === panel) return; + target = target.parentElement; + } + hidePanel(); + }; + + function setCallback(func) { + callback = typeof func === 'function' ? func : defaultCallback; + } + + // Register custom events + WindowEvents.on(document, options.name + '-Open', openPanel); + WindowEvents.on(document, 'ConfimBox-Hide', hidePanel); + + // ------------------------------------------------------------------------ + // Public properties + + this.DOMRoot = panel; +} diff --git a/src/scripts/utils/context-menu.js b/src/scripts/utils/context-menu.js new file mode 100644 index 0000000..3fe14b4 --- /dev/null +++ b/src/scripts/utils/context-menu.js @@ -0,0 +1,130 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +// App +import { AppConfig } from '../config'; +import { HTMLCreator } from './dom'; + +// TODO - remove window/global events +import { WindowEvents, GlobalEvents } from './global-events'; + +// ***************************************************************************** +// Bookmark Context Menu + +var previousContextMenu = null; + +export function ContextMenu(options) { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + // var self = this; + var menu = DomElem('div', { class: 'context-menu' }); + if (options.width != undefined) { + menu.style.width = options.width + 'px'; + } + + var context = undefined; + + // ------------------------------------------------------------------------ + // Events + + var openMenu = (event) => { + if (previousContextMenu && previousContextMenu != this) { + previousContextMenu.hideMenu(); + } + previousContextMenu = this; + + menu.setAttribute('data-active', 'true'); + var padding = 10; + menu.style.left = Math.min(event.pageX, window.innerWidth - menu.clientWidth - padding) + 'px'; + menu.style.top = Math.min(event.pageY, window.innerHeight - menu.clientHeight - padding) + 'px'; + + document.addEventListener('mousedown', blurMenu); + + WindowEvents.emit(document, 'ConfimBox-Hide'); + }; + + var hideMenu = function hideMenu(e) { + menu.removeAttribute('data-active'); + document.removeEventListener('mousedown', blurMenu); + WindowEvents.emit(document, 'OverlaySystem', { state: false }); + }; + + var blurMenu = function hideWhenClickOutside(e) { + var target = e.target; + while (target) { + if (target === menu) return; + target = target.parentElement; + } + hideMenu(); + }; + + // Actions + // TODO - investigate switch to event delegation + var NewAction = function NewAction(opt) { + if (opt.func) { + return function actionEvent(e) { + context[opt.func](e); + hideMenu(); + }; + } + + if (opt.callback) { + return function actionEvent(e) { + opt.callback(context, e); + hideMenu(); + }; + } + + if (opt.globalEvent) { + return function action() { + GlobalEvents.emit(opt.globalEvent, context); + hideMenu(); + }; + } + + return function action() { + WindowEvents.emit(document, options.name + '-' + opt.event, context); + hideMenu(); + }; + }; + + var addMenuEntry = function AddMenuEntry(opt) { + var event = NewAction(opt); + var entry = DomElem('div', { class: 'button' }); + entry.textContent = opt.value; + entry.setAttribute('icon', opt.icon); + menu.appendChild(entry); + + var addSeparator = opt.separator; + if (addSeparator != undefined) { + var separator = DomElem('div', { class: 'separator' }); + menu.insertBefore(separator, addSeparator == 'top' ? entry : undefined); + } + + entry.addEventListener('click', event); + }; + + // Register custom events + WindowEvents.on(document, options.name + '-Open', function (options) { + // menu.setAttribute('icons', options.showIcons ? 'yes' : 'no'); + openMenu(options.event); + context = options.context; + WindowEvents.emit(document, 'OverlaySystem', { state: true }); + }); + + AppConfig.onChange('context.menu.icons', function (value) { + menu.setAttribute('icons', value ? 'yes' : 'no'); + }); + + // ------------------------------------------------------------------------ + // Public properties + + this.DOMRoot = menu; + this.hideMenu = hideMenu; + this.addMenuEntry = addMenuEntry; +} diff --git a/src/scripts/utils/dom.js b/src/scripts/utils/dom.js new file mode 100644 index 0000000..929bcb8 --- /dev/null +++ b/src/scripts/utils/dom.js @@ -0,0 +1,25 @@ +'use strict'; + +const NS_HTML = 'http://www.w3.org/1999/xhtml'; + +// ------------------------------------------------------------------------ +// Generic Node Creator + +function Node(ns, type, attrs) { + var elem = document.createElementNS(ns, type); + + attrs && + Object.keys(attrs).forEach(function (attr) { + elem.setAttribute(attr, attrs[attr]); + }); + + return elem; +} + +export function HTMLCreator() { + return Node.bind(null, NS_HTML); +} + +export function DomElem(type, attrs) { + return Node(NS_HTML, type, attrs); +} diff --git a/src/scripts/utils/drag-context.js b/src/scripts/utils/drag-context.js new file mode 100644 index 0000000..e7cf56b --- /dev/null +++ b/src/scripts/utils/drag-context.js @@ -0,0 +1,39 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules + +import { WindowEvents } from './global-events'; + +// ***************************************************************************** +// API + +export const DragContext = (function DragContext() { + var context = null; + + function setContext(ctx) { + context = ctx; + if (typeof document !== 'undefined') { + WindowEvents.emit(document, 'TrashCan-Droppable', ctx != null); + } + } + + function getContext() { + return context; + } + + function hasContext() { + return context != null; + } + + function clearContext() { + setContext(null); + } + + return { + hasContext: hasContext, + clearContext: clearContext, + setContext: setContext, + getContext: getContext + }; +})(); diff --git a/src/scripts/utils/field-edit-widget.js b/src/scripts/utils/field-edit-widget.js new file mode 100644 index 0000000..b101ad8 --- /dev/null +++ b/src/scripts/utils/field-edit-widget.js @@ -0,0 +1,135 @@ +'use strict'; + +// ***************************************************************************** +// Custom Modules +import { HTMLCreator } from './dom'; +import { WindowEvents, GlobalEvents } from './global-events'; + +// ***************************************************************************** +// Global Variables + +var counterID = 0; + +export function FieldEditWdiget(options) { + var DomElem = HTMLCreator(); + + // ------------------------------------------------------------------------ + // Create UI + + // Context will be the session folderID or bookmarkID + var context = undefined; + // Context data represents fields that need to be edited + var context_data = null; + + var widgetID = counterID++; + var fields = {}; + var first_field = null; + + // Edit session widget + var widget = DomElem('div', { class: 'field-edit-widget' }); + var wfields = DomElem('div', { class: 'fields' }); + var save = DomElem('div', { class: 'button save' }); + var cancel = DomElem('div', { class: 'button cancel' }); + + save.textContent = 'Save'; + cancel.textContent = 'Cancel'; + + widget.appendChild(wfields); + widget.appendChild(cancel); + widget.appendChild(save); + + // ------------------------------------------------------------------------ + // Methods + + function addField(options) { + var group = DomElem('div', { class: 'group' }); + + var input = DomElem('input'); + + var label = DomElem('label'); + label.textContent = options.label; + + group.appendChild(label); + group.appendChild(input); + wfields.appendChild(group); + + fields[options.name] = input; + + if (first_field == null) { + first_field = input; + } + } + + function keyBoardShortcut(e) { + if (e.keyCode == 13) { + saveValues(); + } + } + + function hideWidget() { + if (first_field) first_field.blur(); + widget.removeAttribute('data-active'); + document.removeEventListener('keydown', keyBoardShortcut); + } + + var hideIfNotInvoked = (ID) => { + if (widgetID !== ID) hideWidget(); + }; + + function invokeWidget(data) { + context_data = data.fields; + context = data.context; + + for (var key in data.fields) { + if (data.fields[key]) { + fields[key].value = data.fields[key]; + } else { + if (Object.prototype.hasOwnProperty.call(fields, key)) { + fields[key].value = ''; + data.fields[key] = ''; + } + } + } + + widget.setAttribute('data-active', ''); + document.addEventListener('keydown', keyBoardShortcut); + WindowEvents.emit(document, 'FieldWidgetInvoked', widgetID); + if (first_field) { + first_field.focus(); + first_field.select(); + } + } + + function saveValues() { + var obj = {}; + var prop_save = 0; + for (var key in fields) { + if (context_data[key] != fields[key].value) { + prop_save++; + obj[key] = fields[key].value; + } + } + + if (prop_save) { + WindowEvents.emit(document, options.name + '-Save', { context: context, fields: obj }); + } + + hideWidget(); + } + + // ------------------------------------------------------------------------ + // Create UI + + cancel.addEventListener('click', hideWidget); + save.addEventListener('click', saveValues); + + GlobalEvents.on('BookmarkRemoved', hideWidget); + WindowEvents.on(document, options.name + '-Invoke', invokeWidget); + WindowEvents.on(document, 'FieldWidgetInvoked', hideIfNotInvoked); + + // ------------------------------------------------------------------------ + // Public properties + + this.DOMRoot = widget; + this.addField = addField; +} diff --git a/src/scripts/utils/general.js b/src/scripts/utils/general.js new file mode 100644 index 0000000..1d6a833 --- /dev/null +++ b/src/scripts/utils/general.js @@ -0,0 +1,41 @@ +'use strict'; + +export function copyProperties(source, destination) { + for (var prop in source) { + if (Object.prototype.hasOwnProperty.call(source, prop)) { + destination[prop] = source[prop]; + } + } +} + +// Test if object is a valid javascript function +// Returns the same object if true or a new function otherwise +export function getValidFunction(object) { + var value = typeof object === 'function' ? object : function () {}; + return value; +} + +export function LimitCounter(limit, callback) { + var func = getValidFunction(callback); + var counted = 0; + var triggered = false; + + function checkLimit() { + if (!triggered && counted >= limit) { + triggered = true; + func(); + } + } + + this.advance = function advance() { + counted++; + checkLimit(); + }; + + this.offsetLimit = function offsetLimit(offset) { + limit += offset; + if (limit > 0) { + checkLimit(); + } + }; +} diff --git a/src/scripts/utils/global-events.js b/src/scripts/utils/global-events.js new file mode 100644 index 0000000..c87d433 --- /dev/null +++ b/src/scripts/utils/global-events.js @@ -0,0 +1,90 @@ +'use strict'; + +// ------------------------------------------------------------------------ +// Modules + +import { PubSub } from '../3rd/pubsub'; + +// ------------------------------------------------------------------------ +// API + +const GlobalEventSystem = (function GlobalEventSystem() { + const globalEvents = new PubSub(); + + return { + on: globalEvents.subscribe.bind(globalEvents), + off: globalEvents.unsubscribe.bind(globalEvents), + emit: globalEvents.publish.bind(globalEvents) + }; +})(); + +// Handles collection of PubSub events separated by diffrent namespaces +function NamespaceEvents() { + var map = new Map(); + + //------------------------------------------------------------------------- + // Public API + + var broadcastEvent = function broadcastEvent(topic, object) { + map.forEach(function (collection) { + collection.publish(topic, object); + }); + }; + + var emitEvent = function emitEvent(namespace, topic, object) { + var collection = map.get(namespace); + if (collection instanceof PubSub) { + collection.publish(topic, object); + } + }; + + var subscribe = function subscribe(namespace, topic, callback) { + var collection = map.get(namespace); + if (collection instanceof PubSub === false) { + collection = new PubSub(); + map.set(namespace, collection); + } + collection.subscribe(topic, callback); + }; + + var unsubscribe = function unsubscribe(namespace, topic, callback) { + var collection = map.get(namespace); + if (collection instanceof PubSub) { + collection.unsubscribe(topic, callback); + } + }; + + var remove = function remove(namespace) { + var collection = map.get(namespace); + if (collection instanceof PubSub) { + collection.messages = {}; + map.delete(namespace); + } + }; + + return { + on: subscribe, + off: unsubscribe, + emit: emitEvent, + broadcast: broadcastEvent, + remove: remove + }; +} + +// WindowEvents based on window.document topic + +const WindowEvents = new NamespaceEvents(); + +WindowEvents.onLocal = function onLocal(topic, callback) { + if (typeof document !== 'undefined') { + this.on(document, topic, callback); + } +}; + +WindowEvents.emitLocal = function emitLocal(topic, object) { + if (typeof document !== 'undefined') { + this.emit(document, topic, object); + } +}; + +export { GlobalEventSystem as GlobalEvents, WindowEvents }; diff --git a/wxt.config.js b/wxt.config.js new file mode 100644 index 0000000..dbc8798 --- /dev/null +++ b/wxt.config.js @@ -0,0 +1,107 @@ +import 'dotenv/config'; +import { defineConfig } from 'wxt'; + +export default defineConfig(() => { + console.log('FIREFOX_BINARY:', process.env.FIREFOX_BINARY); + console.log('FIREFOX_PROFILE:', process.env.FIREFOX_PROFILE); + + return { + srcDir: 'src', + publicDir: 'public', + imports: { + eslintrc: { + enabled: 9 + } + }, + vite: () => ({ + build: { + minify: false, + cssMinify: false, + modulePreload: false, + rollupOptions: { + output: { + entryFileNames: '[name].js', + chunkFileNames: '[name].js', + assetFileNames: 'assets/[name].[ext]' + } + } + }, + optimizeDeps: { + entries: ['src/entrypoints/session-sync.js', 'src/entrypoints/background.js'] + } + }), + browser: 'firefox', + webExt: { + binaries: { + firefox: process.env.FIREFOX_BINARY + }, + firefoxProfile: process.env.FIREFOX_PROFILE, + keepProfileChanges: true + }, + manifest: { + name: 'Session Sync', + version: '3.3.0', + author: 'Gabriel Ivanica', + description: + 'Save sessions as bookmarks and sync them through Firefox Sync (or any other sync engine).\nPowerful session management - organize, edit, change, save, restore, etc\n', + homepage_url: 'https://github.com/ReDEnergy/SessionSync', + default_locale: 'en', + browser_specific_settings: { + gecko: { + id: 'session-sync@gabrielivanica.com', + data_collection_permissions: { + required: ['none'] + } + } + }, + permissions: [ + 'storage', + 'bookmarks', + 'tabs', + 'clipboardWrite', + 'unlimitedStorage', + 'cookies', + 'contextualIdentities', + 'tabGroups' + ], + action: { + default_icon: { + 16: 'images/icon16.png', + 32: 'images/icon32.png', + 48: 'images/icon48.png', + 128: 'images/icon128.png' + }, + default_title: 'Session Sync', + default_popup: 'session-sync.html' + }, + icons: { + 16: 'images/icon16.png', + 32: 'images/icon32.png', + 48: 'images/icon48.png', + 64: 'images/icon64.png', + 96: 'images/icon96.png', + 128: 'images/icon128.png' + }, + commands: { + _execute_action: { + suggested_key: { + default: 'Ctrl+Shift+F' + }, + description: 'Widget View' + }, + session_sync_detach_tab: { + suggested_key: { + default: 'Ctrl+Shift+U' + }, + description: 'Tab View' + }, + session_sync_detach_window: { + suggested_key: { + default: 'Ctrl+Shift+F6' + }, + description: 'Window View' + } + } + } + }; +});