├── img ├── icon16.png ├── icon48.png ├── nofile.png ├── icon128.png ├── icon256.png ├── nofile@2x.png ├── notimage.png ├── nointernet.png ├── notimage@2x.png ├── example-image.png ├── nointernet@2x.png └── example-image@2x.png ├── fonts ├── arvo-bold.ttf ├── arvo-italic.ttf ├── arvo-regular.ttf ├── bariol_bold.ttf ├── bariol_light.ttf ├── bariol_regular.ttf ├── Mado-Icons-Normal.ttf ├── arvo-bold-italic.ttf ├── bariol_bold_italic.ttf ├── bariol_light_italic.ttf ├── SourceCodePro-Regular.ttf ├── bariol_regular_italic.ttf └── Mado-Little-Icons-Normal.ttf ├── js ├── more │ ├── about-onload.js │ ├── shortcuts-onload.js │ ├── settings-onload.js │ ├── Localizer.js │ ├── ShortcutsManager.js │ ├── SettingsManager.js │ └── CloseButtonManager.js ├── Topbar │ ├── PrintManager.js │ ├── ExportManager.js │ ├── NewFileManager.js │ ├── SaveFileAsManager.js │ ├── MoreWindowsManager.js │ ├── OpenFileManager.js │ ├── LabelManager.js │ ├── DnDManager.js │ ├── SaveFileManager.js │ ├── App.js │ ├── SwitchManager.js │ └── RecentFilesManager.js ├── onload.js ├── Editor │ ├── NameManager.js │ ├── SaveStateManager.js │ ├── UrlManager.js │ ├── Counter.js │ ├── ImageArray.js │ ├── ScrollManager.js │ ├── StyleManager.js │ ├── WebImageManager.js │ ├── LinkManager.js │ ├── Editor.js │ ├── ImageManager.js │ ├── HelpManager.js │ └── DisplayManager.js ├── background.js ├── lib │ ├── rangyinputs.js │ └── mousetrap.min.js └── Window │ └── AppWindow.js ├── _locales ├── am │ └── messages.json ├── ar │ └── messages.json ├── bg │ └── messages.json ├── bn │ └── messages.json ├── ca │ └── messages.json ├── cs │ └── messages.json ├── da │ └── messages.json ├── de │ └── messages.json ├── el │ └── messages.json ├── en_GB │ └── messages.json ├── en_US │ └── messages.json ├── es │ └── messages.json ├── es_419 │ └── messages.json ├── et │ └── messages.json ├── fa │ └── messages.json ├── fil │ └── messages.json ├── gu │ └── messages.json ├── he │ └── messages.json ├── hi │ └── messages.json ├── hr │ └── messages.json ├── hu │ └── messages.json ├── id │ └── messages.json ├── it │ └── messages.json ├── ja │ └── messages.json ├── kn │ └── messages.json ├── ko │ └── messages.json ├── lt │ └── messages.json ├── lv │ └── messages.json ├── ml │ └── messages.json ├── mr │ └── messages.json ├── ms │ └── messages.json ├── nl │ └── messages.json ├── no │ └── messages.json ├── pl │ └── messages.json ├── pt_BR │ └── messages.json ├── pt_PT │ └── messages.json ├── ro │ └── messages.json ├── ru │ └── messages.json ├── sk │ └── messages.json ├── sl │ └── messages.json ├── sr │ └── messages.json ├── sv │ └── messages.json ├── sw │ └── messages.json ├── ta │ └── messages.json ├── te │ └── messages.json ├── th │ └── messages.json ├── tr │ └── messages.json ├── uk │ └── messages.json ├── vi │ └── messages.json └── zh_TW │ └── messages.json ├── .gitignore ├── css ├── header.css ├── retina.css ├── document-base.css ├── more │ ├── about.css │ ├── more-frame-chromeos.css │ ├── more-frame-windows.css │ ├── more-frame-mac.css │ ├── shortcuts.css │ └── options-base.css ├── themes │ ├── home.css │ ├── tramway.css │ └── clinic.css ├── init.min.css ├── responsive.css ├── window-frame-chromeos.css ├── window-frame-linux.css ├── print.css ├── window-frame-mac.css ├── style-base.css ├── window-frame-windows.css ├── footer.css ├── icons.css ├── window-frame.css └── conversion-view-base.css ├── README.md ├── package.json ├── LICENSE ├── manifest.json ├── more ├── settings.html ├── shortcuts.html └── about.html └── Gruntfile.js /img/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/icon16.png -------------------------------------------------------------------------------- /img/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/icon48.png -------------------------------------------------------------------------------- /img/nofile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/nofile.png -------------------------------------------------------------------------------- /img/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/icon128.png -------------------------------------------------------------------------------- /img/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/icon256.png -------------------------------------------------------------------------------- /img/nofile@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/nofile@2x.png -------------------------------------------------------------------------------- /img/notimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/notimage.png -------------------------------------------------------------------------------- /fonts/arvo-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/arvo-bold.ttf -------------------------------------------------------------------------------- /img/nointernet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/nointernet.png -------------------------------------------------------------------------------- /img/notimage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/notimage@2x.png -------------------------------------------------------------------------------- /fonts/arvo-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/arvo-italic.ttf -------------------------------------------------------------------------------- /fonts/arvo-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/arvo-regular.ttf -------------------------------------------------------------------------------- /fonts/bariol_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/bariol_bold.ttf -------------------------------------------------------------------------------- /fonts/bariol_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/bariol_light.ttf -------------------------------------------------------------------------------- /img/example-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/example-image.png -------------------------------------------------------------------------------- /img/nointernet@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/nointernet@2x.png -------------------------------------------------------------------------------- /fonts/bariol_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/bariol_regular.ttf -------------------------------------------------------------------------------- /img/example-image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/img/example-image@2x.png -------------------------------------------------------------------------------- /fonts/Mado-Icons-Normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/Mado-Icons-Normal.ttf -------------------------------------------------------------------------------- /fonts/arvo-bold-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/arvo-bold-italic.ttf -------------------------------------------------------------------------------- /fonts/bariol_bold_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/bariol_bold_italic.ttf -------------------------------------------------------------------------------- /fonts/bariol_light_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/bariol_light_italic.ttf -------------------------------------------------------------------------------- /fonts/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /fonts/bariol_regular_italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/bariol_regular_italic.ttf -------------------------------------------------------------------------------- /fonts/Mado-Little-Icons-Normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armandgrillet/Mado/HEAD/fonts/Mado-Little-Icons-Normal.ttf -------------------------------------------------------------------------------- /js/more/about-onload.js: -------------------------------------------------------------------------------- 1 | /* All the things to do when about.html is loaded. */ 2 | 3 | window.onload = function() { 4 | var closeButtonManager = new CloseButtonManager(); 5 | var localizer = new Localizer(); 6 | }; 7 | -------------------------------------------------------------------------------- /_locales/am/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/ar/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/bg/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/bn/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/ca/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/cs/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/da/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/el/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/en_GB/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/en_US/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/es_419/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/et/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/fa/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/fil/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/gu/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/he/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/hi/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/hr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/hu/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/id/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/it/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/kn/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/ko/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/lt/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/lv/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/ml/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/mr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/ms/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/nl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/no/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/pl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/pt_BR/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/pt_PT/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/ro/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/sk/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/sl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/sr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/sv/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/sw/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/ta/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/te/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/th/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/tr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/uk/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/vi/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /_locales/zh_TW/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Mado" 4 | }, 5 | "appDesc": { 6 | "message": "Mado is a Markdown editor application. You can use it to take notes, write blog posts or edit documents." 7 | } 8 | } -------------------------------------------------------------------------------- /js/more/shortcuts-onload.js: -------------------------------------------------------------------------------- 1 | /* All the things to do when shortcuts.html is loaded. */ 2 | 3 | window.onload = function() { 4 | var closeButtonManager = new CloseButtonManager(); 5 | var localizer = new Localizer(); 6 | var shortcutsManager = new ShortcutsManager(); 7 | }; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | ###################### 3 | .DS_Store 4 | .DS_Store? 5 | ._* 6 | .Spotlight-V100 7 | .Trashes 8 | ehthumbs.db 9 | Thumbs.db 10 | 11 | # npm modules # 12 | /node_modules/* 13 | node_modules/ 14 | node_modules 15 | 16 | # Minified version # 17 | /min/* 18 | min/ 19 | min 20 | -------------------------------------------------------------------------------- /js/more/settings-onload.js: -------------------------------------------------------------------------------- 1 | /* All the things to do when settings.html is loaded, event listeners are here because Chrome doesn't want JS in the HTML. */ 2 | 3 | window.onload = function() { 4 | var closeButtonManager = new CloseButtonManager(); 5 | var localizer = new Localizer(); 6 | var settingsManager = new SettingsManager(); 7 | }; 8 | -------------------------------------------------------------------------------- /js/Topbar/PrintManager.js: -------------------------------------------------------------------------------- 1 | function PrintManager() { 2 | /* Outlets */ 3 | this.printButton = $("#print"); 4 | 5 | /* Events */ 6 | this.printButton.on("click", function () { window.print(); }); 7 | Mousetrap.bind(["command+p", "ctrl+p"], function(e) { window.print(); return false; }); // We only need to call window.print() to do it well, everything is in CSS. 8 | } 9 | 10 | PrintManager.prototype = { 11 | constructor: PrintManager 12 | }; 13 | -------------------------------------------------------------------------------- /css/header.css: -------------------------------------------------------------------------------- 1 | /* ======== 2 | HEADER 3 | ======== 4 | Styles for the header. 5 | It is displayed as a 100%*60px blue block. Its position is fixed so that it does not get in the way of other elements. */ 6 | 7 | #mado-header { 8 | /* Positioning */ 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | z-index: 1000; 13 | 14 | /* Box-model */ 15 | width: 100%; 16 | height: 60px; 17 | overflow: visible; 18 | 19 | /* Visual */ 20 | background-color: #325c9c; 21 | } -------------------------------------------------------------------------------- /js/more/Localizer.js: -------------------------------------------------------------------------------- 1 | function Localizer() { 2 | /* Outlets */ 3 | this.localizedText = $(".localized"); // Text to translate. 4 | 5 | /* Initialization */ 6 | this.init(); 7 | } 8 | 9 | Localizer.prototype = { 10 | constructor: Localizer, 11 | /* Set the window depending on the operating system. */ 12 | init: function() { 13 | this.localizedText.each(function() { 14 | $(this).html(chrome.i18n.getMessage(this.innerHTML)); 15 | }); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mado 2 | 3 | [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/) 4 |

5 | 6 | 7 |

8 | 9 | Mado is a Markdown editor application. It works on Chrome OS, Linux, Mac OS X and Windows. You can use it to take notes, write blog posts or edit documents. 10 | 11 | ## Main contributors 12 | 13 | * [Armand Grillet](https://twitter.com/ArmandGrillet) 14 | * [Théophile Barbin](https://twitter.com/TheophileBarbin) 15 | -------------------------------------------------------------------------------- /js/onload.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | var editor = new Editor(); // The editor. 3 | var app = new App(editor); 4 | var appWindow = new AppWindow(app); 5 | 6 | /* Localization */ 7 | $(".localized-placeholder").each(function() { 8 | $(this).attr("placeholder", chrome.i18n.getMessage($(this).attr("placeholder"))); // Placeholders to translate. 9 | }); 10 | 11 | $(".localized").each(function() { // Text to translate. 12 | $(this).html(chrome.i18n.getMessage(this.innerHTML)); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /css/retina.css: -------------------------------------------------------------------------------- 1 | /* ========================= 2 | RETINA RESPONSIVE RULES 3 | ========================= 4 | This part describes how Mado should be displayed on retina screens. */ 5 | 6 | @media only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen and (-o-min-device-pixel-ratio: 13/10), only screen and (min-resolution: 120dpi) { 7 | /* HELP INPUT SPECIFICS: HELP RESULTS */ 8 | /* Example images for the image insertion syntax */ 9 | .example-image { 10 | /* Visual */ 11 | background-image: url("../img/example-image@2x.png"); 12 | background-size: 20px 16px; 13 | } 14 | } -------------------------------------------------------------------------------- /css/document-base.css: -------------------------------------------------------------------------------- 1 | /* ============== 2 | MISCELLANOUS 3 | ============== */ 4 | 5 | body { padding: 60px 0 20px; } /* Simplifies positioning according to the topbar and the footer; the padding-top is the sum of the window bar and the navbar */ 6 | 7 | #document { 8 | /* Positioning */ 9 | position: relative; 10 | 11 | /* Box-model */ 12 | width: 100%; 13 | height: 100%; 14 | overflow: hidden; 15 | 16 | /* Visual */ 17 | background-color: #fafafa; 18 | } 19 | 20 | 21 | /* ======= 22 | FONTS 23 | ======= */ 24 | 25 | @font-face { 26 | /* Typography */ 27 | font-family: SourceCodePro; 28 | src: url('../fonts/SourceCodePro-Regular.ttf'); 29 | } -------------------------------------------------------------------------------- /css/more/about.css: -------------------------------------------------------------------------------- 1 | ul li { 2 | /* Positioning */ 3 | position: relative; 4 | 5 | /* Box-model */ 6 | padding-left: 1.3em; 7 | } 8 | 9 | ul li:before { 10 | /* Positioning */ 11 | position: absolute; 12 | top: 0; 13 | left: 0; 14 | 15 | /* Box-model */ 16 | display: block; 17 | width: 1.3em; 18 | 19 | /* Misc */ 20 | content: "\2022"; /* '\2022' is the escaped Unicode version of '•' (bullet) */ 21 | } 22 | 23 | .about-logo { 24 | /* Positioning */ 25 | margin: 10px auto; 26 | 27 | /* Box-model */ 28 | display: block; 29 | } 30 | 31 | /* License */ 32 | .mit-license { 33 | /* Typography */ 34 | font-size: 0.75em; 35 | line-height: 1.35em; 36 | } -------------------------------------------------------------------------------- /css/more/more-frame-chromeos.css: -------------------------------------------------------------------------------- 1 | /* ===================================== 2 | WINDOWS OPTIONS CLOSE BUTTON STYLES 3 | ===================================== 4 | Specific styles for Mado's options' close button on Windows. */ 5 | 6 | #window-close { 7 | /* Positioning */ 8 | position: fixed; 9 | top: 0; 10 | right: 0; 11 | 12 | /* Box-model */ 13 | width: 40px; 14 | height: 20px; 15 | 16 | /* Typography */ 17 | color: #fff; 18 | line-height: 20px; 19 | text-align: center; 20 | 21 | /* Visual */ 22 | background-color: #f24848; 23 | 24 | /* Misc */ 25 | -webkit-app-region: no-drag; /* Disable drag action */ 26 | } 27 | 28 | #window-close:hover { background-color: #d11; } 29 | 30 | #window-close:active { background-color: #c01a1a; } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mado", 3 | "version": "0.4.1", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/ArmandGrillet/Mado.git" 8 | }, 9 | "devDependencies": { 10 | "grunt": "latest", 11 | "grunt-banner": "latest", 12 | "grunt-concat-css": "latest", 13 | "grunt-contrib-clean": "latest", 14 | "grunt-contrib-concat": "latest", 15 | "grunt-contrib-copy": "latest", 16 | "grunt-contrib-cssmin": "latest", 17 | "grunt-contrib-jshint": "latest", 18 | "grunt-contrib-uglify": "latest", 19 | "grunt-htmlclean": "latest", 20 | "grunt-processhtml": "latest", 21 | "grunt-yui-compressor": "latest" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /js/more/ShortcutsManager.js: -------------------------------------------------------------------------------- 1 | function ShortcutsManager() { 2 | /* Outlets */ 3 | this.shortcutUnavailableOnMac = $("#help-shortcut"); 4 | this.ctrlOrCmdKey = $(".ctrl-cmd-key"); // The div displaying Ctrl or Cmd. 5 | 6 | /* Initialization */ 7 | this.init(); 8 | } 9 | 10 | ShortcutsManager.prototype = { 11 | constructor: ShortcutsManager, 12 | 13 | /* To do only when the window is initialized. */ 14 | init: function() { 15 | if (navigator.appVersion.indexOf("Mac") > -1) {// If the user is on a Mac. 16 | this.ctrlOrCmdKey.html("⌘"); // Insert the "Cmd" symbol. 17 | this.shortcutUnavailableOnMac.css("display", "none"); // Hide the help's shortcut. 18 | } else { 19 | this.ctrlOrCmdKey.html("Ctrl"); // Insert the "Ctrl" string. 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /css/themes/home.css: -------------------------------------------------------------------------------- 1 | /* Default style for the conversion view. Its name, "Home", is obvious and reminds of the comfort and softness of a familiar house. */ 2 | 3 | /* ------- 4 | Lists 5 | ------- */ 6 | /* UNORDERED LISTS */ 7 | #html-conversion ul > li:before { 8 | /* Box-model */ 9 | width: 2em; 10 | 11 | /* Typography */ 12 | text-align: right; 13 | } 14 | 15 | /* ORDERED LISTS */ 16 | #html-conversion ol > li:before { 17 | /* Box-model */ 18 | width: 2em; 19 | 20 | /* Typography */ 21 | text-align: right; 22 | 23 | /* Misc */ 24 | content: counter(list)"."; 25 | } 26 | 27 | /* ------------- 28 | Blockquotes 29 | ------------- */ 30 | #html-conversion blockquote { 31 | /* Positioning */ 32 | margin-bottom: 0.7em; 33 | margin-left: auto; 34 | 35 | /* Box-model */ 36 | width: calc(100% - 2.4em); 37 | padding: 0.7em 0 0 1.2em; 38 | 39 | /* Visual */ 40 | border-left: 0.7em solid rgba(0,0,0,.1); 41 | } -------------------------------------------------------------------------------- /css/more/more-frame-windows.css: -------------------------------------------------------------------------------- 1 | /* ===================================== 2 | WINDOWS OPTIONS CLOSE BUTTON STYLES 3 | ===================================== 4 | Specific styles for Mado's options' close button on Windows. */ 5 | 6 | #window-close { 7 | /* Positioning */ 8 | position: fixed; 9 | top: 0; 10 | right: 0; 11 | 12 | /* Box-model */ 13 | width: 40px; 14 | height: 20px; 15 | padding-left: 1px; /* Counterbalance the right border */ 16 | 17 | /* Typography */ 18 | color: #fff; 19 | line-height: 18px; 20 | text-align: center; 21 | 22 | /* Visual */ 23 | background-color: #f24848; 24 | border-color: #991515; 25 | border-style: solid; 26 | border-width: 1px 1px 0 0; 27 | 28 | /* Misc */ 29 | -webkit-app-region: no-drag; /* Disable drag action */ 30 | } 31 | 32 | #window-close:hover { background-color: #d11; } 33 | 34 | #window-close:active { background-color: #c01a1a; } 35 | 36 | /* ------- 37 | Frame 38 | ------- */ 39 | body { border: 1px solid #5f5e52; } -------------------------------------------------------------------------------- /css/init.min.css: -------------------------------------------------------------------------------- 1 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,textarea,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}*:focus{outline:0}@viewport{width:device-width}@-ms-viewport{width:device-width}html,body{width:100%;height:100%} -------------------------------------------------------------------------------- /css/more/more-frame-mac.css: -------------------------------------------------------------------------------- 1 | /* ====================================== 2 | MAC OS X OPTIONS CLOSE BUTTON STYLES 3 | ====================================== 4 | Specific styles for Mado's options' close button on Mac OS X. */ 5 | 6 | #window-close { 7 | /* Positioning */ 8 | position: fixed; 9 | top: 4px; 10 | left: 4px; 11 | 12 | /* Box-model */ 13 | width: 12px; 14 | height: 12px; 15 | 16 | /* Typography */ 17 | line-height: 12px; 18 | text-align: left; 19 | color: transparent; 20 | 21 | /* Visual */ 22 | background-color: #f24848; 23 | border-radius: 6px; 24 | 25 | /* Misc */ 26 | -webkit-app-region: no-drag; /* Disable drag action */ 27 | } 28 | 29 | /* These styles make the icons appear centered */ 30 | #window-close:before { 31 | /* Positioning */ 32 | position: relative; 33 | left: -2px; /* 16px (original font square size) - 12px (actual buttons' width) = 4px, then 4px / 2 (because it's centered) = 2px */ 34 | } 35 | 36 | #window-close:hover { color: #832727; } 37 | 38 | #window-close:active { background-color: #d11; } -------------------------------------------------------------------------------- /js/Editor/NameManager.js: -------------------------------------------------------------------------------- 1 | function NameManager() { 2 | /* Outlets */ 3 | this.windowName = $("#doc-name"); // The name at the top of the window. 4 | this.headTitle = $("title")[0]; // The tag in the head. 5 | 6 | /* Variables */ 7 | this.name = undefined; 8 | } 9 | 10 | NameManager.prototype = { 11 | constructor: NameManager, 12 | /* Return the name of the file. */ 13 | getName: function() { 14 | return this.name; 15 | }, 16 | 17 | /* Return if the file edited has a name. */ 18 | isNamed: function() { 19 | if (this.name) { 20 | return true; 21 | } else { 22 | return false; 23 | } 24 | }, 25 | 26 | /* Set the name of the file. 27 | * newName: name of the file edited. 28 | */ 29 | set: function(newName) { 30 | this.name = newName; 31 | this.windowName.html(newName + " -"); // We put a caret between file's name and Mado. 32 | this.headTitle.innerHTML = newName + " - Mado"; // We still display Mado in the title. 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /css/more/shortcuts.css: -------------------------------------------------------------------------------- 1 | /* Wrapping each couple into a box */ 2 | .shortcut-container { 3 | /* Box-model */ 4 | height: auto; 5 | padding-bottom: 13px; /* This makes 40 pixels with the line height */ 6 | } 7 | 8 | /* Wrapping and positioning each shortcut combination */ 9 | .shortcut { 10 | /* Positioning */ 11 | float: left; 12 | 13 | /* Box-model */ 14 | width: 33%; 15 | } 16 | 17 | .shortcut-info { 18 | /* Box-model */ 19 | padding-left: 33%; 20 | } 21 | 22 | /* Styling the keys so that they feel like actual keys */ 23 | .shortcut-key { 24 | /* Box-model */ 25 | display: inline-block; 26 | min-width: 27px; 27 | padding: 0 5px; 28 | 29 | /* Typography */ 30 | color: #fff; 31 | text-align: center; 32 | 33 | /* Visual */ 34 | background-color: #4a4942; 35 | border-bottom: 1px solid #000; 36 | border-radius: 3px; 37 | } 38 | 39 | /* Shift key */ 40 | .shift-key::before { content: "\21e7\a0Shift"; } /* '\21e7' is the escaped Unicode version of '⇧' (convert '8679' in hexa, then add a backslash before the result). '\a0' is the escaped Unicode version of ' '. This is necessary to insert special chars via "content" CSS property */ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT): 2 | 3 | Copyright (c) 2015 Armand Grillet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "background": { 4 | "scripts": [ 5 | "js/background.js" 6 | ] 7 | } 8 | }, 9 | "default_locale": "en", 10 | "manifest_version": 2, 11 | "name": "__MSG_appName__", 12 | "version": "0.5", 13 | 14 | "author": "Armand Grillet", 15 | "description": "__MSG_appDesc__", 16 | "file_handlers": { 17 | "text": { 18 | "extensions": [ 19 | "md" 20 | ] 21 | } 22 | }, 23 | "icons": { 24 | "16": "img/icon16.png", 25 | "48": "img/icon48.png", 26 | "128": "img/icon128.png", 27 | "256": "img/icon256.png" 28 | }, 29 | "minimum_chrome_version": "35", 30 | "offline_enabled": true, 31 | "permissions": [ 32 | "alwaysOnTopWindows", 33 | { 34 | "fileSystem": [ 35 | "write", 36 | "retainEntries" 37 | ] 38 | }, 39 | { 40 | "mediaGalleries": [ 41 | "read" 42 | ] 43 | }, 44 | "storage", 45 | "webview", 46 | "" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /js/Editor/SaveStateManager.js: -------------------------------------------------------------------------------- 1 | function SaveStateManager(editor) { 2 | /* Outlets */ 3 | this.saveState = $("#save-state"); 4 | 5 | /* Variables */ 6 | this.editor = editor; 7 | this.savedText = editor.getMarkdown(); // The text as the last save. 8 | } 9 | 10 | SaveStateManager.prototype = { 11 | constructor: SaveStateManager, 12 | 13 | /* Return if the file is saved. */ 14 | isSaved: function() { 15 | if (this.editor.getMarkdown() == this.savedText) { // The text in the textarea is the same as the text saved. */ 16 | return true; 17 | } else { 18 | return false; 19 | } 20 | }, 21 | 22 | /* Save the file. */ 23 | save: function() { 24 | this.savedText = this.editor.getMarkdown(); // Set the saved text with the current text. 25 | this.update(); 26 | }, 27 | 28 | /* Display if the file has unseaved changes or not. */ 29 | update: function() { 30 | if (this.editor.getMarkdown() != this.savedText) { 31 | this.saveState.html(""); 32 | } else { 33 | this.saveState.html(""); 34 | } 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /js/Topbar/ExportManager.js: -------------------------------------------------------------------------------- 1 | function ExportManager(app) { 2 | /* Outlets */ 3 | this.exportButton = $("#export"); 4 | 5 | /* Variables */ 6 | this.app = app; 7 | 8 | /* Events */ 9 | this.exportButton.on("click", $.proxy(function () { this.apply(); }, this)); 10 | } 11 | 12 | ExportManager.prototype = { 13 | constructor: ExportManager, 14 | 15 | /* Exportation of the document edited. */ 16 | apply: function() { 17 | var textToEport = marked(this.app.getEditorText()); // Get the text. 18 | var displayedName = chrome.i18n.getMessage("msgDocument") + ".html"; // If the document is not saved we use something standard. 19 | if (this.app.getName()) { // We have a real name so we use it. 20 | displayedName = this.app.getName().replace(/\.[^/.]+$/, "") + ".html"; 21 | } 22 | chrome.fileSystem.chooseEntry({ type: "saveFile", suggestedName: displayedName }, function(exportedFile) { // Save the file but it is in HTML. 23 | if (exportedFile) { 24 | exportedFile.createWriter( function(writer) { 25 | writer.write(new Blob([textToEport], { type: "text/HTML" })); 26 | }, function(error) { console.log(error); }); 27 | } 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /js/Topbar/NewFileManager.js: -------------------------------------------------------------------------------- 1 | function NewFileManager(app) { 2 | /* Outlets */ 3 | this.newButton = $("#new"); 4 | 5 | /* Variables */ 6 | this.app = app; 7 | 8 | /* Events */ 9 | this.newButton.on("click", $.proxy(function () { this.apply(); }, this)); 10 | Mousetrap.bind(["command+n", "ctrl+n"], $.proxy(function(e) { this.apply(); return false; }, this)); // Ctrl+n = new window. 11 | } 12 | 13 | NewFileManager.prototype = { 14 | constructor: NewFileManager, 15 | apply: function() { 16 | if ((this.app.getEditorText().length > 0 && this.app.getEditorText() != chrome.i18n.getMessage("msgFirstLaunch")) || (this.app.isDocumentNamed())) { 17 | chrome.app.window.create("mado.html", { 18 | outerBounds: { 19 | left: (window.screenX + 20), // "+ 20" to watch this is a new window. 20 | top: (window.screenY + 20), 21 | width: window.innerWidth, 22 | height: window.innerHeight, 23 | minWidth: 750, 24 | minHeight: 330 25 | }, 26 | frame: "none", 27 | 28 | }); 29 | } else if (this.app.getEditorText() == chrome.i18n.getMessage("msgFirstLaunch")) { 30 | this.app.resetEditor(); 31 | this.app.focusOnEditor(); 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /js/more/SettingsManager.js: -------------------------------------------------------------------------------- 1 | function SettingsManager() { 2 | /* Outlets */ 3 | this.markdownSyntax = $("#markdown-radio"); 4 | this.gfmSyntax = $("#gfm-radio"); 5 | this.reset = $("#reset"); 6 | 7 | /* Events */ 8 | this.markdownSyntax.add(this.gfmSyntax).on("click", $.proxy(function() { chrome.storage.local.set({ "gfm": this.gfmSyntax[0].checked }); }, this)); 9 | this.reset.on("click", $.proxy(function() { this.clean(); }, this)); 10 | 11 | /* Initialization */ 12 | this.init(); 13 | } 14 | 15 | SettingsManager.prototype = { 16 | constructor: SettingsManager, 17 | 18 | /* Reset Mado and restart it. */ 19 | clean: function() { 20 | chrome.storage.local.clear(function() { 21 | chrome.runtime.reload(); 22 | }); 23 | }, 24 | 25 | /* Get the correct syntax and display it. */ 26 | init: function() { 27 | chrome.storage.local.get("gfm", $.proxy(function(mado) { 28 | if (mado.gfm) { 29 | if (mado.gfm === true) { 30 | this.gfmSyntax.prop("checked", true); 31 | } else { 32 | this.markdownSyntax.prop("checked", true); 33 | } 34 | } 35 | else { 36 | chrome.storage.local.set({ "gfm": true }); 37 | this.gfmSyntax.prop("checked", true); 38 | } 39 | }, this)); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /js/Editor/UrlManager.js: -------------------------------------------------------------------------------- 1 | function UrlManager(divWithUrls) { 2 | /* Outlets */ 3 | this.linkUrlSpan = $("#link-url"); // Div displaying the url. 4 | 5 | /* Events */ 6 | divWithUrls.on("mouseenter", "a", $.proxy(function(e) { // Mouse hover a link in the div with Urls. 7 | if (e.currentTarget.href.indexOf("chrome-extension://") == -1) { // It is not an internal link. 8 | this.linkUrlSpan.html(e.currentTarget.href); // innerHTML set to be the href. 9 | } else { 10 | this.linkUrlSpan.html(e.currentTarget.hash); // It is an internal link, innerHTML set to be the hash. 11 | } 12 | this.linkUrlSpan.addClass("show"); // Display the span. 13 | }, this)); 14 | 15 | divWithUrls.on("mouseleave", "a", $.proxy(function() { 16 | this.linkUrlSpan.removeClass("show"); // Hide the span. 17 | }, this)); 18 | 19 | divWithUrls.on("click", "a", function(e) { // Click on a link 20 | if (e.currentTarget.href.indexOf("chrome-extension://") != -1) { // Click on an inner link. 21 | e.preventDefault(); 22 | if (e.currentTarget.hash !== "" && $(e.currentTarget.hash).length !== 0) { 23 | divWithUrls.scrollTop($(e.currentTarget.hash).position().top); // Scroll to the link pointed in the document. 24 | } 25 | } 26 | }); 27 | } 28 | 29 | UrlManager.prototype = { 30 | constructor: UrlManager 31 | }; 32 | -------------------------------------------------------------------------------- /css/responsive.css: -------------------------------------------------------------------------------- 1 | /* ================================ 2 | RESPONSIVE FOR SMALLER SCREENS 3 | ================================ */ 4 | /* Screens strictly under 1600 pixels */ 5 | @media screen and (max-width: 1599px) { 6 | /* -------------- 7 | Navbar items 8 | -------------- */ 9 | .nav-icon { padding: 0; } 10 | 11 | .nav-label { 12 | /* Box-model */ 13 | width: 0; 14 | 15 | /* Misc */ 16 | opacity: 0; 17 | } 18 | 19 | /* Screens strictly under 1366 pixels */ 20 | @media screen and (max-width: 1159px) { 21 | /* --------------- 22 | Navbar switch 23 | --------------- */ 24 | #view-switch { width: 180px; } /* Normal width minus the "Both" button's width */ 25 | 26 | /* The second switch button ("Both") */ 27 | .switch-button:nth-of-type(2) { 28 | /* Box-model */ 29 | width: 0; 30 | padding: 0; 31 | } 32 | 33 | /* Switch cursor's position when in markdown view and normal view */ 34 | #switch-cursor.markdown-view, 35 | #switch-cursor.normal, 36 | #switch-cursor { 37 | /* Positioning */ 38 | margin-left: -49px; 39 | } 40 | 41 | /* Switch cursor's position when in HTML view */ 42 | #switch-cursor.conversion-view { margin-left: 41px; } 43 | 44 | /* ----------- 45 | Workspace 46 | ----------- */ 47 | /* Setting the workspace's behavior in normal mode when on small screen */ 48 | #workspace.normal { width: 200%; } 49 | 50 | .normal #center-line-container { 51 | /* Positioning */ 52 | left: 100%; 53 | 54 | /* Misc */ 55 | opacity: 0; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /js/more/CloseButtonManager.js: -------------------------------------------------------------------------------- 1 | function CloseButtonManager() { 2 | /* Outlets */ 3 | this.head = $("head")[0]; // The "head" section of the main app. 4 | this.close = $("#window-close"); 5 | 6 | /* Events */ 7 | this.close.on("click", function(e) { chrome.app.window.current().close(); }); 8 | 9 | /* Initialization */ 10 | this.init(); 11 | } 12 | 13 | CloseButtonManager.prototype = { 14 | constructor: CloseButtonManager, 15 | 16 | init: function() { 17 | var operatingSystem; 18 | 19 | var frameStylesheetLink = document.createElement("link"); // Create a link that will be the correct CSS file for the more-frame. 20 | frameStylesheetLink.setAttribute("rel", "stylesheet"); 21 | frameStylesheetLink.setAttribute("type", "text/css"); 22 | 23 | if (navigator.appVersion.indexOf("Mac") > -1) { // If the user is on a Mac, redirect to the Mac window frame styles. 24 | operatingSystem = "mac"; 25 | } else if (navigator.appVersion.indexOf("Win") > -1) { // If the user is on a Windows PC, redirect to the Windows window frame styles. 26 | operatingSystem = "windows"; 27 | } else { // If the user is on another type of computer, redirect to the generic window frame styles (which are primarily Chrome OS's styles). 28 | operatingSystem = "chromeos"; 29 | } 30 | 31 | frameStylesheetLink.setAttribute("href", "../../css/more/more-frame-" + operatingSystem + ".css"); 32 | this.close.attr("class", "cta little-icon-" + operatingSystem.substring(0,3) + "-close"); // Set the correct close icon. 33 | 34 | this.head.appendChild(frameStylesheetLink); // Append the link node to the "head" section. 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /js/Topbar/SaveFileAsManager.js: -------------------------------------------------------------------------------- 1 | function SaveFileAsManager(app) { 2 | /* Outlets */ 3 | this.saveAsButton = $("#save-as"); 4 | 5 | /* Variables */ 6 | this.app = app; 7 | 8 | /* Events */ 9 | this.saveAsButton.on("click", $.proxy(function () { this.apply(); }, this)); 10 | Mousetrap.bind(["command+shift+s", "ctrl+shift+s"], $.proxy(function(e) { this.apply(); return false; }, this)); // Ctrl+n = new window. 11 | } 12 | 13 | SaveFileAsManager.prototype = { 14 | constructor: SaveFileAsManager, 15 | apply: function(callback) { 16 | var t = this; 17 | chrome.fileSystem.chooseEntry( { type: "saveFile", suggestedName: chrome.i18n.getMessage("msgDocument") + ".md" }, function(savedFile) { 18 | if (savedFile) { 19 | savedFile.createWriter(function(fileWriter) { 20 | var truncated = false; // Variable used to do a good save even if the new file is shorter than the old one. 21 | fileWriter.onwriteend = function(e) { 22 | if (!truncated) { 23 | truncated = true; 24 | this.truncate(this.position); 25 | return; 26 | } 27 | chrome.storage.local.set({ "newFile": chrome.fileSystem.retainEntry(savedFile), "newFilePath": savedFile.fullPath }, function() { // For RecentFilesManager. 28 | t.app.setEditorEntry(savedFile); // The editor know that the document is saved. 29 | if (callback) { 30 | callback(); 31 | } 32 | }); 33 | }; 34 | fileWriter.write(new Blob([t.app.getEditorText()], {type: 'plain/text'})); 35 | }, function(error) { console.log(error); }); 36 | } 37 | }); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /css/window-frame-chromeos.css: -------------------------------------------------------------------------------- 1 | /* ============================= 2 | CHROME OS WINDOW BAR STYLES 3 | ============================= 4 | Specific styles for Mado's window bar on Chrome OS. */ 5 | 6 | #window-bar ul { height: 100%; } /* Enable percentage-based height for the list items */ 7 | 8 | #window-bar li { 9 | /* Positioning */ 10 | float: right; 11 | 12 | /* Box-model */ 13 | width: 40px; 14 | height: 100%; 15 | 16 | /* Typography */ 17 | color: #0c2a54; 18 | line-height: 24px; 19 | text-align: center; 20 | 21 | /* Visual */ 22 | background: transparent; 23 | 24 | /* Misc */ 25 | transition: all 0.2s ease; 26 | -webkit-app-region: no-drag; /* Disable drag action */ 27 | } 28 | 29 | #window-close-button, 30 | #window-maximize, 31 | #window-minimize { 32 | /* Visual */ 33 | text-shadow: 0 0 1px rgba(255,255,255,.15); 34 | 35 | /* Misc */ 36 | transition: all 0.1s ease; 37 | } 38 | 39 | #window-close-button { height: 100%; } 40 | 41 | #window-bar li:active, 42 | #window-close-button:active { transition: none; } 43 | 44 | /* Background colors for the buttons */ 45 | #window-close-button:hover, 46 | #window-maximize:hover, 47 | #window-minimize:hover { 48 | /* Visual */ 49 | background-color: #4a7aba; 50 | box-shadow: 0 0 9px rgba(255,255,255,.1) inset; 51 | text-shadow: 0 0 1px rgba(255,255,255,.4); 52 | } 53 | 54 | #window-close-button:active, 55 | #window-maximize:active, 56 | #window-minimize:active { 57 | /* Typography */ 58 | color: #000; 59 | 60 | /* Visual */ 61 | background-color: #143d7a; 62 | box-shadow: 0 0 9px rgba(0,0,0,.1) inset; 63 | text-shadow: 0 0 1px rgba(255,255,255,.5); 64 | } 65 | 66 | /* ------- 67 | Alert 68 | ------- */ 69 | /* When the user clicks the "Close" button with unsaved changes, the displayer is deployed and shows the alert */ 70 | #close-alert-displayer { 71 | /* Positioning */ 72 | top: 20px; 73 | right: 5px; 74 | 75 | /* Misc */ 76 | -webkit-transform-origin: 436px 1px; 77 | } 78 | 79 | /* The arrow is a gray corner for the container */ 80 | #close-alert-arrow { margin: 1px 0 0 432px; } -------------------------------------------------------------------------------- /js/Editor/Counter.js: -------------------------------------------------------------------------------- 1 | function Counter(countedDiv) { 2 | /* Outlets */ 3 | this.charsDiv = $("#character-nb"); // The div containing the number of characters. 4 | this.countedDiv = countedDiv; // The div that is counted. 5 | this.wordsDiv = $("#word-nb"); // The div containing the number of words. 6 | 7 | /* Events */ 8 | this.charsDiv.add(this.wordsDiv).on("click", $.proxy(function () { 9 | this.toggleInformationDisplayed(); // Toggle the number displayed on click. 10 | }, this)); 11 | 12 | /* Initialization */ 13 | this.charsDiv.css("display", "none"); // On launch we display the number of words. 14 | this.update(); 15 | } 16 | 17 | Counter.prototype = { 18 | constructor: Counter, 19 | 20 | /* Displays the number of characters and words in the div counted. */ 21 | display: function(counter) { 22 | this.charsDiv.html(" " + counter.characters + " " + chrome.i18n.getMessage("msgCharacters") + " "); 23 | this.wordsDiv.html(" " + counter.words + " " + chrome.i18n.getMessage("msgWords") + " "); 24 | if (counter.characters == 1) { 25 | this.charsDiv.html(" " + " " + chrome.i18n.getMessage("msgCharacter") + " "); 26 | } 27 | if (counter.words == 1) { 28 | this.wordsDiv.html(" " + counter.words + " " + chrome.i18n.getMessage("msgWord") + " "); 29 | } 30 | }, 31 | 32 | /* Toggles the information displayed. */ 33 | toggleInformationDisplayed: function() { 34 | if (this.charsDiv.css("display") == "none") { 35 | this.charsDiv.css("display", "inline"); 36 | this.wordsDiv.css("display", "none"); 37 | } else { 38 | this.charsDiv.css("display", "none"); 39 | this.wordsDiv.css("display", "inline"); 40 | } 41 | }, 42 | 43 | /* Update number of words and characters. */ 44 | update: function() { 45 | Countable.count(this.countedDiv, $.proxy(function(counter) { this.display(counter); }, this), { stripTags: true }); // Without HTML tags. 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /css/more/options-base.css: -------------------------------------------------------------------------------- 1 | /* ============== 2 | MISCELLANOUS 3 | ============== */ 4 | 5 | body { padding: 40px 0 20px 20px; } 6 | 7 | #options-container { 8 | /* Box-model */ 9 | padding-right: 20px; 10 | width: 100%; 11 | height: 100%; 12 | overflow-y: auto; 13 | } 14 | 15 | 16 | /* =============== 17 | OPTION STYLES 18 | =============== */ 19 | 20 | /* Window bar for the option window */ 21 | #window-bar { 22 | /* Positioning */ 23 | position: fixed; 24 | top: 0; 25 | left: 0; 26 | z-index: 3000; 27 | 28 | /* Box-model */ 29 | width: 100%; 30 | height: 20px; 31 | 32 | /* Visual */ 33 | -webkit-app-region: drag; /* Enable drag action */ 34 | } 35 | 36 | /* Main title for the option window */ 37 | #option-title { 38 | /* Box-model */ 39 | padding-bottom: 14px; 40 | 41 | /* Typography */ 42 | font-family: "Segoe UI Light", "HelveticaNeue-Light", "Helvetica Neue Light", "RobotoLight", "Roboto Light", "Open Sans", sans-serif; 43 | font-size: 1.8em; 44 | line-height: 40px; 45 | } 46 | 47 | /* Content block inside the option window */ 48 | .option-part { line-height: 27px; } 49 | 50 | .option-part:not(:first-of-type) { 51 | /* Box-model */ 52 | padding-top: 10px; 53 | 54 | /* Visual */ 55 | border-top: 1px solid #f0f0f0; 56 | } 57 | 58 | .option-part:not(:last-of-type) { padding-bottom: 10px; } 59 | 60 | .option-part-title { 61 | /* Box-model */ 62 | padding: 14px 0 13px; 63 | 64 | /* Typography */ 65 | font-family: "Segoe UI Light", "HelveticaNeue-Light", "Helvetica Neue Light", "Roboto Light", "Open Sans", sans-serif; 66 | font-size: 1.2em; 67 | line-height: 27px; 68 | } 69 | 70 | .input-container { 71 | /* Positioning */ 72 | float: left; 73 | 74 | /* Box-model */ 75 | display: block; 76 | width: 40px; 77 | height: 27px; 78 | 79 | /* Typography */ 80 | text-align: center; 81 | } 82 | 83 | /* Label positioning; there is a .label-container class because the label itself, displayed as a block, would become clickable on the full window width. */ 84 | .option-part .label-container { 85 | /* Box-model */ 86 | display: block; 87 | width: 100%; 88 | padding-left: 40px; 89 | } -------------------------------------------------------------------------------- /js/Topbar/MoreWindowsManager.js: -------------------------------------------------------------------------------- 1 | function MoreWindowsManager() { 2 | /* Outlets */ 3 | this.moreButton = $("#more-button"); 4 | this.moreDisplayer = $("#more-displayer"); 5 | this.moreBox = $("#more-container"); 6 | 7 | this.settingsLine = $("#settings"); 8 | this.shortcutsLine = $("#shortcuts"); 9 | this.aboutLine = $("#about"); 10 | 11 | /* Events */ 12 | $(document).click($.proxy(function(e) { 13 | if ($(e.target).closest("#more-button").length) { 14 | this.moreDisplayer.toggleClass("hidden"); 15 | } else if (!this.moreDisplayer.hasClass("hidden") && !$(e.target).closest("#more-container").length) { 16 | this.moreDisplayer.addClass("hidden"); // This is not a cancellation. 17 | } 18 | }, this)); 19 | 20 | this.settingsLine.add(this.shortcutsLine).add(this.aboutLine).on("click", $.proxy(function(e) { 21 | this.apply(e.currentTarget.id); 22 | }, this)); 23 | } 24 | 25 | MoreWindowsManager.prototype = { 26 | constructor: MoreWindowsManager, 27 | 28 | /* Open the correct window with a good size. */ 29 | apply: function(windowId) { 30 | var newWindowConfig = { 31 | outerBounds: { 32 | left: Math.round((window.screenX + (($(window).width() - 498) / 2))), // Perfect left position. 33 | top: Math.round((window.screenY + (($(window).height() - 664) / 2))), // Perfect top position. 34 | width: 498, 35 | height: 664 36 | }, 37 | frame : "none", 38 | id: windowId, 39 | resizable: false 40 | }; 41 | 42 | if (windowId == "settings") { 43 | newWindowConfig.alwaysOnTop = true; 44 | } 45 | 46 | chrome.app.window.create("more/" + windowId + ".html", newWindowConfig, function(newWindow) { 47 | newWindow.outerBounds.setPosition( 48 | Math.round((window.screenX + (($(window).width() - 498) / 2))), 49 | Math.round((window.screenY + (($(window).height() - 664) / 2))) 50 | ); 51 | }); 52 | this.moreDisplayer.toggleClass("hidden"); // Hide the more displayer once a more window has been opened. 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /more/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Settings - Mado 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 |
36 | 37 |
38 | 39 |

htmlSettings

40 | 41 |
42 | 43 |

moreSettingsSyntaxTitle

44 | 45 |
46 |
47 |
48 | 49 |
50 | 51 |

moreSettingsResetTitle

52 | 53 | 54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /js/Editor/ImageArray.js: -------------------------------------------------------------------------------- 1 | function ImageArray() { 2 | this.images = []; // This array has 3 columns: Path | Data | Used during last conversion. 3 | } 4 | 5 | ImageArray.prototype = { 6 | constructor: ImageArray, 7 | 8 | /* Add an image to the array. 9 | * imagePath: path of the image added. 10 | * imageData: data of the image added. 11 | */ 12 | addImage: function(imagePath, imageData) { 13 | this.images.push([imagePath, imageData, true]); 14 | }, 15 | 16 | /* Remove the non-used images and set the use of used image to flase for the next iteration. */ 17 | clean: function() { 18 | for (var i = 0; i < this.images.length; i++) { 19 | if (this.images[i][2]) { 20 | this.images[i][2] = false; // Set the use to flase for the next iteration. 21 | } else { 22 | this.images.splice(i, 1); // Remove the row. 23 | i--; 24 | } 25 | } 26 | }, 27 | 28 | /* Return the image's data with its path. 29 | * Pre: imagePath is an existing path. 30 | * imagePath: path of the image wanted. 31 | */ 32 | getImage: function(imagePath) { 33 | for (var i = 0; i < this.images.length; i++) { // Put all the names 34 | if (this.images[i][0] == imagePath) { 35 | this.setUsed(imagePath); 36 | return this.images[i][1]; 37 | } else if (i == this.images.length -1) { 38 | throw "Image's path not available in image array."; 39 | } 40 | } 41 | }, 42 | 43 | /* Return if the path exist in the array. 44 | * path: path of the image that we want to check. 45 | */ 46 | hasPath: function(path) { 47 | for (var i = 0; i < this.images.length; i++) { 48 | if (this.images[i][0] == path) { // The image already exists. 49 | return true; 50 | } 51 | } 52 | return false; 53 | }, 54 | 55 | /* Set the image as used during. */ 56 | setUsed: function(path) { 57 | for (var i = 0; i < this.images.length; i++) { 58 | if (this.images[i][0] == path) { // It is the image we searched. 59 | this.images[i][2] = true; // The image has been used. 60 | break; 61 | } 62 | } 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /js/Editor/ScrollManager.js: -------------------------------------------------------------------------------- 1 | function ScrollManager(firstZone, secondZone) { 2 | /* Outlets */ 3 | this.centerLine = $("#center-line-container"); 4 | 5 | /* Variables */ 6 | this.atTheBottom = undefined; // True if the user is at the end of the document. 7 | this.firstZone = firstZone; // Zone 1 with synced scrolling. 8 | this.secondZone = secondZone; // Zone 2 with synced scrolling. 9 | this.lastFirstZoneHeight = firstZone[0].scrollHeight; // Save of the first zone height. 10 | 11 | /* Events */ 12 | var t = this; 13 | firstZone.add(secondZone).on("scroll", function (e) { // On scroll in one of the two zones. 14 | if ($(this).is(":hover")) { 15 | t.asyncScroll(this.id); 16 | } 17 | }); 18 | } 19 | 20 | ScrollManager.prototype = { 21 | constructor: ScrollManager, 22 | 23 | /* Apply a scroll on the zone not scrolled so that there is a scroll in the two zones, depending on the height of each zone. */ 24 | asyncScroll: function(zone) { 25 | if (zone == this.firstZone[0].id) { 26 | this.secondZone.scrollTop((this.firstZone.scrollTop() / (this.firstZone[0].scrollHeight - this.firstZone[0].offsetHeight)) * (this.secondZone[0].scrollHeight - this.secondZone[0].offsetHeight)); 27 | } else { 28 | this.firstZone.scrollTop((this.secondZone.scrollTop() / (this.secondZone[0].scrollHeight - this.secondZone[0].offsetHeight)) * (this.firstZone[0].scrollHeight - this.firstZone[0].offsetHeight)); 29 | } 30 | 31 | if (this.firstZone.scrollTop() + this.firstZone[0].offsetHeight == this.firstZone[0].scrollHeight) { 32 | this.atTheBottom = true; 33 | } else { 34 | this.atTheBottom = false; 35 | } 36 | }, 37 | 38 | /* Automatic scroll when writing. */ 39 | checkZonesHeight: function() { 40 | if (this.lastFirstZoneHeight < this.firstZone[0].scrollHeight) { // New height. 41 | if (this.atTheBottom) { // We are at the bottom. 42 | this.firstZone.scrollTop(this.firstZone[0].scrollHeight - this.firstZone[0].offsetHeight); 43 | this.secondZone.scrollTop(this.secondZone[0].scrollHeight - this.secondZone[0].offsetHeight); 44 | } 45 | } 46 | this.lastFirstZoneHeight = this.firstZone[0].scrollHeight; // Update the height saved. 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /css/window-frame-linux.css: -------------------------------------------------------------------------------- 1 | /* =========================== 2 | LINUX WINDOW FRAME STYLES 3 | =========================== 4 | Specific styles for Mado's window frame on Linux (based on Ubuntu). */ 5 | 6 | #window-bar li { 7 | /* Positioning */ 8 | position: absolute; 9 | top: 50%; 10 | margin-top: -6px; 11 | 12 | /* Box-model */ 13 | width: 15px; 14 | height: 15px; 15 | 16 | /* Typography */ 17 | /*line-height: 15px;*/ 18 | text-align: left; 19 | color: #325c9c; 20 | 21 | /* Visual */ 22 | border-radius: 8px; 23 | 24 | /* Misc */ 25 | transition: color 0.05s linear; 26 | -webkit-app-region: no-drag; /* Disable drag action */ 27 | } 28 | 29 | /* These styles make the icons appear centered */ 30 | #window-close-button:before, 31 | #window-bar li:before { 32 | /* Positioning */ 33 | position: relative; 34 | } 35 | 36 | #window-close { left: 10px; } 37 | 38 | #window-close-button { 39 | /* Box-model */ 40 | width: 100%; 41 | height: 100%; 42 | 43 | /* Typography */ 44 | color: #691700; 45 | 46 | /* Visual */ 47 | background-color: #e0652c; 48 | border-radius: 10px; 49 | } 50 | 51 | #window-minimize { left: 28px; } 52 | 53 | #window-maximize { left: 46px; } 54 | 55 | #window-minimize, 56 | #window-maximize { 57 | /* Visual */ 58 | background-color: #5489d3; 59 | } 60 | 61 | #window-close-button:hover { 62 | /* Typography */ 63 | color: #7f1b00; 64 | 65 | /* Visual */ 66 | background-color: #ea885d; 67 | } 68 | 69 | #window-close-button:active { 70 | /* Typography */ 71 | color: #4c0f00; 72 | 73 | /* Visual */ 74 | background-color: #b24d1e; 75 | } 76 | 77 | #window-maximize:hover, 78 | #window-minimize:hover { 79 | /* Typography */ 80 | color: #426aa5; 81 | 82 | /* Visual */ 83 | background-color: #77a7d8; 84 | } 85 | 86 | #window-maximize:active, 87 | #window-minimize:active { 88 | /* Typography */ 89 | color: #194584; 90 | 91 | /* Visual */ 92 | background-color: #4779b2; 93 | } 94 | 95 | /* ------- 96 | Alert 97 | ------- */ 98 | /* When the user clicks the "Close" button with unsaved changes, the displayer is deployed and shows the alert */ 99 | #close-alert-displayer { 100 | /* Positioning */ 101 | top: 17px; 102 | left: -2px; 103 | 104 | /* Misc */ 105 | -webkit-transform-origin: 9px 1px; 106 | } 107 | 108 | /* The arrow is a gray corner for the container */ 109 | #close-alert-arrow { margin: 1px 0 0 5px; } -------------------------------------------------------------------------------- /js/background.js: -------------------------------------------------------------------------------- 1 | /* Open Mado. 2 | * parameters: filepath if a file has to be open. 3 | */ 4 | function appLaunch(parameters) { 5 | if (parameters.items) { // User wants to open a Markdown file. 6 | chrome.storage.local.set({ "appInitFileEntry": chrome.fileSystem.retainEntry(parameters.items[0].entry), "editorInitFileEntry": chrome.fileSystem.retainEntry(parameters.items[0].entry) }, windowCreation); 7 | } else { // New file. 8 | windowCreation(); 9 | } 10 | } 11 | 12 | /* Set new bounds for Mado 13 | * bounds: new bounds. 14 | */ 15 | function newBounds (bounds) { 16 | chrome.storage.local.set({"lastX" : bounds.left, "lastY" : bounds.top, "lastWidth" : bounds.width, "lastHeight" : bounds.height }); 17 | } 18 | 19 | /* Create the window with a good size. */ 20 | function windowCreation () { 21 | var windowConfig = { 22 | outerBounds: { 23 | width: Math.round(screen.width * 0.85), 24 | height: Math.round(screen.height * 0.85), 25 | minWidth: 750, 26 | minHeight: 330 27 | }, 28 | frame: "none" 29 | }; 30 | 31 | chrome.storage.local.get(["lastX", "lastY", "lastWidth", "lastHeight"], function(mado) { 32 | if (!isNaN(mado.lastX) && mado.lastX >= 0 && !isNaN(mado.lastY) && mado.lastY >= 0 && !isNaN(mado.lastWidth) && mado.lastWidth >= 330 && !isNaN(mado.lastHeight) && mado.lastHeight >= 750) { 33 | windowConfig.outerBounds.left = mado.lastX; 34 | windowConfig.outerBounds.top = mado.lastY; 35 | windowConfig.outerBounds.width = mado.lastWidth; 36 | windowConfig.outerBounds.height = mado.lastHeight; 37 | } 38 | 39 | chrome.app.window.create("mado.html", windowConfig); 40 | }); 41 | } 42 | 43 | /* 44 | * Chrome method. 45 | */ 46 | 47 | /* Open mado.html in a new window when button pressed. */ 48 | chrome.app.runtime.onLaunched.addListener(function(parameters) { 49 | var param = parameters; 50 | chrome.storage.local.get("mado3To4", function(mado) { 51 | if (mado.mado3To4 !== true) { // First launch with 0.4, we reset everything because of some problems raised by users. 52 | chrome.storage.local.clear(function() { 53 | chrome.storage.local.set({ "mado3To4": true}); 54 | appLaunch(param); 55 | }); 56 | } else { 57 | appLaunch(param); 58 | } 59 | /* If you want to try opening a Markdown file with Mado on Windows, open the command line and try that : 60 | "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --app-id=YourAppId "C:\Path\To\document.md" 61 | */ 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /css/print.css: -------------------------------------------------------------------------------- 1 | /* ======= 2 | PRINT 3 | ======= 4 | This stylesheet describes the styles for the print version of a document. 5 | Only the HTML conversion view is printed. */ 6 | @media print { 7 | /* Pagination enabled (wouldn't work with fixed height and hidden overflow) */ 8 | html, 9 | body, 10 | #document, 11 | #workspace, 12 | #conversion-container, 13 | #html-conversion { 14 | /* Box-model */ 15 | height: auto; 16 | } 17 | 18 | body { padding: 1cm; } 19 | 20 | /* Delete inner margins and paddings */ 21 | #conversion-container, 22 | #html-conversion { 23 | /* Box-model */ 24 | padding: 0; 25 | } 26 | 27 | /* Delete useless UI elements */ 28 | #mado-header, 29 | #switch-cursor, 30 | .sidebar, 31 | #markdown-container, 32 | #center-line-container, 33 | #mado-footer { 34 | /* Box-model */ 35 | display: none; 36 | } 37 | 38 | #document { 39 | /* Visual */ 40 | background: transparent; /* Save ink: no background color */ 41 | border: none !important; /* Delete borders on Windows */ 42 | } 43 | 44 | #conversion-container { 45 | /* Box-model */ 46 | width: 100%; /* Even if the user is in "Both" view, the HTML view takes all the page's width */ 47 | 48 | /* Typography */ 49 | color: #000; /* Cleaner than dark grey for print */ 50 | } 51 | 52 | /* Links are made obvious (for example if the user prints in black & white) */ 53 | a { text-decoration: underline; } 54 | 55 | /* Links' URLs are displayed between parenthesis after the hypertext */ 56 | a:after { content: " (" attr(href) ")"; } 57 | 58 | /* Horizontal rules: these styles enable the horizontal rules to be printed (wouldn't work with background-color) */ 59 | hr { 60 | /* Visual */ 61 | background-color: transparent !important; 62 | border-top: 1px solid #000 !important; 63 | } 64 | 65 | p, 66 | blockquote { 67 | /* Typography */ 68 | orphans: 3; /* No single line at the bottom of a page */ 69 | widows: 3; /* No single line at the top of a page */ 70 | } 71 | 72 | blockquote, 73 | ul, 74 | ol { 75 | /* Typography */ 76 | page-break-inside: avoid; /* No break inside these elements */ 77 | } 78 | 79 | h1, 80 | h2, 81 | h3, 82 | h4, 83 | h5, 84 | h6, 85 | caption { 86 | /* Typography */ 87 | page-break-after: avoid; /* No break after these elements */ 88 | } 89 | 90 | td, 91 | th { 92 | /* Visual */ 93 | border: 1px solid #000 !important; 94 | } 95 | 96 | /* Zebra stripes canceled */ 97 | tbody tr:nth-child(2n+1) { background: transparent !important; } 98 | } -------------------------------------------------------------------------------- /css/window-frame-mac.css: -------------------------------------------------------------------------------- 1 | /* ============================== 2 | MAC OS X WINDOW FRAME STYLES 3 | ============================== 4 | Specific styles for Mado's window frame on Mac OS X. */ 5 | 6 | #window-bar li { 7 | /* Positioning */ 8 | position: absolute; 9 | top: 50%; 10 | margin-top: -6px; 11 | 12 | /* Box-model */ 13 | width: 12px; 14 | height: 12px; 15 | 16 | /* Typography */ 17 | line-height: 12px; 18 | text-align: left; 19 | color: transparent; 20 | 21 | /* Visual */ 22 | border-radius: 6px; 23 | 24 | /* Misc */ 25 | transition: color 0.05s linear; 26 | -webkit-app-region: no-drag; /* Disable drag action */ 27 | } 28 | 29 | /* These styles make the icons appear centered */ 30 | #window-close-button:before, 31 | #window-bar li:before { 32 | /* Positioning */ 33 | position: relative; 34 | left: -2px; /* 16px (original font square size) - 12px (actual buttons' width) = 4px, then 4px / 2 (because it's centered) = 2px */ 35 | } 36 | 37 | #window-close { left: 10px; } 38 | 39 | #window-close-button { 40 | /* Box-model */ 41 | width: 100%; 42 | height: 100%; 43 | 44 | /* Visual */ 45 | background-color: #f24848; 46 | border-radius: 10px; 47 | } 48 | 49 | #window-minimize { 50 | /* Positioning */ 51 | left: 30px; 52 | 53 | /* Visual */ 54 | background-color: #f2d048; 55 | } 56 | 57 | #window-maximize { 58 | /* Positioning */ 59 | left: 50px; 60 | 61 | /* Visual */ 62 | background-color: #82cc28; 63 | } 64 | 65 | #window-close-button:hover { color: #832727; } 66 | 67 | #window-close-button:active { background-color: #d11; } 68 | 69 | #window-minimize:hover { color: #814424; } 70 | 71 | #window-minimize:active { background-color: #ddb114; } 72 | 73 | #window-maximize:hover { color: #273a18; } 74 | 75 | #window-maximize:active { background-color: #64b00b; } 76 | 77 | /* ------- 78 | Alert 79 | ------- */ 80 | /* When the user clicks the "Close" button with unsaved changes, the displayer is deployed and shows the alert */ 81 | #close-alert-displayer { 82 | /* Positioning */ 83 | top: 16px; 84 | left: -3px; 85 | 86 | /* Misc */ 87 | -webkit-transform-origin: 20px 1px; 88 | } 89 | 90 | /* The arrow is a gray corner for the container */ 91 | #close-alert-arrow { margin: 1px 0 0 5px; } 92 | 93 | /* -------- 94 | Header 95 | -------- */ 96 | #save-state span { 97 | /* Box-model */ 98 | height: 18px !important; 99 | 100 | /* Typography */ 101 | line-height: 18px !important; 102 | } 103 | -------------------------------------------------------------------------------- /js/Topbar/OpenFileManager.js: -------------------------------------------------------------------------------- 1 | function OpenFileManager(app) { 2 | /* Outlets */ 3 | this.openButton = $("#open"); 4 | 5 | /* Variables */ 6 | this.app = app; 7 | 8 | /* Events */ 9 | this.openButton.on("click", $.proxy(function () { this.apply(); }, this)); 10 | Mousetrap.bind(["command+o", "ctrl+o"], $.proxy(function(e) { this.apply(); return false; }, this)); // Ctrl+n = new window. 11 | } 12 | 13 | OpenFileManager.prototype = { 14 | constructor: OpenFileManager, 15 | 16 | /* Open the standard window to open a file. */ 17 | apply: function() { 18 | chrome.fileSystem.chooseEntry({ type: "openFile", accepts:[{ extensions: ["markdown", "md", "txt"] }]}, 19 | $.proxy(function(loadedFile) { 20 | if (loadedFile) { 21 | this.open(loadedFile); 22 | } 23 | }, this) 24 | ); 25 | }, 26 | 27 | /* Open the loaded file in Mado. */ 28 | open: function(loadedFile) { 29 | var t = this; // Shortcut. 30 | if (loadedFile) { 31 | loadedFile.file( 32 | function(file) { 33 | var reader = new FileReader(); 34 | reader.onload = function(e) { 35 | if ((t.app.getEditorText().length > 0 && t.app.getEditorText() != chrome.i18n.getMessage("msgFirstLaunch")) || (t.app.isDocumentNamed())) { // Something is already in the editor, Mado opens a new window and we set local variables to open it well in the new window. 36 | chrome.storage.local.set({ "appInitFileEntry": chrome.fileSystem.retainEntry(loadedFile), "editorInitFileEntry": chrome.fileSystem.retainEntry(loadedFile), "newFile": chrome.fileSystem.retainEntry(loadedFile), "newFilePath": loadedFile.fullPath }, function() { 37 | t.app.newFile(); // Open a new window that will have the text. 38 | }); 39 | } else { 40 | chrome.storage.local.set({ "newFile": chrome.fileSystem.retainEntry(loadedFile), "newFilePath": loadedFile.fullPath }, function() { 41 | t.app.setEditorWithEntry(loadedFile, e.target.result); // Open the file in the current window, set the editor. 42 | }); 43 | } 44 | }; 45 | reader.readAsText(file); 46 | }, 47 | function(error) { console.log(error); } 48 | ); 49 | } 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /js/Topbar/LabelManager.js: -------------------------------------------------------------------------------- 1 | function LabelManager() { 2 | /* Variables. */ 3 | this.bigWidth = undefined; // We can display labels above this width. 4 | this.topbarButtons = [["#export", "msgExport"], ["#new", "msgNew"], ["#open", "msgOpen"], ["#print", "msgPrint"], ["#recent-button", "msgRecent"], ["#save", "msgSave"], ["#save-as", "msgSaveAs"]]; // Targets and labels linked. 5 | this.lastWidth = undefined; // Used to remove and add titles only when it is useful. 6 | 7 | /* Events */ 8 | chrome.app.window.current().onBoundsChanged.addListener($.proxy(function () { this.update(); }, this)); 9 | 10 | /* Initialization */ 11 | this.init(); 12 | } 13 | 14 | LabelManager.prototype = { 15 | constructor: LabelManager, 16 | 17 | /* Display labels in the topbar */ 18 | displayLabels: function() { 19 | $(".nav-label").each(function() { 20 | $(this).removeClass("hidden"); 21 | $(this).removeAttr("title"); 22 | }); 23 | 24 | for (var j = 0; j < this.topbarButtons.length; j++) { 25 | $(this.topbarButtons[j][0]).removeAttr("title"); // Removes the titles because we are displaying them entirely. 26 | } 27 | }, 28 | 29 | /* Hide labels in the topbar and add a title to each button */ 30 | hideLabels: function() { 31 | $(".nav-label").each(function() { 32 | $(this).addClass("hidden"); 33 | }); 34 | 35 | for (var i = 0; i < this.topbarButtons.length; i++) { 36 | $(this.topbarButtons[i][0]).attr("title", chrome.i18n.getMessage(this.topbarButtons[i][1])); // We set the localized title. 37 | } 38 | }, 39 | 40 | init: function() { 41 | switch (chrome.i18n.getUILanguage()) { 42 | case "fr": 43 | this.hideLabels(); // French words are too long thus we're not showing labels, only titles. 44 | break; 45 | default: 46 | this.bigWidth = 1600; 47 | this.lastWidth = chrome.app.window.current().outerBounds.width; 48 | if (chrome.app.window.current().outerBounds.width < this.bigWidth) { 49 | this.hideLabels(); 50 | } 51 | } 52 | }, 53 | 54 | update: function() { 55 | if (chrome.app.window.current().outerBounds.width < this.bigWidth && this.lastWidth >= this.bigWidth) { 56 | this.hideLabels(); 57 | } else if (chrome.app.window.current().outerBounds.width >= this.bigWidth && this.lastWidth < this.bigWidth) { 58 | this.displayLabels(); 59 | } 60 | this.lastWidth = chrome.app.window.current().outerBounds.width; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /js/Editor/StyleManager.js: -------------------------------------------------------------------------------- 1 | function StyleManager() { 2 | /* Outlets */ 3 | this.styleButton = $("#style-tool"); 4 | this.styleDisplayer = $("#style-tool-displayer"); 5 | this.homeRadio = $("#home-style"); 6 | this.clinicRadio = $("#clinic-style"); 7 | this.tramwayRadio = $("#tramway-style"); 8 | 9 | /* Events */ 10 | $(document).click($.proxy(function(e) { 11 | if ($(e.target).closest(this.styleButton).length && this.styleDisplayer.hasClass("hidden")) { 12 | this.styleDisplayer.removeClass("hidden"); 13 | } else if (!this.styleDisplayer.hasClass("hidden") && ! $(e.target).closest(this.styleDisplayer).length) { 14 | this.styleDisplayer.addClass("hidden"); 15 | } 16 | }, this)); 17 | 18 | this.homeRadio.add(this.clinicRadio).add(this.tramwayRadio).on("click", $.proxy(function(e) { 19 | this.setStyle(e.target.id.slice(0, -6)); // Remove the part of the id with "-style". 20 | }, this)); 21 | 22 | chrome.storage.onChanged.addListener($.proxy(function(changes, namespace) { 23 | for (var key in changes) { 24 | switch (key) { 25 | case "style": 26 | this.getStyle(); // Get all the recent files again in chrome.storage.local and display them. 27 | break; 28 | } 29 | } 30 | }, this)); 31 | 32 | /* Initialization */ 33 | this.getStyle(); 34 | } 35 | 36 | StyleManager.prototype = { 37 | constructor: StyleManager, 38 | 39 | /* Gets the style in chrome.storage.local and applies it. */ 40 | getStyle: function() { 41 | chrome.storage.local.get("style", $.proxy(function(mado) { 42 | if (mado.style) { 43 | switch (mado.style) { 44 | case "home": 45 | this.homeRadio[0].checked = true; 46 | break; 47 | case "clinic": 48 | this.clinicRadio[0].checked = true; 49 | break; 50 | case "tramway": 51 | this.tramwayRadio[0].checked = true; 52 | } 53 | this.setStyle(mado.style); 54 | } else { // Not settled yet, we choose Home. 55 | this.homeRadio[0].checked = true; 56 | this.setStyle("home"); 57 | } 58 | }, this)); 59 | }, 60 | 61 | setStyle: function(newStyle) { 62 | for (var i = 0; i < document.styleSheets.length; i++) { 63 | if (document.styleSheets.item(i).href.indexOf("css/themes/") != -1) { 64 | if (document.styleSheets.item(i).href.indexOf(newStyle) == -1) { 65 | document.styleSheets.item(i).disabled = true; 66 | } else { 67 | document.styleSheets.item(i).disabled = false; 68 | } 69 | } 70 | } 71 | chrome.storage.local.set({ "style" : newStyle }); 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /js/Topbar/DnDManager.js: -------------------------------------------------------------------------------- 1 | function DnDManager(app, selector) { 2 | /* Outlets */ 3 | this.documentSection = $("#document"); // The section named "document" in the HTML. 4 | 5 | /* Variables */ 6 | this.app = app; 7 | this.dragMessageAlreadyVisible = false; // True if the message about Drag and Drop is already visible. 8 | this.selector = document.querySelector(selector); // The fiel handled by the Drag and Drop manager. 9 | this.extensionsAllowed = [".markdown", ".md", ".txt"]; // Extensions allowed by Mado. 10 | this.filePath = undefined; // The path of the dragged file. 11 | this.overCount = 0; // Use to only display once the animation of drag. 12 | 13 | /* Events */ 14 | this.selector.addEventListener("dragenter", $.proxy(function(e) { this.dragenter(e); }, this)); 15 | this.selector.addEventListener("dragover", $.proxy(function(e) { this.dragover(e); }, this)); 16 | this.selector.addEventListener("dragleave", $.proxy(function(e) { this.dragleave(e); }, this)); 17 | this.selector.addEventListener("drop", $.proxy(function(e) { this.drop(e); }, this)); 18 | } 19 | 20 | DnDManager.prototype = { 21 | constructor: DnDManager, 22 | 23 | /* What to do when a document start to be dragged in the selector. */ 24 | dragenter: function(e) { 25 | e.stopPropagation(); 26 | e.preventDefault(); 27 | this.overCount++; 28 | this.selector.classList.add('dropping'); 29 | }, 30 | 31 | /* What to do when a document is over the selector. */ 32 | dragover: function(e) { 33 | if (! this.dragMessageAlreadyVisible) { 34 | this.documentSection.attr("class", "dragging"); 35 | this.dragMessageAlreadyVisible = true; 36 | } 37 | e.stopPropagation(); 38 | e.preventDefault(); 39 | }, 40 | 41 | /* What to do when a document leave the selector. */ 42 | dragleave: function(e) { 43 | this.documentSection.attr("class", ""); 44 | this.dragMessageAlreadyVisible = false; 45 | e.stopPropagation(); 46 | e.preventDefault(); 47 | if (--this.overCount <= 0) { 48 | this.selector.classList.remove('dropping'); 49 | this.overCount = 0; 50 | } 51 | }, 52 | 53 | /* What to do when a document is droped on the selector. */ 54 | drop: function(e) { 55 | this.documentSection.attr("class", ""); 56 | this.dragMessageAlreadyVisible = false; 57 | e.stopPropagation(); 58 | e.preventDefault(); 59 | 60 | this.selector.classList.remove('dropping'); 61 | 62 | var filePath = e.dataTransfer.items[0].webkitGetAsEntry().fullPath; 63 | if (this.extensionsAllowed.indexOf(filePath.substring(filePath.lastIndexOf("."), filePath.length)) != -1) { // It is a file that Mado can handled. 64 | this.app.openFile(e.dataTransfer.items[0].webkitGetAsEntry()); // We open the file dropped. 65 | } 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /js/Topbar/SaveFileManager.js: -------------------------------------------------------------------------------- 1 | function SaveFileManager(app) { 2 | /* Outlets */ 3 | this.saveButton = $("#save"); 4 | 5 | /* Variables */ 6 | this.app = app; 7 | this.fileToSave = undefined; // The file saved. 8 | 9 | /* Events */ 10 | this.saveButton.on("click", $.proxy(function () { this.apply(); }, this)); 11 | Mousetrap.bind(["command+s", "ctrl+s"], $.proxy(function(e) { this.apply(); return false; }, this)); // Ctrl+n = new window. 12 | 13 | /* Initialization */ 14 | this.init(); 15 | } 16 | 17 | SaveFileManager.prototype = { 18 | constructor: SaveFileManager, 19 | 20 | /* Save the document. */ 21 | apply: function(callback) { 22 | var t = this; 23 | if (this.fileToSave) { // The document already exists, we can directly saved. 24 | this.fileToSave.createWriter(function(fileWriter) { 25 | var truncated = false; // Variable used to do a good save even if the new file is shorter than the old one. 26 | fileWriter.onwriteend = function(e) { 27 | if (!truncated) { 28 | truncated = true; 29 | this.truncate(this.position); 30 | return; 31 | } 32 | chrome.storage.local.set({ "newFile": chrome.fileSystem.retainEntry(t.fileToSave), "newFilePath": t.fileToSave.fullPath }, function() { // For RecentFilesManager. 33 | t.app.setDocumentSaved(); // The editor know that the document is saved. 34 | if (callback) { 35 | callback(); 36 | } 37 | }); 38 | 39 | }; 40 | fileWriter.write(new Blob([t.app.getEditorText()], {type: 'plain/text'})); 41 | }, function(error) { console.log(error); }); 42 | } else { 43 | this.app.saveAs(); 44 | } 45 | }, 46 | 47 | /* At initialization we directly have the fileToSave if a file has been loaded. */ 48 | init: function() { 49 | var t = this; 50 | chrome.storage.local.get("appInitFileEntry", function(mado) { 51 | if (mado.appInitFileEntry) { 52 | chrome.fileSystem.restoreEntry( 53 | mado.appInitFileEntry, 54 | function (entry) { 55 | t.fileToSave = entry; // Set fileToSave to allow direct saves. 56 | chrome.storage.local.remove("appInitFileEntry"); 57 | } 58 | ); 59 | } 60 | }); 61 | }, 62 | 63 | /* Returns if we have fileToSave. */ 64 | isSaved: function() { 65 | if (this.fileToSave) { 66 | return true; 67 | } else { 68 | return false; 69 | } 70 | }, 71 | 72 | /* Sets the file to save with an entry. */ 73 | setFileToSave: function(fileEntry) { 74 | this.fileToSave = fileEntry; 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /css/themes/tramway.css: -------------------------------------------------------------------------------- 1 | /* Newspaper style for the conversion view. Its name, "Tramway", reminds of the time spent reading a journal in the public transport. */ 2 | 3 | /* ------- 4 | Fonts 5 | ------- */ 6 | #html-conversion { 7 | /* Typography */ 8 | font-family: Arvo, serif; 9 | font-size: 1em; 10 | line-height: 1.35em; 11 | } 12 | 13 | /* ------------ 14 | Paragraphs 15 | ------------ */ 16 | #html-conversion p, 17 | #html-conversion ol, /* Organized and unorganized lists follow the same rule : a blank space at the bottom */ 18 | #html-conversion ul { 19 | /* Box-model */ 20 | padding-bottom: 0.65em; 21 | } 22 | 23 | /* -------- 24 | Titles 25 | -------- */ 26 | #html-conversion h1, 27 | #html-conversion h2, 28 | #html-conversion h3 { 29 | /* Typography */ 30 | font-family: Arvo; 31 | } 32 | 33 | #html-conversion h1 { 34 | /* Box-model */ 35 | padding: 0.35em 0; 36 | 37 | /* Typography */ 38 | font-size: 3em; 39 | } 40 | 41 | #html-conversion h2 { 42 | /* Box-model */ 43 | padding: 0.3em 0 0.65em; 44 | 45 | /* Typography */ 46 | font-size: 2em; 47 | } 48 | 49 | #html-conversion h3 { 50 | /* Box-model */ 51 | padding: 0.5em 0; 52 | 53 | /* Typography */ 54 | font-size: 1.25em; 55 | } 56 | 57 | #html-conversion h4 { 58 | /* Box-model */ 59 | padding: 0.65em 0; 60 | 61 | /* Typography */ 62 | font-weight: bold; 63 | } 64 | 65 | #html-conversion h5 { 66 | /* Box-model */ 67 | padding-bottom: 0.8em; 68 | 69 | /* Typography */ 70 | font-size: 0.8em; 71 | font-weight: bold; 72 | } 73 | 74 | #html-conversion h6 { 75 | /* Typography */ 76 | font-size: 0.75em; 77 | font-weight: bold; 78 | } 79 | 80 | /* ------- 81 | Lists 82 | ------- */ 83 | /* UNORDERED LISTS */ 84 | #html-conversion ul > li { padding-left: 2em; } 85 | 86 | #html-conversion ul > li:before { width: 2em; } 87 | 88 | /* ORDERED LISTS */ 89 | #html-conversion ol > li { padding-left: 2em; } 90 | 91 | #html-conversion ol > li:before { 92 | /* Box-model */ 93 | width: 2em; 94 | 95 | /* Misc */ 96 | content: counter(list)"."; 97 | } 98 | 99 | /* SUB-LISTS */ 100 | #html-conversion ol ol, 101 | #html-conversion ol ul, 102 | #html-conversion ul ul, 103 | #html-conversion ul ol { 104 | /* Box-model */ 105 | padding-bottom: 0; 106 | } 107 | 108 | /* ------------- 109 | Blockquotes 110 | ------------- */ 111 | #html-conversion blockquote { 112 | /* Box-model */ 113 | padding: 0 1.35em; 114 | 115 | /* Typography */ 116 | font-size: 1.05em; 117 | text-align: center; 118 | } 119 | 120 | /* -------- 121 | Tables 122 | -------- */ 123 | #html-conversion td, 124 | #html-conversion th { border: 1px solid #4a4942; } 125 | 126 | /* ------------------ 127 | Horizontal rules 128 | ------------------ */ 129 | #html-conversion hr { 130 | margin: 0.65em auto 1.35em; /* No matter the size of the rule, it will be centered */ 131 | 132 | /* Box-model */ 133 | width: calc(100% - 4em); 134 | 135 | /* Visual */ 136 | background-color: #4a4942; 137 | } -------------------------------------------------------------------------------- /css/style-base.css: -------------------------------------------------------------------------------- 1 | /* ============== 2 | MISCELLANOUS 3 | ============== */ 4 | * { 5 | /* Box-model */ 6 | box-sizing: border-box; /* Simplifies width and height calculation */ 7 | 8 | /* Misc */ 9 | outline: none; /* Disables default outlining style (on Google Chrome, a golden border light) */ 10 | } 11 | 12 | html, 13 | body { 14 | /* Box-model */ 15 | width: 100%; 16 | height: 100%; 17 | } 18 | 19 | body { 20 | /* Box-model */ 21 | overflow: hidden; 22 | 23 | /* Typography */ 24 | font-family: "Segoe UI", "Helvetica Neue", Roboto, "Open Sans", sans-serif; /* System fonts for Windows, OS X and Android/Chrome OS */ 25 | font-size: 1em; 26 | } 27 | 28 | /* The call to action (CTA) class forces the cursor to be a pointer */ 29 | .cta { cursor: pointer; } 30 | 31 | /* The button class renders a generic clickable button */ 32 | .button { 33 | /* Box-model */ 34 | min-width: 40px; 35 | float: left; 36 | padding: 7px 10px; 37 | 38 | /* Typography */ 39 | color: #fff; 40 | font-size: 13px; 41 | line-height: 16px; 42 | font-weight: bold; 43 | text-align: center; 44 | 45 | /* Visual */ 46 | background-color: #1c75ea; 47 | border-bottom: 2px solid #104387; 48 | border-radius: 3px; 49 | } 50 | 51 | .button:not(:first-child) { margin-top: 5px; } 52 | 53 | .button:hover { 54 | /* Visual */ 55 | background-color: #187af9; 56 | 57 | /* Misc */ 58 | transition: background-color .1s ease; 59 | } 60 | 61 | .button:active { 62 | /* Visual */ 63 | background-color: #156ad8; 64 | border-bottom: none; 65 | border-top: 2px solid #104387; 66 | 67 | /* Misc */ 68 | transition: none; 69 | } 70 | 71 | .button-grey { 72 | /* Typography */ 73 | color: #4a4942; 74 | 75 | /* Visual */ 76 | background-color: #e9e9e9; 77 | border-bottom: 2px solid #d0d0d0; 78 | } 79 | 80 | .button-grey:hover { background-color: #f0f0f0; } 81 | 82 | .button-grey:active { 83 | /* Visual */ 84 | background-color: #e9e9e9; 85 | border-bottom: none; 86 | border-top: 2px solid #d0d0d0; 87 | } 88 | 89 | /* ------------------- 90 | Basic HTML styles 91 | ------------------- */ 92 | a { 93 | /* Typography */ 94 | color: #1c75ea; 95 | text-decoration: none; 96 | } 97 | 98 | a:hover { text-decoration: underline; } 99 | 100 | a:active { color: #187af9; } 101 | 102 | em { font-style: italic; } 103 | 104 | strong { font-weight: bold; } 105 | 106 | sup { 107 | /* Typography */ 108 | font-size: 0.8; 109 | line-height: 0; 110 | vertical-align: super; 111 | } 112 | 113 | input[type="text"] { 114 | /* Positioning */ 115 | margin-top: 5px; 116 | 117 | /* Box-model */ 118 | width: 300px; 119 | height: 30px; 120 | padding: 0 5px; 121 | 122 | /* Visual */ 123 | border: 1px solid #f0f0f0; 124 | 125 | /* Misc */ 126 | transition: border-color 0.2s ease; 127 | } 128 | 129 | input[type="text"]:focus { border-color: #e0e0e0; } 130 | 131 | input[type="text"].flash { -webkit-animation: flash 0.8s; } 132 | 133 | @-webkit-keyframes flash { 134 | 0%, 25% { border-color: #3ea2f9; } 135 | 100% { 136 | /* Visual */ 137 | border-color: #e0e0e0; 138 | 139 | /* Misc */ 140 | -webkit-animation-timing-function: ease-out; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /css/window-frame-windows.css: -------------------------------------------------------------------------------- 1 | /* =========================== 2 | WINDOWS WINDOW BAR STYLES 3 | =========================== 4 | Specific styles for Mado's window bar on Windows. 5 | It also sets up a border all around the app window. */ 6 | 7 | #window-bar ul { height: 100%; } /* Enable percentage-based height for the list items */ 8 | 9 | #window-bar li { 10 | /* Positioning */ 11 | float: right; 12 | 13 | /* Box-model */ 14 | width: 40px; 15 | height: 100%; 16 | 17 | /* Typography */ 18 | color: #fff; 19 | line-height: 18px; 20 | text-align: center; 21 | 22 | /* Visual */ 23 | background: transparent; 24 | 25 | /* Misc */ 26 | transition: background-color 0.1s ease; 27 | -webkit-app-region: no-drag; /* Disable drag action */ 28 | } 29 | 30 | #window-bar li:active { transition: none; } 31 | 32 | /* Borders for the Windows frame */ 33 | #window-maximize, 34 | #window-minimize, 35 | #window-close-button { 36 | /* Visual */ 37 | border-color: transparent; 38 | border-style: solid; 39 | } 40 | 41 | #window-maximize, 42 | #window-minimize { 43 | /* Visual */ 44 | border-width: 1px 0 0; 45 | } 46 | 47 | #window-close-button { 48 | /* Box-model */ 49 | padding-left: 1px; /* Counterbalance the right border */ 50 | 51 | /* Visual */ 52 | border-width: 1px 1px 0 0; 53 | } 54 | 55 | #window-close-button:hover, 56 | #window-close-button:active { border-color: #991515; } 57 | 58 | #window-maximize:hover, 59 | #window-maximize:active, 60 | #window-minimize:hover, 61 | #window-minimize:active { border-color: #103365; } 62 | 63 | /* Background colors for the buttons */ 64 | #window-close-button:hover { 65 | /* Visual */ 66 | background-color: #d11; 67 | } 68 | 69 | #window-close-button:active { background-color: #c01a1a; } 70 | 71 | #window-maximize:hover, 72 | #window-minimize:hover { background-color: #143d7a; } 73 | 74 | #window-maximize:active, 75 | #window-minimize:active { background-color: #103365; } 76 | 77 | /* ------- 78 | Alert 79 | ------- */ 80 | /* When the user clicks the "Close" button with unsaved changes, the displayer is deployed and shows the alert */ 81 | #close-alert-displayer { 82 | /* Positioning */ 83 | top: 20px; 84 | right: 5px; 85 | 86 | /* Misc */ 87 | -webkit-transform-origin: 434px 1px; 88 | } 89 | 90 | /* The arrow is a gray corner for the container */ 91 | #close-alert-arrow { margin: 1px 0 0 431px; } 92 | 93 | /* -------- 94 | Header 95 | -------- */ 96 | #mado-header { 97 | /* Visual */ 98 | border-color: #103365; 99 | border-style: solid; 100 | border-width: 1px 1px 0; 101 | } 102 | 103 | /* NAVBAR */ 104 | #new, 105 | #more-button { 106 | /* Visual */ 107 | border-color: #103365; 108 | border-style: solid; 109 | } 110 | 111 | #new { border-width: 0 0 0 1px; } 112 | 113 | #more-button { 114 | /* Box-model */ 115 | padding-left: 1px; /* Counterbalance the right border */ 116 | 117 | /* Visual */ 118 | border-width: 0 1px 0 0; 119 | } 120 | 121 | /* ----------- 122 | Workspace 123 | ----------- */ 124 | #document { 125 | /* Visual */ 126 | border-color: #4a4942; 127 | border-style: solid; 128 | border-width: 0 1px; 129 | } 130 | 131 | /* -------- 132 | Footer 133 | -------- */ 134 | #mado-footer { 135 | /* Visual */ 136 | border-color: #21201d; 137 | border-style: solid; 138 | border-width: 0 1px 1px; 139 | } -------------------------------------------------------------------------------- /css/footer.css: -------------------------------------------------------------------------------- 1 | /* ============ 2 | BOTTOM BAR 3 | ============ */ 4 | 5 | #mado-footer { 6 | /* Positioning */ 7 | position: fixed; 8 | bottom: 0; 9 | left: 0; 10 | 11 | /* Box-model */ 12 | width: 100%; 13 | height: 20px; 14 | padding: 0 40px 0 10px; 15 | 16 | /* Typography */ 17 | font-size: 0.8em; 18 | line-height: 20px; 19 | color: #fafafa; 20 | 21 | /* Visual */ 22 | background-color: #4a4942; 23 | 24 | /* Misc */ 25 | cursor: default; 26 | transition: padding .25s ease; 27 | } 28 | 29 | #mado-footer.markdown-view { padding: 0 0 0 10px; } 30 | 31 | #mado-footer p { text-align: right; } 32 | 33 | /* LINK URL 34 | Shows the URL of the link element that is currently hovered. */ 35 | #link-url { 36 | /* Typograhpy */ 37 | color: #b3b3b0; 38 | 39 | /* Visual */ 40 | opacity: 0; 41 | 42 | /* Misc */ 43 | transition: opacity .1s ease; 44 | } 45 | 46 | #link-url.show { opacity: 1; } 47 | 48 | /* NUMBER OF CHARS AND OF WORDS */ 49 | #character-nb, 50 | #word-nb { 51 | /* Positioning */ 52 | display: inline; 53 | 54 | /* Visual */ 55 | border-radius: 2px; 56 | 57 | /* Misc */ 58 | cursor: pointer; 59 | } 60 | 61 | #character-nb:hover, 62 | #word-nb:hover { 63 | /* Typography */ 64 | color: #fff; 65 | 66 | /* Visual */ 67 | background-color: #5f5e52; 68 | 69 | } 70 | 71 | #character-nb:active, 72 | #word-nb:active { 73 | /* Visual */ 74 | background-color: #424139; 75 | } 76 | 77 | /* STYLES TOOL SPECIFICS */ 78 | #mado-footer .tool-container { 79 | /* Positioning */ 80 | position: absolute; 81 | top: 0; 82 | right: 0; 83 | 84 | /* Misc */ 85 | transition: right .25s ease; 86 | } 87 | 88 | #mado-footer.markdown-view .tool-container { right: -40px; } 89 | 90 | #mado-footer .tool { 91 | /* Box-model */ 92 | width: 40px; 93 | height: 20px; 94 | 95 | /* Typography */ 96 | color: #e9e9e9; 97 | line-height: 20px; 98 | } 99 | 100 | #mado-footer .tool:hover { color: #fff; } 101 | 102 | /* The displayer is actually a container that fades out when combined with the "hidden" class */ 103 | #mado-footer .tool-displayer { 104 | /* Positioning */ 105 | right: 6px; 106 | bottom: 22px; 107 | 108 | /* Box-model */ 109 | width: 120px; 110 | 111 | /* Misc */ 112 | -webkit-transform-origin: 105px 96px; 113 | } 114 | 115 | /* The arrow is a gray corner for the "box" (see #mado-footer .tool-box below) */ 116 | #mado-footer .tool-arrow { 117 | /* Positioning */ 118 | margin: 0 0 0 101px; 119 | /* Visual */ 120 | border-color: #fff transparent transparent transparent; 121 | border-width: 4px 4px 0 4px; 122 | } 123 | 124 | /* The "box" is a gray container for the sidebar tool; it comes with an arrow (see #mado-footer .tool-arrow above) */ 125 | #mado-footer .tool-box { width: 120px; } 126 | 127 | .style-choice-container { 128 | /* Box-model */ 129 | display: block; 130 | width: 100%; 131 | height: 27px; 132 | 133 | /* Typography */ 134 | font-size: 1em; 135 | line-height: 27px; 136 | font-weight: bold; 137 | color: #4a4942; 138 | } 139 | 140 | .style-choice-container label:hover, 141 | .style-choice-container input[type="radio"]:hover + label { 142 | color: #000; 143 | } 144 | 145 | .style-choice-container input[type="radio"]:checked + label:hover, 146 | .style-choice-container input[type="radio"]:checked:hover + label { 147 | color: #4a4942; 148 | } -------------------------------------------------------------------------------- /css/themes/clinic.css: -------------------------------------------------------------------------------- 1 | /* Minimalistic and futuristic style for the conversion view. Its name, "Clinic", reminds of the purity of a hospital. */ 2 | 3 | /* ------- 4 | Fonts 5 | ------- */ 6 | #html-conversion { 7 | /* Typography */ 8 | font-family: Bariol, sans-serif; 9 | font-size: 1.1em; 10 | line-height: 1.5em; 11 | /*-webkit-font-smoothing: antialiased;*/ 12 | } 13 | 14 | /* ------------ 15 | Paragraphs 16 | ------------ */ 17 | #html-conversion p, 18 | #html-conversion ol, /* Organized and unorganized lists follow the same rule : a blank space at the bottom */ 19 | #html-conversion ul { 20 | /* Box-model */ 21 | padding-bottom: 0.7em; 22 | } 23 | 24 | /* -------- 25 | Titles 26 | -------- */ 27 | #html-conversion h1, 28 | #html-conversion h2 { 29 | /* Typography */ 30 | font-family: BariolLight; 31 | } 32 | 33 | #html-conversion h1 { 34 | /* Box-model */ 35 | padding: 0.6em 0 0.3em; 36 | 37 | /* Typography */ 38 | font-size: 3em; 39 | } 40 | 41 | #html-conversion h2 { 42 | /* Box-model */ 43 | padding: 0.7em 0 0.5em; 44 | 45 | /* Typography */ 46 | font-size: 2em; 47 | } 48 | 49 | #html-conversion h3 { 50 | /* Box-model */ 51 | padding: 0.75em 0; 52 | 53 | /* Typography */ 54 | font-family: Bariol; 55 | font-size: 1.25em; 56 | } 57 | 58 | #html-conversion h4 { 59 | /* Box-model */ 60 | padding: 0.7em 0; 61 | 62 | /* Typography */ 63 | font-weight: bold; 64 | } 65 | 66 | #html-conversion h5 { 67 | /* Box-model */ 68 | padding-bottom: 0.85em; 69 | 70 | /* Typography */ 71 | font-size: 0.85em; 72 | font-weight: bold; 73 | } 74 | 75 | #html-conversion h6 { 76 | /* Typography */ 77 | font-size: 0.8em; 78 | font-weight: bold; 79 | } 80 | 81 | /* ------- 82 | Lists 83 | ------- */ 84 | /* UNORDERED LISTS */ 85 | #html-conversion ul > li { padding-left: 2em; } 86 | 87 | #html-conversion ul > li:before { padding-left: 0; } 88 | 89 | /* ORDERED LISTS */ 90 | #html-conversion ol > li { padding-left: 2em; } 91 | 92 | #html-conversion ol > li:before { 93 | /* Box-model */ 94 | padding-left: 0; 95 | 96 | /* Typography */ 97 | font-weight: bold; 98 | text-align: left; 99 | 100 | /* Misc */ 101 | content: counter(list); 102 | } 103 | 104 | /* Prevent empty list items to be displayed without a height */ 105 | #html-conversion li:empty { height: 1.5em; } 106 | 107 | /* SUB-LISTS */ 108 | #html-conversion ol ol, 109 | #html-conversion ol ul, 110 | #html-conversion ul ul, 111 | #html-conversion ul ol { 112 | /* Box-model */ 113 | padding-bottom: 0; 114 | } 115 | 116 | /* ------------- 117 | Blockquotes 118 | ------------- */ 119 | #html-conversion blockquote { 120 | /* Positioning */ 121 | margin-bottom: 0.7em; 122 | 123 | /* Box-model */ 124 | padding: 0.7em 0 0 1em; 125 | 126 | /* Visual */ 127 | background-color: rgba(0,0,0,.05); 128 | border-left: none; 129 | } 130 | 131 | #html-conversion blockquote em { font-style: normal; } 132 | 133 | /* -------- 134 | Tables 135 | -------- */ 136 | #html-conversion td, 137 | #html-conversion th { 138 | /* Box-model */ 139 | padding: 0.6em; 140 | 141 | /* Visual */ 142 | border: 1px solid #dfdfdf; 143 | } 144 | 145 | /* Zebra stripes for the table's body */ 146 | #html-conversion tbody tr:nth-child(2n+1) { background-color: #f3f3f3; } 147 | 148 | /* ------------------ 149 | Horizontal rules 150 | ------------------ */ 151 | #html-conversion hr { margin: 0.7em auto 1.5em; } 152 | 153 | /* ------ 154 | Code 155 | ------ */ 156 | #html-conversion code { 157 | /* Typography */ 158 | font-size: 0.85em; 159 | line-height: 1.45em; 160 | } -------------------------------------------------------------------------------- /js/Topbar/App.js: -------------------------------------------------------------------------------- 1 | function App(editor) { 2 | /* Variables */ 3 | this.editor = editor; 4 | this.lastBounds = chrome.app.window.current().outerBounds; 5 | 6 | this.dragAndDropManager = new DnDManager(this, "body"); 7 | this.exportManager = new ExportManager(this); 8 | this.labelManager = new LabelManager(); 9 | this.moreWindowsManager = new MoreWindowsManager(); 10 | this.newFileManager = new NewFileManager(this); 11 | this.openFileManager = new OpenFileManager(this); 12 | this.printManager = new PrintManager(); 13 | this.recentFilesManager = new RecentFilesManager(this); 14 | this.saveFileManager = new SaveFileManager(this); 15 | this.saveFileAsManager = new SaveFileAsManager(this); 16 | this.switchManager = new SwitchManager(); 17 | } 18 | 19 | App.prototype = { 20 | constructor: App, 21 | 22 | /* Focus in the editor. */ 23 | focusOnEditor: function() { 24 | this.editor.focus(); 25 | }, 26 | 27 | /* Return editor's text as a String. */ 28 | getEditorText: function() { 29 | return this.editor.getMarkdown(); 30 | }, 31 | 32 | /* Return the name of the document edited. */ 33 | getName: function() { 34 | return this.editor.getName(); 35 | }, 36 | 37 | /* Return if the document has a name or not. */ 38 | isDocumentNamed: function() { 39 | return this.editor.isNamed(); 40 | }, 41 | 42 | /* Return if the document is saved or not for the editor (just the icon on top of the window, not a real entry). */ 43 | isDocumentSaved: function() { 44 | return this.editor.isSaved(); 45 | }, 46 | 47 | /* Return if the document is the document is available as an entry (really saved). */ 48 | isEntrySaved: function() { 49 | return this.saveFileManager.isSaved(); 50 | }, 51 | 52 | /* Apply the file manager. */ 53 | newFile: function() { 54 | this.newFileManager.apply(); 55 | }, 56 | 57 | /* Open a file. 58 | * file: entry to open. 59 | */ 60 | openFile: function(file) { 61 | this.openFileManager.open(file); 62 | }, 63 | 64 | /* Empty the editor to start fresh. */ 65 | resetEditor: function() { 66 | this.editor.setMarkdown("", 0, this.editor.getMarkdown().length); 67 | }, 68 | 69 | /* Set that the document is saved for the editor. */ 70 | setDocumentSaved: function() { 71 | this.editor.save(); 72 | }, 73 | 74 | /* Really save the document. 75 | * Callback: what to do after the save. 76 | */ 77 | save: function(callback) { 78 | this.saveFileManager.apply(callback); 79 | }, 80 | 81 | /* Save the document has a new one. 82 | * Callback: what to do after the save. 83 | */ 84 | saveAs: function(callback) { 85 | this.saveFileAsManager.apply(callback); 86 | }, 87 | 88 | /* Set the entry of the window. Set the document as saved with the name of the entry. 89 | * entry: the entry used to save the editor. 90 | */ 91 | setEditorEntry: function(entry) { 92 | this.saveFileManager.setFileToSave(entry); 93 | this.editor.saveWithName(entry.fullPath.substring(entry.fullPath.lastIndexOf('/') + 1)); 94 | }, 95 | 96 | /* Set the entry + set the text. 97 | * entry: the entry used to save the editor. 98 | * content: content of the entry. 99 | */ 100 | setEditorWithEntry: function(entry, content) { 101 | this.saveFileManager.setFileToSave(entry); 102 | this.editor.setMarkdown(content, 0, this.editor.getMarkdown().length); 103 | this.editor.saveWithName(entry.fullPath.substring(entry.fullPath.lastIndexOf('/') + 1)); 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /js/lib/rangyinputs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Rangy Inputs, a jQuery plug-in for selection and caret manipulation within textareas and text inputs. 3 | * 4 | * https://github.com/timdown/rangyinputs 5 | * 6 | * For range and selection features for contenteditable, see Rangy. 7 | 8 | * http://code.google.com/p/rangy/ 9 | * 10 | * Depends on jQuery 1.0 or later. 11 | * 12 | * Copyright 2014, Tim Down 13 | * Licensed under the MIT license. 14 | * Version: 1.2.0 15 | * Build date: 30 November 2014 16 | */ 17 | !function(e){function t(e,t){var n=typeof e[t];return"function"===n||!("object"!=n||!e[t])||"unknown"==n}function n(e,t){return typeof e[t]!=x}function r(e,t){return!("object"!=typeof e[t]||!e[t])}function o(e){window.console&&window.console.log&&window.console.log("RangyInputs not supported in your browser. Reason: "+e)}function a(e,t,n){return 0>t&&(t+=e.value.length),typeof n==x&&(n=t),0>n&&(n+=e.value.length),{start:t,end:n}}function c(e,t,n){return{start:t,end:n,length:n-t,text:e.value.slice(t,n)}}function l(){return r(document,"body")?document.body:document.getElementsByTagName("body")[0]}var i,u,s,d,f,v,p,m,g,x="undefined";e(document).ready(function(){function h(e,t){var n=e.value,r=i(e),o=r.start;return{value:n.slice(0,o)+t+n.slice(r.end),index:o,replaced:r.text}}function y(e,t){e.focus();var n=i(e);return u(e,n.start,n.end),""==t?document.execCommand("delete",!1,null):document.execCommand("insertText",!1,t),{replaced:n.text,index:n.start}}function T(e,t){e.focus();var n=h(e,t);return e.value=n.value,n}function E(e,t){return function(){var n=this.jquery?this[0]:this,r=n.nodeName.toLowerCase();if(1==n.nodeType&&("textarea"==r||"input"==r&&/^(?:text|email|number|search|tel|url|password)$/i.test(n.type))){var o=[n].concat(Array.prototype.slice.call(arguments)),a=e.apply(this,o);if(!t)return a}return t?this:void 0}}var S=document.createElement("textarea");if(l().appendChild(S),n(S,"selectionStart")&&n(S,"selectionEnd"))i=function(e){var t=e.selectionStart,n=e.selectionEnd;return c(e,t,n)},u=function(e,t,n){var r=a(e,t,n);e.selectionStart=r.start,e.selectionEnd=r.end},g=function(e,t){t?e.selectionEnd=e.selectionStart:e.selectionStart=e.selectionEnd};else{if(!(t(S,"createTextRange")&&r(document,"selection")&&t(document.selection,"createRange")))return l().removeChild(S),void o("No means of finding text input caret position");i=function(e){var t,n,r,o,a=0,l=0,i=document.selection.createRange();return i&&i.parentElement()==e&&(r=e.value.length,t=e.value.replace(/\r\n/g,"\n"),n=e.createTextRange(),n.moveToBookmark(i.getBookmark()),o=e.createTextRange(),o.collapse(!1),n.compareEndPoints("StartToEnd",o)>-1?a=l=r:(a=-n.moveStart("character",-r),a+=t.slice(0,a).split("\n").length-1,n.compareEndPoints("EndToEnd",o)>-1?l=r:(l=-n.moveEnd("character",-r),l+=t.slice(0,l).split("\n").length-1))),c(e,a,l)};var w=function(e,t){return t-(e.value.slice(0,t).split("\r\n").length-1)};u=function(e,t,n){var r=a(e,t,n),o=e.createTextRange(),c=w(e,r.start);o.collapse(!0),r.start==r.end?o.move("character",c):(o.moveEnd("character",w(e,r.end)),o.moveStart("character",c)),o.select()},g=function(e,t){var n=document.selection.createRange();n.collapse(t),n.select()}}l().removeChild(S);var b=function(e,t){var n=h(e,t);try{var r=y(e,t);if(e.value==n.value)return b=y,r}catch(o){}return b=T,e.value=n.value,n};d=function(e,t,n,r){t!=n&&(u(e,t,n),b(e,"")),r&&u(e,t)},s=function(e){u(e,b(e,"").index)},m=function(e){var t=b(e,"");return u(e,t.index),t.replaced};var R=function(e,t,n,r){var o=t+n.length;if(r="string"==typeof r?r.toLowerCase():"",("collapsetoend"==r||"select"==r)&&/[\r\n]/.test(n)){var a=n.replace(/\r\n/g,"\n").replace(/\r/g,"\n");o=t+a.length;var c=t+a.indexOf("\n");"\r\n"==e.value.slice(c,c+2)&&(o+=a.match(/\n/g).length)}switch(r){case"collapsetostart":u(e,t,t);break;case"collapsetoend":u(e,o,o);break;case"select":u(e,t,o)}};f=function(e,t,n,r){u(e,n),b(e,t),"boolean"==typeof r&&(r=r?"collapseToEnd":""),R(e,n,t,r)},v=function(e,t,n){var r=b(e,t);R(e,r.index,t,n||"collapseToEnd")},p=function(e,t,n,r){typeof n==x&&(n=t);var o=i(e),a=b(e,t+o.text+n);R(e,a.index+t.length,o.text,r||"select")},e.fn.extend({getSelection:E(i,!1),setSelection:E(u,!0),collapseSelection:E(g,!0),deleteSelectedText:E(s,!0),deleteText:E(d,!0),extractSelectedText:E(m,!1),insertText:E(f,!0),replaceSelectedText:E(v,!0),surroundSelectedText:E(p,!0)})})}(jQuery); 18 | -------------------------------------------------------------------------------- /css/icons.css: -------------------------------------------------------------------------------- 1 | /* Generated via IcoMoon (http://icomoon.io/app/). */ 2 | 3 | 4 | /* =========== 5 | BIG ICONS 6 | =========== */ 7 | 8 | @font-face { 9 | font-family: 'Mado-Icons-Normal'; 10 | src: url('../fonts/Mado-Icons-Normal.ttf') format('truetype'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | 15 | /* Use the following CSS code (one class per icon) */ 16 | .icon-new, 17 | .icon-open, 18 | .icon-recent, 19 | .icon-recent-clear, 20 | .icon-save, 21 | .icon-save-as, 22 | .icon-export, 23 | .icon-print, 24 | .icon-more, 25 | .icon-help, 26 | .icon-link, 27 | .icon-webimage, 28 | .icon-image, 29 | .icon-style { 30 | /* Typography */ 31 | font-family: 'Mado-Icons-Normal'; 32 | speak: none; 33 | font-size: 32px; /* Crisp size */ 34 | font-style: normal; 35 | font-weight: normal; 36 | font-variant: normal; 37 | text-transform: none; 38 | -webkit-font-smoothing: antialiased; 39 | } 40 | 41 | /* -------------- 42 | Navbar icons 43 | -------------- */ 44 | .icon-new:before { content: "\e00a"; } 45 | 46 | .icon-open:before { content: "\e001"; } 47 | 48 | .icon-recent:before { content: "\e002"; } 49 | 50 | .icon-recent-clear:before { content: "\e003"; } 51 | 52 | .icon-save:before { content: "\e004"; } 53 | 54 | .icon-save-as:before { content: "\e005"; } 55 | 56 | .icon-export:before { content: "\e006"; } 57 | 58 | .icon-print:before { content: "\e007"; } 59 | 60 | .icon-more:before { content: "\e008"; } 61 | 62 | /* --------------- 63 | Sidebar icons 64 | --------------- */ 65 | /* LEFT SIDEBAR ICONS */ 66 | .icon-help:before { content: "\e010"; } 67 | 68 | .icon-link:before { content: "\e011"; } 69 | 70 | .icon-webimage:before { content: "\e012"; } 71 | 72 | .icon-image:before { content: "\e013"; } 73 | 74 | /* RIGHT SIDEBAR ICON */ 75 | .icon-style:before { content: "\e020"; } 76 | 77 | 78 | /* ============== 79 | LITTLE ICONS 80 | ============== */ 81 | 82 | @font-face { 83 | font-family: 'Mado-Little-Icons-Normal'; 84 | src: url('../fonts/Mado-Little-Icons-Normal.ttf') format('truetype'); 85 | font-weight: normal; 86 | font-style: normal; 87 | } 88 | 89 | /* Use the following CSS code (one class per icon) */ 90 | .little-icon-win-close, 91 | .little-icon-win-maximize, 92 | .little-icon-win-minimize, 93 | .little-icon-mac-close, 94 | .little-icon-mac-minimize, 95 | .little-icon-mac-maximize, 96 | .little-icon-chr-close, 97 | .little-icon-chr-maximize, 98 | .little-icon-chr-minimize, 99 | .little-icon-lin-close, 100 | .little-icon-lin-maximize, 101 | .little-icon-lin-minimize, 102 | .little-icon-both, 103 | .little-icon-switch, 104 | .little-icon-delete, 105 | .little-icon-unsaved { 106 | /* Typography */ 107 | font-family: 'Mado-Little-Icons-Normal'; 108 | speak: none; 109 | font-size: 16px; /* Crisp size */ 110 | font-style: normal; 111 | font-weight: normal; 112 | font-variant: normal; 113 | text-transform: none; 114 | -webkit-font-smoothing: antialiased; 115 | } 116 | 117 | /* ------------- 118 | Frame icons 119 | ------------- */ 120 | /* WINDOWS FRAME ICONS */ 121 | .little-icon-win-close:before { content: "\e200"; } 122 | 123 | .little-icon-win-maximize:before { content: "\e201"; } 124 | 125 | .little-icon-win-minimize:before { content: "\e202"; } 126 | 127 | /* MAC OS X FRAME ICONS */ 128 | .little-icon-mac-close:before { content: "\e300"; } 129 | 130 | .little-icon-mac-minimize:before { content: "\e301"; } 131 | 132 | .little-icon-mac-maximize:before { content: "\e302"; } 133 | 134 | /* CHROME OS FRAME ICONS */ 135 | .little-icon-chr-close:before { content: "\e400"; } 136 | 137 | .little-icon-chr-maximize:before { content: "\e401"; } 138 | 139 | .little-icon-chr-minimize:before { content: "\e402"; } 140 | 141 | /* LINUX UBUNTU FRAME ICONS */ 142 | .little-icon-lin-close:before { content: "\e500"; } 143 | 144 | .little-icon-lin-minimize:before { content: "\e501"; } 145 | 146 | .little-icon-lin-maximize:before { content: "\e502"; } 147 | 148 | /* --------------------------- 149 | Navbar switch "Both" icon 150 | --------------------------- */ 151 | .little-icon-both:before { content: "\e010"; } 152 | 153 | /* --------------- 154 | Generic icons 155 | --------------- */ 156 | .little-icon-switch:before { content: "\e000"; } 157 | 158 | .little-icon-delete:before { content: "\e001"; } 159 | 160 | /* ------------- 161 | Topbar icon 162 | ------------- */ 163 | .little-icon-unsaved:before { content: "\e002"; } -------------------------------------------------------------------------------- /css/window-frame.css: -------------------------------------------------------------------------------- 1 | /* ============ 2 | WINDOW BAR 3 | ============ 4 | Specific styles for Mado's window bar. 5 | It displays the document's name, the app title and three buttons: 6 | - "Minimize": on click, Mado is minimized; 7 | - "Maximize"/"Restore": on click, Mado is set to fullscreen mode or restored to its last dimensions; 8 | - "Close": on click, closes the document. */ 9 | 10 | #window-bar { 11 | /* Positioning */ 12 | position: fixed; 13 | top: 0; 14 | left: 0; 15 | z-index: 3000; 16 | 17 | /* Box-model */ 18 | width: 100%; 19 | height: 20px; 20 | 21 | /* Misc */ 22 | -webkit-app-region: drag; /* Enable drag action */ 23 | } 24 | 25 | /* -------- 26 | Header 27 | -------- */ 28 | #app-header { 29 | /* Positioning */ 30 | position: absolute; 31 | bottom: 0; 32 | left: 0; 33 | 34 | /* Box-model */ 35 | width: 100%; 36 | 37 | /* Typography */ 38 | color: #fff; 39 | font-size: 0.9em; 40 | line-height: 1em; 41 | text-align: center; 42 | 43 | /* Misc */ 44 | pointer-events: none; 45 | } 46 | 47 | #app-header-centered { position: relative; } /* Help positioning the "Unsaved" icon */ 48 | 49 | #save-state span { 50 | /* Positioning */ 51 | position: absolute; /* See #app-header-centered above */ 52 | left: -20px; /* Override the child span's own width */ 53 | top: 0px; 54 | 55 | /* Box-model */ 56 | display: block; 57 | width: 20px; 58 | height: 22px; 59 | 60 | /* Typography */ 61 | line-height: 22px; 62 | text-align: center; 63 | } 64 | 65 | /* ------- 66 | Alert 67 | ------- */ 68 | /* Setting the "Close" button's positioning to relative */ 69 | #window-close { 70 | /* Positioning */ 71 | position: relative; 72 | z-index: 3000; 73 | } 74 | 75 | /* When the user clicks the "Close" button with unsaved changes, the displayer is deployed and shows the alert */ 76 | #close-alert-displayer { 77 | /* Positioning */ 78 | position: absolute; 79 | right: 5px; 80 | 81 | /* Box-model */ 82 | width: 450px; 83 | 84 | /* Misc */ 85 | -webkit-transform-origin: 430px 1px; 86 | transition: all .15s ease-out; 87 | } 88 | 89 | #close-alert-displayer.hidden { -webkit-transform: scale(0); } 90 | 91 | /* The arrow is a gray corner for the container */ 92 | #close-alert-arrow { 93 | /* Positioning */ 94 | position: relative; /* For the Z-index */ 95 | margin: 1px 0 0 431px; 96 | z-index: 1500; 97 | 98 | /* Box-model */ 99 | width: 0; 100 | height: 0; 101 | 102 | /* Visual */ 103 | border-color: transparent transparent #fff transparent; 104 | border-style: solid; 105 | border-width: 0 4px 4px 4px; 106 | } 107 | 108 | #close-alert-container { 109 | /* Positioning */ 110 | position: relative; 111 | 112 | /* Box-model */ 113 | width: 450px; 114 | height: 225px; 115 | overflow: hidden; 116 | padding: 20px 20px 80px; /* The padding-bottom matches the footer's height */ 117 | 118 | /* Typography */ 119 | color: #4a4942; 120 | font-size: 0.8em; 121 | line-height: 1.3em; 122 | text-align: left; 123 | 124 | /* Visual */ 125 | background-color: #fff; 126 | border-radius: 5px; 127 | box-shadow: 0 0 15px rgba(0,0,0,.2); 128 | 129 | /* Misc */ 130 | transition: height .1s ease; 131 | } 132 | 133 | #alert-message { 134 | /* Box-model */ 135 | width: 100%; 136 | height: 100%; 137 | overflow-y: auto; /* Enables long messages */ 138 | } 139 | 140 | /* ALERT STYLES */ 141 | /* Main title for the alert */ 142 | #alert-title { 143 | /* Box-model */ 144 | padding-bottom: 14px; 145 | 146 | /* Typography */ 147 | font-family: "Segoe UI Light", "HelveticaNeue-Light", "Helvetica Neue Light", "RobotoLight", "Roboto Light", "Open Sans", sans-serif; 148 | font-size: 2.3em; 149 | line-height: 1.3em; 150 | } 151 | 152 | /* Content block inside the option window */ 153 | .alert-part { line-height: 27px; } 154 | 155 | .alert-part:not(:first-of-type) { padding-top: 10px; } 156 | 157 | .alert-part:not(:last-of-type) { padding-bottom: 10px; } 158 | 159 | /* ALERT ACTIONS 160 | Contains the style for the alert's footer. The footer itself contains the buttons that respond to the alert. */ 161 | #alert-actions { 162 | /* Positioning */ 163 | position: absolute; 164 | bottom: 0; 165 | left: 0; 166 | 167 | /* Box-model */ 168 | width: 100%; 169 | height: 80px; 170 | padding: 20px; 171 | 172 | /* Visual */ 173 | background-color: #f8f8f8; 174 | } 175 | 176 | .alert-button-container { float: left; } 177 | 178 | .alert-button-container:not(:last-of-type) { padding-right: 5px; } 179 | -------------------------------------------------------------------------------- /js/Topbar/SwitchManager.js: -------------------------------------------------------------------------------- 1 | function SwitchManager() { 2 | /* Outlets */ 3 | this.madoFooter = $("#mado-footer"); 4 | this.switchCursor = $("#switch-cursor"); 5 | this.switchToMD = $("#switch-md"); 6 | this.switchToBoth = $("#switch-both"); 7 | this.switchToHTML = $("#switch-html"); 8 | this.workspace = $("#workspace"); 9 | 10 | /* Variables */ 11 | this.switchButtons = [this.switchToMD, this.switchToBoth, this.switchToHTML]; // Wrapping the switch buttons in an array. 12 | this.previousSize = chrome.app.window.current().outerBounds.width; // Setting the size of the window, forbid the resize() function to be launched before the complete loading. 13 | 14 | /* Events */ 15 | chrome.app.window.current().onBoundsChanged.addListener($.proxy(function () { 16 | if (chrome.app.window.current().outerBounds.width < 1160 && this.switchToBoth.hasClass("activated")) { 17 | this.switchToMD.click(); // Markdown set as default view. 18 | } else if (chrome.app.window.current().outerBounds.width >= 1160 && this.previousSize < 1160) { 19 | this.switchToBoth.click(); 20 | } 21 | this.previousSize = chrome.app.window.current().outerBounds.width; 22 | }, this)); 23 | 24 | this.switchToMD.add(this.switchToBoth).add(this.switchToHTML).on("click", $.proxy(function(e) { this.activate(e.currentTarget.id); }, this)); 25 | 26 | Mousetrap.bind(["command+alt+left", "ctrl+alt+left"], $.proxy(function(e) { // Ctrl + -> = to the left. 27 | this.switch("left"); 28 | return false; 29 | }, this)); 30 | Mousetrap.bind(["command+alt+right", "ctrl+alt+right"], $.proxy(function(e) { // Ctrl + <- = to the right. 31 | this.switch("right"); 32 | return false; 33 | }, this)); 34 | 35 | /* Initialization */ 36 | if (chrome.app.window.current().outerBounds.width > 1159) { // Big window, both sides are displayed. 37 | this.activate("switch-both"); 38 | } else { // Small window, we only show the Markdown side. 39 | this.activate("switch-md"); 40 | } 41 | } 42 | 43 | SwitchManager.prototype = { 44 | constructor: SwitchManager, 45 | 46 | /* Do the switch. */ 47 | activate: function(button) { 48 | for (var i = 0; i < this.switchButtons.length; i++) { 49 | if (this.switchButtons[i].attr("id") != button) { // Deactivating the switch buttons that are not selected. 50 | this.switchButtons[i].removeClass("activated"); 51 | } else { // Activating the clicked button. 52 | this.switchButtons[i].addClass("activated"); 53 | } 54 | } 55 | 56 | switch (button) { 57 | case this.switchButtons[0].attr("id"): // Markdown. 58 | this.workspace.add(this.switchCursor).add(this.madoFooter).attr("class", "markdown-view"); 59 | break; 60 | case this.switchButtons[1].attr("id"): // Normal. 61 | this.workspace.add(this.switchCursor).attr("class", "normal"); 62 | this.madoFooter.attr("class", ""); 63 | break; 64 | case this.switchButtons[2].attr("id"): // HTML. 65 | this.workspace.add(this.switchCursor).attr("class", "conversion-view"); 66 | this.madoFooter.attr("class", ""); 67 | break; 68 | } 69 | }, 70 | 71 | /* Shortcut used to switch in a specific direction. */ 72 | switch: function(direction) { 73 | if (window.innerWidth > 1159) { // Normal window 74 | for (var i = 0; i < this.switchButtons.length; i++) { 75 | if (this.switchButtons[i].hasClass("activated")) { // We found which button is activated. 76 | if (direction == "left" && i > 0) { // Left. 77 | this.switchButtons[i - 1].click(); // The previous button is now activated. 78 | } else if (direction == "right" && i < this.switchButtons.length -1) { // Right. 79 | this.switchButtons[i + 1].click(); // The next button is now activated. 80 | } 81 | i = this.switchButtons.length; // End of the loop. 82 | } 83 | } 84 | } else { // Small window, only Markdown and HTML views are available so the code is easier. 85 | if (direction == "left") { 86 | this.switchToMD.click(); 87 | } else { 88 | this.switchToHTML.click(); 89 | } 90 | } 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /js/lib/mousetrap.min.js: -------------------------------------------------------------------------------- 1 | /* mousetrap v1.5.3 craig.is/killing/mice */ 2 | (function(C,r,g){function t(a,b,h){a.addEventListener?a.addEventListener(b,h,!1):a.attachEvent("on"+b,h)}function x(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return l[a.which]?l[a.which]:p[a.which]?p[a.which]:String.fromCharCode(a.which).toLowerCase()}function D(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function u(a){return"shift"==a||"ctrl"==a||"alt"==a|| 3 | "meta"==a}function y(a,b){var h,c,e,g=[];h=a;"+"===h?h=["+"]:(h=h.replace(/\+{2}/g,"+plus"),h=h.split("+"));for(e=0;em||l.hasOwnProperty(m)&&(k[l[m]]=m)}e=k[h]?"keydown":"keypress"}"keypress"==e&&g.length&&(e="keydown");return{key:c,modifiers:g,action:e}}function B(a,b){return null===a||a===r?!1:a===b?!0:B(a.parentNode,b)}function c(a){function b(a){a= 4 | a||{};var b=!1,n;for(n in q)a[n]?b=!0:q[n]=0;b||(v=!1)}function h(a,b,n,f,c,h){var g,e,l=[],m=n.type;if(!d._callbacks[a])return[];"keyup"==m&&u(a)&&(b=[a]);for(g=0;g":".","?":"/","|":"\\"},z={option:"alt",command:"meta","return":"enter", 9 | escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},k;for(g=1;20>g;++g)l[111+g]="f"+g;for(g=0;9>=g;++g)l[g+96]=g;c.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};c.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};c.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};c.prototype.reset=function(){this._callbacks={};this._directMap= 10 | {};return this};c.prototype.stopCallback=function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")||B(b,this.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};c.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};c.init=function(){var a=c(r),b;for(b in a)"_"!==b.charAt(0)&&(c[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};c.init();C.Mousetrap=c;"undefined"!==typeof module&&module.exports&&(module.exports= 11 | c);"function"===typeof define&&define.amd&&define(function(){return c})})(window,document); 12 | -------------------------------------------------------------------------------- /more/shortcuts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shortcuts - Mado 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 |

htmlShortcuts

43 | 44 |
45 | 46 |

moreShortcutsClassicTitle

47 | 48 |
49 | 50 |
51 |  N 52 |
53 |

moreShortcutsNew

54 |
55 | 56 |
57 | 58 |
59 |  O 60 |
61 |

moreShortcutsOpen

62 |
63 | 64 |
65 | 66 |
67 |  S 68 |
69 |

moreShortcutsSave

70 |
71 | 72 |
73 | 74 |
75 |   S 76 |
77 |

moreShortcutsSaveAs

78 |
79 | 80 |
81 | 82 |
83 |  P 84 |
85 |

moreShortcutsPrint

86 |
87 | 88 |
89 | 90 |
91 |  H 92 |
93 |

moreShortcutsHelp

94 |
95 | 96 |
97 | 98 |
99 |  K 100 |
101 |

moreShortcutsLink

102 |
103 |
104 | 105 |
106 |  W 107 |
108 |

moreShortcutsClose

109 |
110 |
111 | 112 |
113 | 114 |

moreShortcutsSpecificTitle

115 |
116 | 117 |
118 |  Alt  119 |
120 |

moreShortcutsSwitchLeft

121 |
122 | 123 |
124 |
125 |  Alt  126 |
127 |

moreShortcutsSwitchRight

128 |
129 |
130 |
131 | 132 | 133 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // 1. All configuration goes here 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON("package.json"), 6 | 7 | copy: { 8 | main: { // Copy everything in a min folder. 9 | expand: true, 10 | src: [ 11 | "manifest.json", 12 | "_locales/**", 13 | "css/**", 14 | "fonts/**", 15 | "img/**", 16 | "js/background.js", 17 | "js/lib/*" 18 | ], 19 | dest: "min/" 20 | } 21 | }, 22 | 23 | concat: { 24 | mado: { 25 | src: ["js/onload.js", "js/Editor/*.js", "js/Topbar/*.js", "js/Window/*.js"], 26 | dest: "min/js/mado.js" 27 | }, 28 | moreAbout: { 29 | src: [ 30 | ["js/more/CloseButtonManager.js", "js/more/Localizer.js", "js/more/about-onload.js"], 31 | ], 32 | dest: "min/js/more/about.js" 33 | }, 34 | moreSettings: { 35 | src: [ 36 | ["js/more/CloseButtonManager.js", "js/more/Localizer.js", "js/more/SettingsManager.js", "js/more/settings-onload.js"], 37 | ], 38 | dest: "min/js/more/settings.js" 39 | }, 40 | moreShortcuts: { 41 | src: [ 42 | ["js/more/CloseButtonManager.js", "js/more/Localizer.js", "js/more/ShortcutsManager.js", "js/more/shortcuts-onload.js"], 43 | ], 44 | dest: "min/js/more/shortcuts.js" 45 | } 46 | }, 47 | 48 | jshint: { 49 | beforeconcat: ["js/onload.js", "js/Editor/*.js", "js/Topbar/*.js", "js/Window/*.js", "js/more/*.js"], 50 | afterconcat: ["min/js/mado.js", "min/js/more/*.js"] 51 | }, 52 | 53 | uglify: { 54 | mado: { 55 | files: { 56 | "min/js/mado.js": ["min/js/mado.js"], 57 | "min/js/more/about.js": ["min/js/more/about.js"], 58 | "min/js/more/settings.js": ["min/js/more/settings.js"], 59 | "min/js/more/shortcuts.js": ["min/js/more/shortcuts.js"] 60 | } 61 | } 62 | }, 63 | 64 | processhtml: { 65 | mado: { 66 | files: { 67 | "min/mado.html": ["mado.html"], 68 | "min/more/about.html": ["more/about.html"], 69 | "min/more/settings.html": ["more/settings.html"], 70 | "min/more/shortcuts.html": ["more/shortcuts.html"] 71 | } 72 | } 73 | }, 74 | 75 | htmlclean: { 76 | deploy: { 77 | expand: true, 78 | cwd: "min/", 79 | src: "**/*.html", 80 | dest: "min/" 81 | } 82 | }, 83 | 84 | usebanner: { 85 | html: { 86 | options: { 87 | position: "top", 88 | banner: "", 89 | linebreak: true 90 | }, 91 | files: { 92 | src: ["min/*.html", "min/more/*.html"] 93 | } 94 | }, 95 | cssJsBanner: { 96 | options: { 97 | position: "top", 98 | banner: "/* Copyright (c) 2016 Armand Grillet.\nSee the license in the \"About\" section for further information. */", 99 | linebreak: true 100 | }, 101 | files: { 102 | src: ["min/css/*.css", "min/css/more/*.css", "min/css/themes/*.css", "min/js/mado.js", "min/js/more/*.js", "!min/css/icons.css"] 103 | } 104 | }, 105 | iconsBanner: { 106 | options: { 107 | position: "top", 108 | banner: "/* Copyright (c) 2016 Armand Grillet.\nSee the license in the \"About\" section for further information.\nGenerated via IcoMoon (http://icomoon.io/app/) */", 109 | linebreak: true 110 | }, 111 | files: { 112 | src: ["min/css/icons.css"] 113 | } 114 | } 115 | } 116 | }); 117 | 118 | // 3. Where we tell Grunt we plan to use this plug-in. 119 | grunt.loadNpmTasks("grunt-banner"); 120 | grunt.loadNpmTasks("grunt-contrib-clean"); 121 | grunt.loadNpmTasks("grunt-contrib-concat"); 122 | grunt.loadNpmTasks("grunt-contrib-copy"); 123 | grunt.loadNpmTasks('grunt-contrib-jshint'); 124 | grunt.loadNpmTasks('grunt-contrib-uglify'); 125 | grunt.loadNpmTasks("grunt-htmlclean"); 126 | grunt.loadNpmTasks("grunt-processhtml"); 127 | grunt.loadNpmTasks("grunt-yui-compressor"); 128 | 129 | // 4. Where we tell Grunt what to do when we type "grunt" into the terminal. 130 | grunt.registerTask("default", ["copy", "concat", "jshint", "uglify", "processhtml", "htmlclean", "usebanner"]); 131 | }; 132 | -------------------------------------------------------------------------------- /more/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | About Mado 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 | 37 |
38 | 39 |
40 | 41 |

moreAboutTitle

42 | 43 | 44 |
45 | 46 | 47 | 48 | 49 |

moreAboutUsing0.5moreAboutCreatedBy Armand Grillet.

50 |
51 | 52 | 53 |
54 | 55 |

moreAboutProjects

56 | 57 | 65 |
66 | 67 | 68 |
69 | 70 |

moreAboutFonts

71 | 72 | 78 |
79 | 80 | 81 |
82 |

Mado moreAboutAvailableOnGitHub moreAboutMITLicense

83 |
84 | 85 |
86 |
The MIT License (MIT)
87 |
88 |

89 | Copyright (c) 2016 Armand Grillet 90 |

91 | Permission is hereby granted, free of charge, to any person obtaining a copy 92 | of this software and associated documentation files (the "Software"), to deal 93 | in the Software without restriction, including without limitation the rights 94 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 95 | copies of the Software, and to permit persons to whom the Software is 96 | furnished to do so, subject to the following conditions: 97 |

98 |

99 | The above copyright notice and this permission notice shall be included in 100 | all copies or substantial portions of the Software. 101 |

102 |

103 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 104 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 105 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 106 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 107 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 108 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 109 | THE SOFTWARE. 110 |

111 |
112 |
113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /js/Window/AppWindow.js: -------------------------------------------------------------------------------- 1 | function AppWindow(app) { 2 | /* Outlets */ 3 | this.cancelCloseButton = $("#cancel"); // Close div to cancel closing. 4 | this.closeDisplayer = $("#close-alert-displayer"); // The div that contains all the close divs. 5 | this.head = $("head")[0]; // The "head" section of the main app. 6 | this.quitCloseButton = $("#quit"); // Quit button. 7 | this.saveAndQuitCloseButton = $("#save-quit"); // "Save and exit" div. 8 | this.close = $("#window-close-button"); // Close button. 9 | this.maximize = $("#window-maximize"); // Maximize button. 10 | this.minimize = $("#window-minimize"); // Minimize buton. 11 | 12 | /* Variables */ 13 | this.app = app; // The application that is contained in the window. 14 | 15 | /* Events */ 16 | $(document).click($.proxy(function(e) { 17 | if ($(e.target).closest(this.close).length) { // Click on the close button. 18 | this.closeWindow(); // Display the close div's container. 19 | } else if (this.closeDisplayer.hasClass("visible") && !$(e.target).closest(this.closeDisplayer).length) { 20 | this.closeDisplayer.attr("class", "hidden"); 21 | } 22 | }, this)); 23 | 24 | Mousetrap.bind(["command+w", "ctrl+w"], $.proxy(function(e) { // Ctrl + w = close. 25 | this.closeWindow(); 26 | return false; 27 | }, this)); 28 | 29 | this.maximize.on("click", function(e) { 30 | if (! chrome.app.window.current().isMaximized()) { 31 | chrome.app.window.current().maximize(); 32 | } else { // Restore the last bounds. 33 | chrome.app.window.current().restore(); 34 | } 35 | }); 36 | 37 | this.minimize.on("click", function(e) { 38 | chrome.app.window.current().minimize(); 39 | }); 40 | 41 | this.cancelCloseButton.on("click", $.proxy(function(e) { this.closeDisplayer.attr("class", "hidden"); }, this)); // Cancels the closure. 42 | this.quitCloseButton.on("click", $.proxy(function(e) { this.quitCloseWindow(); }, this)); // Quit directly. 43 | this.saveAndQuitCloseButton.on("click", $.proxy(function(e) { this.saveQuitCloseWindow(); }, this)); // Saves the file and closes it. 44 | 45 | /* Initialization */ 46 | this.init(); 47 | } 48 | 49 | AppWindow.prototype = { 50 | constructor: AppWindow, 51 | 52 | /* Close the window if the document is saved or display the closeDisplayer. */ 53 | closeWindow: function() { 54 | if (this.app.isDocumentSaved()) { // Document saved. 55 | this.setNewBounds(function() { chrome.app.window.current().close(); }); 56 | } else { 57 | this.closeDisplayer.attr("class", "visible"); 58 | } 59 | }, 60 | 61 | /* Set the window depending on the operating system. */ 62 | init: function() { 63 | var operatingSystem; 64 | var frameStylesheetLink = document.createElement("link"); 65 | 66 | frameStylesheetLink.setAttribute("rel", "stylesheet"); 67 | frameStylesheetLink.setAttribute("type", "text/css"); 68 | 69 | if (navigator.appVersion.indexOf("Mac") > -1) { // If the user is on a Mac, redirect to the Mac window frame styles. 70 | operatingSystem = "mac"; 71 | } else if (navigator.appVersion.indexOf("Win") > -1) { // If the user is on a Windows PC, redirect to the Windows window frame styles. 72 | operatingSystem = "windows"; 73 | } else if (navigator.appVersion.indexOf("Linux") > -1) { // If the user is on a Linux computer, redirect to the Linux Ubuntu window frame styles. 74 | operatingSystem = "linux"; 75 | } else { // If the user is on another type of computer, redirect to the generic window frame styles (which are primarily Chrome OS's styles). 76 | operatingSystem = "chromeos"; 77 | } 78 | 79 | frameStylesheetLink.setAttribute("href", "css/window-frame-" + operatingSystem + ".css"); // Set the correct window frame CSS file. 80 | this.close.attr("class", "cta little-icon-" + operatingSystem.substring(0,3) + "-close"); // Set the correct close button. 81 | this.maximize.attr("class", "cta little-icon-" + operatingSystem.substring(0,3) + "-maximize"); // Set the correct maximize button. 82 | this.minimize.attr("class", "cta little-icon-" + operatingSystem.substring(0,3) + "-minimize"); // Set the correct minimize button. 83 | 84 | this.head.appendChild(frameStylesheetLink); // Append the link node to the "head" section. 85 | }, 86 | 87 | /* Set the bounds and close the window. */ 88 | quitCloseWindow: function() { 89 | this.setNewBounds(function() { chrome.app.window.current().close(); }); 90 | }, 91 | 92 | /* Save the document and close the window. */ 93 | saveQuitCloseWindow: function() { 94 | this.setNewBounds($.proxy(function() { 95 | if (this.app.isEntrySaved()) { // Document is a known entry, we save it. 96 | this.app.save(function() { chrome.app.window.current().close(); }); 97 | } else { // The document is not named so we save it as a new document. 98 | this.app.saveAs(function() { chrome.app.window.current().close(); }); 99 | } 100 | }, this)); 101 | 102 | }, 103 | 104 | /* Set the bounds and apply the callback function. */ 105 | setNewBounds: function(callback) { 106 | chrome.runtime.getBackgroundPage($.proxy(function(backgroundPage) { // Set the bounds for the Mado's window size on relaunch. 107 | backgroundPage.newBounds(chrome.app.window.current().outerBounds); 108 | callback(); 109 | }, this)); 110 | } 111 | }; 112 | -------------------------------------------------------------------------------- /js/Editor/WebImageManager.js: -------------------------------------------------------------------------------- 1 | function WebImageManager(editor) { 2 | /* Outlets */ 3 | this.cancelWebImageButton = $("#cancel-webimage"); 4 | this.insertWebImageButton = $("#insert-webimage"); 5 | this.webImageButton = $("#webimage-button"); 6 | this.webImageUrlInput = $("#webimage-url"); 7 | this.webImageAltInput = $("#webimage-alt-input"); 8 | this.webImageDisplayer = $("#webimage-insertion-displayer"); 9 | this.webImageBox = $("#webimage-insertion-box"); 10 | 11 | /* Variables */ 12 | this.editor = editor; // The editor that we will modify. 13 | this.startSelection = undefined; // The beginning of the selection in the editor when we are writing the link. 14 | this.firstEndSelection = undefined; // The beginning of the selection in the editor when we apply the LinkManager. 15 | this.endSelection = undefined; // The end of the selection in the editor when we are writing the link. 16 | this.initialSelection = undefined; // The text selected in the editor when we apply the LinkManager. 17 | 18 | /* Events */ 19 | $(document).click($.proxy(function(e) { 20 | if ($(e.target).closest("#webimage-button").length) { 21 | if (this.webImageDisplayer.hasClass("hidden")) { 22 | this.reset(); 23 | this.display(); 24 | } else { 25 | this.webImageDisplayer.addClass("hidden"); // This is not a cancellation. 26 | } 27 | } else if (!this.webImageDisplayer.hasClass("hidden") && !$(e.target).closest("#webimage-insertion-box").length) { 28 | this.webImageDisplayer.toggleClass("hidden"); 29 | } 30 | }, this)); 31 | 32 | this.insertWebImageButton.on("click", $.proxy(function(e){ this.apply(); }, this)); // Display the manager when clicking the button. 33 | this.cancelWebImageButton.on("click", $.proxy(function(e){ this.cancel(); }, this)); // Cancels the image (put the old selection). 34 | 35 | this.webImageAltInput.add(this.webImageUrlInput).keyup($.proxy(function(e) { 36 | switch (e.keyCode) { 37 | case 13: // The user press enter. 38 | this.apply(); 39 | break; 40 | case 27: // The user press echap. 41 | this.cancel(); 42 | break; 43 | default: 44 | this.update(); 45 | } 46 | }, this)); 47 | 48 | this.webImageAltInput.keydown($.proxy(function(e){ 49 | if (e.keyCode == 9) { // The user press tab, we put the focus in the image's URL input. 50 | e.preventDefault(); 51 | this.onlineImageUrlInput.select(); 52 | } 53 | }, this)); 54 | } 55 | 56 | WebImageManager.prototype = { 57 | constructor: WebImageManager, 58 | 59 | /* Applies the new image and closes the manager. */ 60 | apply: function() { 61 | if (this.webImageAltInput.val() === "") { // An alternative text is mandatory 62 | this.webImageAltInput.attr("class", "flash"); // We focus the cursor on the input to add an alternative text. 63 | this.webImageAltInput.focus(); 64 | setTimeout($.proxy(function() { 65 | this.altInput.removeClass("flash"); 66 | }, this), 800); // The "flash" class is deleted after 0.8 seconds (the flash animation's duration). 67 | } else if (this.webImageUrlInput.val() === "") { 68 | this.webImageUrlInput.attr("class", "flash"); 69 | this.webImageUrlInput.focus(); 70 | this.webImageUrlInput.attr("class", ""); 71 | } else { // Everything looks good, we close the manager. 72 | this.webImageDisplayer.addClass("hidden"); 73 | this.editor.focus(); 74 | this.editor.setSelection(this.endSelection, this.endSelection); // The caret is at the end of the image. 75 | } 76 | }, 77 | 78 | /* Cancels the manager. */ 79 | cancel: function() { 80 | this.webImageDisplayer.toggleClass("hidden"); 81 | this.editor.replaceSelection(this.initialSelection, this.startSelection, this.endSelection, "select"); 82 | }, 83 | 84 | /* Displays the manager, if a selection is made we use the text to fill inputs. */ 85 | display: function() { 86 | this.webImageDisplayer.toggleClass("hidden"); 87 | var selection = this.editor.getSelection(); // Use of rangyinputs.js 88 | this.initialSelection = selection.text; 89 | this.startSelection = selection.start; 90 | this.firstEndSelection = selection.end; 91 | this.endSelection = selection.end; 92 | 93 | if (/!\[.*\]\(.*\)/.test(selection.text) && selection.text[0] == '!' && selection.text.slice(-1) == ')') { // An online image in markdown syntax has been selected. 94 | this.webImageAltInput.val(selection.text.match(/!\[.*\]/)[0].substring(2, selection.text.match(/!\[.*\]/)[0].length - 1)); 95 | this.webImageUrlInput.val(selection.text.match(/\(.*\)/)[0].substring(1, selection.text.match(/\(.*\)/)[0].length - 1)); 96 | } else { 97 | this.webImageAltInput.val(selection.text); // We put all the selection as the alternative value. 98 | } 99 | this.webImageUrlInput.focus(); 100 | }, 101 | 102 | /* Reset the manager. */ 103 | reset: function() { 104 | this.webImageAltInput.val(""); 105 | this.webImageUrlInput.val(""); 106 | this.initialSelection = this.editor.getMarkdown(); 107 | }, 108 | 109 | /* Input in the manager, we update the markdown */ 110 | update: function() { 111 | var webImage = "![" + this.webImageAltInput.val() + "](" + this.webImageUrlInput.val() + ')'; 112 | 113 | this.editor.setMarkdown(webImage, this.startSelection, this.endSelection); // Sets the markdown value. 114 | this.endSelection = this.startSelection + webImage.length; // Modifies the selection for the next update. 115 | } 116 | }; 117 | -------------------------------------------------------------------------------- /js/Editor/LinkManager.js: -------------------------------------------------------------------------------- 1 | function LinkManager(editor) { 2 | /* Outlets */ 3 | this.cancelLinkButton = $("#cancel-link"); 4 | this.insertLink = $("#insert-link"); 5 | this.linkButton = $("#link-button"); 6 | this.linkDisplayer = $("#link-insertion-displayer"); 7 | this.urlInput = $("#url-input"); 8 | this.hypertextInput = $("#hypertext-input"); 9 | 10 | /* Variables */ 11 | this.editor = editor; // The editor that we will modify. 12 | this.startSelection = undefined; // The beginning of the selection in the editor when we are writing the link. 13 | this.firstEndSelection = undefined; // The beginning of the selection in the editor when we apply the LinkManager. 14 | this.endSelection = undefined; // The end of the selection in the editor when we are writing the link. 15 | this.initialSelection = undefined; // The text selected in the editor when we apply the LinkManager. 16 | 17 | /* Events */ 18 | $(document).click($.proxy(function(e) { 19 | if ($(e.target).closest("#link-button").length) { // Click on the button. 20 | if(this.linkDisplayer.hasClass("hidden")) { // If hidden we reset and display the manager. 21 | this.reset(); 22 | this.display(); 23 | } else { // This is not a cancellation. 24 | this.linkDisplayer.addClass("hidden"); 25 | } 26 | } else if (!this.linkDisplayer.hasClass("hidden") && !$(e.target).closest("#link-insertion-displayer").length) { // Click elsewhere. 27 | this.linkDisplayer.toggleClass("hidden"); 28 | } 29 | }, this)); 30 | 31 | this.insertLink.on("click", $.proxy(function(e){ this.apply(); }, this)); // Display the manager when clicking the button. 32 | this.cancelLinkButton.on("click", $.proxy(function(e){ this.cancel(); }, this)); // Cancel the link (put the old selection). 33 | 34 | Mousetrap.bind(["command+k", "ctrl+k"], $.proxy(function(e) { // Ctrl+k = link. 35 | this.linkButton.click(); 36 | return false; 37 | }, this)); 38 | 39 | this.urlInput.keyup($.proxy(function(e){ 40 | switch (e.keyCode) { 41 | case 13: // The user press enter, we apply. 42 | this.apply(); 43 | break; 44 | case 27: // The user press echap, we cancel. 45 | this.cancel(); 46 | break; 47 | default: // THe user type something in an input, we update. 48 | this.update(); 49 | } 50 | }, this)); 51 | 52 | this.hypertextInput.keydown($.proxy(function(e){ 53 | if (e.keyCode == 9) { // The user press tab, we put the focus in the URL input. 54 | e.preventDefault(); 55 | this.urlInput.select(); 56 | } 57 | }, this)); 58 | 59 | this.hypertextInput.keyup($.proxy(function(e){ 60 | switch (e.keyCode) { 61 | case 13: // The user press enter, we apply. 62 | this.apply(); 63 | break; 64 | case 27: // The user press echap, we cancel. 65 | this.cancel(); 66 | break; 67 | default: // THe user type something in an input, we update. 68 | this.update(); 69 | } 70 | }, this)); 71 | } 72 | 73 | LinkManager.prototype = { 74 | constructor: LinkManager, 75 | 76 | /* Applies the new link and closes the manager. */ 77 | apply: function() { 78 | if (this.urlInput.val() === "") { 79 | this.urlInput.addClass("flash"); // If the URL input is empty, a flash is triggered on it so that the user knows he has to fill it. 80 | this.urlInput.focus(); 81 | setTimeout($.proxy(function() { 82 | this.urlInput.removeClass("flash"); 83 | }, this), 800); // The "flash" class is deleted after 0.8 seconds (the flash animation's duration). 84 | } else { 85 | this.linkDisplayer.addClass("hidden"); 86 | this.editor.focus(); 87 | this.editor.setSelection(this.endSelection, this.endSelection); // The caret is at the end of the link. 88 | } 89 | }, 90 | 91 | /* Cancels the manager. */ 92 | cancel: function() { 93 | this.linkDisplayer.toggleClass("hidden"); 94 | this.editor.replaceSelection(this.initialSelection, this.startSelection, this.endSelection, "select"); 95 | }, 96 | 97 | /* Display the manager, if a selection is made we use the text to fill inputs. */ 98 | display: function() { 99 | this.linkDisplayer.toggleClass("hidden"); 100 | var selection = this.editor.getSelection(); // Use of rangyinputs.js 101 | var initialSelection = selection.text; // Shortcut 102 | this.initialSelection = initialSelection; 103 | this.startSelection = selection.start; 104 | this.firstEndSelection = selection.end; 105 | this.endSelection = selection.end; 106 | 107 | if (/\[.*\]\(.*\)/.test(initialSelection) && initialSelection[0] == '[' && initialSelection.slice(-1) == ')') { // A link in markdown syntax has been selected. 108 | this.hypertextInput.val(initialSelection.match(/\[.*\]/)[0].substring(1, initialSelection.match(/\[.*\]/)[0].length - 1)); // Set the hypertext. 109 | this.urlInput.val(initialSelection.match(/\(.*\)/)[0].substring(1, initialSelection.match(/\(.*\)/)[0].length - 1)); // Set the link. 110 | } else { 111 | this.hypertextInput.val(initialSelection); // We put all the selection as the hypertext value. 112 | } 113 | this.urlInput.focus(); 114 | }, 115 | 116 | /* Reset the manager */ 117 | reset: function() { 118 | this.urlInput.val(""); 119 | this.hypertextInput.val(""); 120 | }, 121 | 122 | /* Input in the manager, we update the markdown */ 123 | update: function() { 124 | var link; 125 | if (this.hypertextInput.val() === "") { // Hypertext not given. 126 | link = '[' + this.urlInput.val() + "](" + this.urlInput.val() + ')'; // Only displays the url twice. 127 | } else { 128 | link = '[' + this.hypertextInput.val() + "](" + this.urlInput.val() + ')'; // Displays the hypertext and the URL. 129 | } 130 | this.editor.setMarkdown(link, this.startSelection, this.endSelection); // Sets the markdown value. 131 | this.endSelection = this.startSelection + link.length; // Modifies the selection for the next update. 132 | } 133 | }; 134 | -------------------------------------------------------------------------------- /js/Editor/Editor.js: -------------------------------------------------------------------------------- 1 | /* Editor is the hub of every objects working to improve the writing experience. */ 2 | function Editor() { 3 | /* Outlets */ 4 | this.markdown = $("#markdown"); // The div where is the markdown textarea. 5 | this.conversionDiv = $("#html-conversion"); 6 | 7 | /* Variables */ 8 | this.counter = new Counter(this.markdown[0]); // Counter of words/characters. 9 | this.displayManager = new DisplayManager(this); // Convert the markdown in HTML. 10 | this.helpManager = new HelpManager(); // Manage the help 11 | this.imageManager = new ImageManager(this); // Manage the button to add offline images. 12 | this.linkManager = new LinkManager(this); // Manage the button to add links. 13 | this.nameManager = new NameManager(this); // Manage the name of the file written. 14 | this.saveStateManager = new SaveStateManager(this); // Manage the save state of the file. 15 | this.scrollManager = new ScrollManager(this.markdown, this.conversionDiv); // Object allowing a synchronized scroll between the convertedDiv and the conversionDiv. 16 | this.webImageManager = new WebImageManager(this); // Manage the button to add online images. 17 | 18 | /* Events */ 19 | this.markdown.on("input propertychange", $.proxy(function() { this.convert(); }, this)); // Conversion when there is a change in the markdown textarea. 20 | 21 | this.markdown.on("keydown", function(e) { 22 | if (e.keyCode === 9) { 23 | e.preventDefault(); 24 | var start = this.selectionStart; 25 | var end = this.selectionEnd; 26 | 27 | // set textarea value to: text before caret + tab + text after caret 28 | $(this).val($(this).val().substring(0, start) + " " + $(this).val().substring(end)); 29 | 30 | // put caret at right position again 31 | this.selectionStart = this.selectionEnd = start + 4; 32 | } 33 | }); 34 | this.markdown.on("keydown", $.proxy(function(e) { if (e.keyCode === 9) { this.convert(); }}, this)); 35 | 36 | /* Initialization */ 37 | this.init(); 38 | } 39 | 40 | Editor.prototype = { 41 | constructor: Editor, 42 | 43 | /* Automatic scroll depending on the height of the two zones. */ 44 | checkHeight: function() { 45 | this.scrollManager.checkZonesHeight(); 46 | }, 47 | 48 | /* Update the objects that have to change when the markdown area is changed. */ 49 | convert: function() { 50 | this.displayManager.update(); 51 | this.saveStateManager.update(); 52 | this.counter.update(); 53 | }, 54 | 55 | /* Focus in the textarea. */ 56 | focus: function() { 57 | this.markdown.focus(); 58 | }, 59 | 60 | /* Return the length of the textarea. */ 61 | getLength: function() { 62 | return this.markdown.val().length; 63 | }, 64 | 65 | /* Return the text of the textarea. */ 66 | getMarkdown: function() { 67 | return this.markdown.val(); 68 | }, 69 | 70 | /* Return the name of the file. */ 71 | getName: function() { 72 | return this.nameManager.getName(); 73 | }, 74 | 75 | /* Return the content of the selection */ 76 | getSelection: function() { 77 | return this.markdown.getSelection(); 78 | }, 79 | 80 | init: function() { 81 | var t = this; // Shortcut. 82 | chrome.storage.local.get("editorInitFileEntry", function(mado) { 83 | if (mado.editorInitFileEntry) { // We are loading a file at start. 84 | chrome.fileSystem.restoreEntry( // We get the entry. 85 | mado.editorInitFileEntry, 86 | function (entry) { 87 | fileEntry = entry; // Shortcut. 88 | chrome.storage.local.remove("editorInitFileEntry"); // We remove the file that has been used. 89 | 90 | fileEntry.file( // We open the file. 91 | function(file) { 92 | var reader = new FileReader(); 93 | reader.onload = function(e) { 94 | chrome.storage.local.set({ "newFile": chrome.fileSystem.retainEntry(fileEntry), "newFilePath": fileEntry.fullPath }, function() { // We set two local variables for RecentFilesManager. 95 | t.setMarkdown(e.target.result); // We set the textarea's text. 96 | t.saveWithName(fileEntry.fullPath.substring(fileEntry.fullPath.lastIndexOf('/') + 1)); // We set the name and the save state of the window. 97 | }); 98 | }; 99 | reader.readAsText(file); // We read the file as text. 100 | }, 101 | function(error) { console.log(error); } 102 | ); 103 | } 104 | ); 105 | } else { // No file loaded, we check if it is the first launch. 106 | chrome.storage.local.get("firstLaunch", function(mado) { 107 | if (mado.firstLaunch === undefined) { // It is the first launch, the variable does not exist yet. 108 | t.setMarkdown(chrome.i18n.getMessage("msgFirstLaunch")); // We set the textarea's text. 109 | chrome.storage.local.set({ "firstLaunch" : false }); // We set the variable so that the message won't be displayed again. 110 | } 111 | }); 112 | } 113 | }); 114 | }, 115 | 116 | /* Return if we're editing a named file. */ 117 | isNamed: function() { 118 | return this.nameManager.isNamed(); 119 | }, 120 | 121 | /* Return if we're editing a samed file. */ 122 | isSaved: function() { 123 | return this.saveStateManager.isSaved(); 124 | }, 125 | 126 | /* Replace some text in the textarea. 127 | * newSelectedText: the new text. 128 | * start: start of the text modified. 129 | * end: end of the text modifierd. 130 | */ 131 | replaceSelection: function(newSelectedText, start, end) { 132 | this.markdown.setSelection(start, end); 133 | this.markdown.replaceSelectedText(newSelectedText, "select"); 134 | }, 135 | 136 | /* Set that the document is saved, it is not for real (the real save is made in App). */ 137 | save: function() { 138 | this.saveStateManager.save(); 139 | }, 140 | 141 | /* Set that the document is saved and give it a name, it is not for real (the real save is made in App). 142 | * newName: new name of the window. 143 | */ 144 | saveWithName: function(newName) { 145 | this.nameManager.set(newName); 146 | this.saveStateManager.save(); 147 | }, 148 | 149 | /* Open a window to chose the galleries that Mado can read. */ 150 | setGalleries: function() { 151 | this.imageManager.setGalleries(); 152 | }, 153 | 154 | /* Insert and optionally replace some text in the textarea. 155 | * newMarkdown: the new text. 156 | * start: start of the text modified. 157 | * end: end of the text modifierd. 158 | */ 159 | setMarkdown: function(newMarkdown, start, end) { 160 | this.markdown.val(this.markdown.val().substring(0, start) + newMarkdown + this.markdown.val().substring(end, this.markdown.val().length)); 161 | this.convert(); 162 | }, 163 | 164 | /* Set the selection in the textarea. 165 | * start: start of the selection. 166 | * end: end of the selection. 167 | */ 168 | setSelection: function(start, end) { 169 | this.markdown.setSelection(start, end); 170 | } 171 | }; 172 | -------------------------------------------------------------------------------- /js/Editor/ImageManager.js: -------------------------------------------------------------------------------- 1 | function ImageManager(editor) { 2 | /* Outlets */ 3 | this.altInput = $("#alt-input"); 4 | this.cancelImageButton = $("#cancel-image"); 5 | this.galleriesButton = $("#galleries-button"); 6 | this.imageButton = $("#image-button"); 7 | this.imageDisplayer = $("#image-insertion-displayer"); 8 | this.imageBox = $("#image-insertion-box"); 9 | this.imageBrowser = $("#browse-image"); 10 | this.insertImageButton = $("#insert-image"); 11 | 12 | /* Variables */ 13 | this.editor = editor; // The editor that we will modify. 14 | this.startSelection = undefined; // The beginning of the selection in the editor when we are writing the link. 15 | this.firstEndSelection = undefined; // The beginning of the selection in the editor when we apply the ImageManager. 16 | this.endSelection = undefined; // The end of the selection in the editor when we are writing the link. 17 | this.initialSelection = undefined; // The text selected in the editor when we apply the ImageManager. 18 | this.imageLoaded = undefined; // The path of the image selected. 19 | 20 | /* Events */ 21 | $(document).click($.proxy(function(e) { 22 | if ($(e.target).closest("#image-button").length) { // Click on the button. 23 | if(this.imageDisplayer.hasClass("hidden")) { // If hidden we reset and display the manager. 24 | this.reset(); 25 | this.display(); 26 | } else { // Manager displayed, we hide it. 27 | this.imageDisplayer.toggleClass("hidden"); 28 | } 29 | } else if (!this.imageDisplayer.hasClass("hidden") && !$(e.target).closest("#image-insertion-displayer").length) { // Click elsewhere. 30 | this.imageDisplayer.toggleClass("hidden"); 31 | } 32 | }, this)); 33 | 34 | this.insertImageButton.on("click", $.proxy(function(e){ this.apply(); }, this)); // Display the manager when clicking the button. 35 | this.imageBrowser.on("click", $.proxy(function(e){ this.chooseImage(); }, this)); // Open a standard window to choose an image. 36 | this.galleriesButton.on("click", $.proxy(function(e){ this.setGalleries(); }, this)); // Open a standard window to choose galleries. 37 | this.cancelImageButton.on("click", $.proxy(function(e){ this.cancel(); }, this)); // Cancels the link (put the old selection). 38 | 39 | this.altInput.keyup($.proxy(function(e) { 40 | switch (e.keyCode) { 41 | case 13: // The user press enter, we apply. 42 | this.apply(); 43 | break; 44 | case 27: // The user press echap, we cancel. 45 | this.cancel(); 46 | break; 47 | default: // THe user type something in an input, we update. 48 | this.update(); 49 | } 50 | }, this)); 51 | } 52 | 53 | ImageManager.prototype = { 54 | constructor: ImageManager, 55 | 56 | /* Applies the new image and closes the manager. */ 57 | apply: function() { 58 | if (this.altInput.val() === "") { // An alternative text is mandatory. 59 | this.altInput.addClass("flash"); 60 | this.altInput.focus(); // We focus the cursor on the input to add an alternative text. 61 | setTimeout($.proxy(function() { 62 | this.altInput.removeClass("flash"); 63 | }, this), 800); // The "flash" class is deleted after 0.8 seconds (the flash animation's duration). 64 | } else if (this.imageLoaded !== undefined) { // Everything looks good, we close the manager. 65 | this.imageDisplayer.addClass("hidden"); 66 | this.editor.focus(); 67 | this.editor.setSelection(this.endSelection, this.endSelection); // The caret is at the end of the image. 68 | } 69 | }, 70 | 71 | /* Cancels the manager. */ 72 | cancel: function() { 73 | this.imageDisplayer.toggleClass("hidden"); 74 | this.editor.replaceSelection(this.initialSelection, this.startSelection, this.endSelection, "select"); 75 | }, 76 | 77 | /* Lets the user choose an image through a standard window. */ 78 | chooseImage: function() { 79 | var t = this; // Shortcut. 80 | chrome.fileSystem.chooseEntry({ 81 | type: "openFile", 82 | accepts:[{ mimeTypes: ["image/*"] }] // We only accept images. 83 | }, function(entry) { 84 | if (entry) { // The user did select one entry. 85 | chrome.fileSystem.getDisplayPath(entry, function(path) { // The path is the information displayed in the markdown area. 86 | var imageName = path.substring(path.replace(/\\/g, "/").lastIndexOf('/') + 1); // We get the name of the file without all the path. 87 | t.setImageBrowser(imageName); 88 | t.imageLoaded = path.replace(/\\/g, "/"); // standardization. 89 | t.update(); 90 | t.altInput.focus(); // An alternative text is mandatory so we encourage users to add it. 91 | }); 92 | } 93 | }); 94 | }, 95 | 96 | /* Displays the manager, if a selection is made we use the text to fill inputs. */ 97 | display: function() { 98 | this.imageDisplayer.toggleClass("hidden"); 99 | var selection = this.editor.getSelection(); // Use of rangyinputs.js 100 | this.initialSelection = selection.text; 101 | this.startSelection = selection.start; 102 | this.firstEndSelection = selection.end; 103 | this.endSelection = selection.end; 104 | 105 | if (/!\[.*\]\(.*\)/.test(selection.text) && selection.text[0] == '!' && selection.text.slice(-1) == ')') { // An image in markdown syntax has been selected. 106 | if (/!\[.*\]\(.*\s+".*"\)/.test(selection.text)) { // Optional title is here. 107 | this.imageLoaded = selection.text.match(/\(.*\)/)[0].substring(2, selection.text.match(/\(.*\s+"/)[0].length - 2).replace(/\\/g, "/"); 108 | } else { 109 | this.imageLoaded = selection.text.match(/\(.*\)/)[0].substring(2, selection.text.match(/\(.*\)/)[0].length - 1).replace(/\\/g, "/"); 110 | } 111 | this.setImageBrowser(this.imageLoaded.substring(this.imageLoaded.replace(/\\/g, "/").lastIndexOf('/') + 1)); 112 | this.altInput.val(selection.text.match(/!\[.+\]/)[0].substring(2, selection.text.match(/!\[.+\]/)[0].length - 1)); 113 | } else { 114 | this.altInput.val(selection.text); // We put all the selection as the alternative value. 115 | } 116 | this.altInput.focus(); 117 | }, 118 | 119 | /* Reset the manager. */ 120 | reset: function() { 121 | this.imageBrowser.html(chrome.i18n.getMessage("msgChooseAnImage")); 122 | this.altInput.val(""); 123 | this.imageLoaded = undefined; 124 | }, 125 | 126 | /* Lets the user choose the galleries. */ 127 | setGalleries: function() { 128 | chrome.mediaGalleries.getMediaFileSystems({ interactive : "yes" }, $.proxy(function(objectProperties){ this.editor.convert(); }, this)); 129 | }, 130 | 131 | /* The length of the button is fixed so we do not want a strign that is too long. */ 132 | setImageBrowser: function(imageName) { 133 | if (imageName.length > 15) { // Too long to be beautiful. 134 | this.imageBrowser.html(imageName.substring(0, 6) + "(…)" + imageName.substring(imageName.length - 6)); 135 | } else { 136 | this.imageBrowser.html(imageName); // We keep the original string. 137 | } 138 | }, 139 | 140 | /* Input in the manager, we update the markdown */ 141 | update: function() { 142 | var image; 143 | if (this.imageLoaded) { // Image selected. 144 | image = "![" + this.altInput.val() + "](" + this.imageLoaded + ')'; 145 | } else { 146 | image = "![" + this.altInput.val() + "]()"; // Displays the alternative text because we don't have an image. 147 | } 148 | 149 | this.editor.setMarkdown(image, this.startSelection, this.endSelection); // Sets the markdown value. 150 | this.endSelection = this.startSelection + image.length; // Modifies the selection for the next update. 151 | } 152 | }; 153 | -------------------------------------------------------------------------------- /js/Topbar/RecentFilesManager.js: -------------------------------------------------------------------------------- 1 | function RecentFilesManager(app) { 2 | /* Outlets */ 3 | this.recentButton = $("#recent-button"); 4 | this.recentFilesDisplayer = $("#recent-files-displayer"); 5 | this.recentFilesContainer = $("#recent-files-container"); 6 | 7 | /* Variables */ 8 | this.app = app; 9 | this.recentFiles = []; // The recent files are in this array has a mix of id | entry. 10 | 11 | /* Events */ 12 | $(document).click($.proxy(function(e) { 13 | if ($(e.target).closest("#recent-button").length && this.recentFilesDisplayer.hasClass("hidden")) { // Click on the button with manager hidden thus we display it. 14 | this.displayRecentFiles(); 15 | this.recentFilesDisplayer.toggleClass("hidden"); 16 | } else if (!this.recentFilesDisplayer.hasClass("hidden") && !$(e.target).closest("#recent-files-container").length) { // Click elsewhere than the manager thus we hide it. 17 | this.recentFilesDisplayer.addClass("hidden"); // This is not a cancellation. 18 | } 19 | }, this)); 20 | 21 | chrome.storage.onChanged.addListener($.proxy(function(changes, namespace) { 22 | for (var key in changes) { 23 | switch (key) { 24 | case "recentFiles": 25 | this.init(); // Get all the recent files again in chrome.storage.local and display them. 26 | break; 27 | case "newFile": 28 | this.update(); // Reorganize recentFiles. 29 | break; 30 | } 31 | } 32 | }, this)); 33 | 34 | /* Initialization */ 35 | this.init(); // Get all the recent files again in chrome.storage.local and display them. 36 | } 37 | 38 | RecentFilesManager.prototype = { 39 | constructor: RecentFilesManager, 40 | 41 | /* Check if a file can be restored by Mado or remove the mfrom the recent files. 42 | * fileToCheck: the number in the array recentFiles corresponding to the file we need to check. 43 | */ 44 | checkRecentFile: function(fileToCheck) { 45 | if (fileToCheck < this.recentFiles.length) { 46 | if (this.recentFiles[fileToCheck] === undefined) { 47 | t.recentFiles.splice(fileToCheck, 1); // Remove the file from recentFile. 48 | t.checkRecentFile(fileToCheck); // Check the next file. 49 | } else { 50 | var t = this; 51 | chrome.fileSystem.isRestorable(t.recentFiles[fileToCheck].id, function(isRestorable) { // We check if it's still restorable. 52 | if (!isRestorable) { // If it's not restorable. 53 | t.recentFiles.splice(fileToCheck, 1); // Remove the file from recentFile. 54 | t.checkRecentFile(fileToCheck); // Check the next file. 55 | } else { 56 | chrome.fileSystem.restoreEntry(t.recentFiles[fileToCheck].id, function (fileToOpen) { 57 | if (!fileToOpen) { // The file is empty or deleted. 58 | t.recentFiles.splice(fileToCheck, 1); // Remove the file from recentFile. 59 | t.checkRecentFile(fileToCheck); // Check the next file. 60 | } else { 61 | t.checkRecentFile(fileToCheck + 1); // Check the next file. 62 | } 63 | }); 64 | } 65 | }); 66 | } 67 | } else { 68 | chrome.storage.local.set({ "recentFiles": this.recentFiles }); // We checked all files, we sync recentFiels with chrome.storage.local. 69 | } 70 | }, 71 | 72 | /* Display the recent files. */ 73 | displayRecentFiles: function() { 74 | var t = this; // Shortcut. 75 | this.recentFilesContainer.html(""); // Reset. 76 | 77 | for (var i = this.recentFiles.length - 1; i >= 0; i--) { // Add a line for each recent file. 78 | this.recentFilesContainer.html(this.recentFilesContainer.html() + "
  • " + this.recentFiles[i].path.substring(this.recentFiles[i].path.lastIndexOf('/') + 1) + "

  • "); 79 | } 80 | 81 | $(".recent-file").on("click", function(e) { // Even if on clicks a recent file. 82 | if (!$(e.target).closest("#delete-button-" + this.id.charAt(this.id.length-1)).length) { // It is not a click on on the delete button. 83 | var file = t.recentFiles[this.id.charAt(this.id.length-1)].id; // Get the file. 84 | chrome.fileSystem.restoreEntry(file, function (fileToOpen) { // We get the file. 85 | t.app.openFile(fileToOpen); // We open the file. 86 | t.recentFilesDisplayer.attr("class", "hidden"); // We hide the recent files manager. 87 | }); 88 | } 89 | }); 90 | $(".delete-recent-button").on("click", function() { t.removeFile(this.id.charAt(this.id.length-1)); }); // Add the event listeners for the remove buttons to really remove the file. 91 | 92 | /* Footer */ 93 | var footerHelp = document.createElement("li"); 94 | footerHelp.setAttribute("id", "recent-files-info"); 95 | if (this.recentFilesContainer.html() !== "") { // Something in the div for recent files. 96 | footerHelp.setAttribute("class", "clear-all"); 97 | $(footerHelp).html("
    " + chrome.i18n.getMessage("msgClearAll") + ""); 98 | } else { 99 | $(footerHelp).attr("class", " "); // Nothing in the div for recent files. 100 | $(footerHelp).html(chrome.i18n.getMessage("msgNoRecentDocument")); 101 | } 102 | 103 | this.recentFilesContainer[0].appendChild(footerHelp); // Add the footer to the container. 104 | $(".clear-all").on("click", function(e) { t.removeAllFiles(e); }); 105 | }, 106 | 107 | /* Initialization, we get the recent files from chrome.Storage.local and test if they are still available. */ 108 | init: function() { 109 | chrome.storage.local.get("recentFiles", $.proxy(function(mado) { 110 | if (mado.recentFiles) { 111 | this.recentFiles = mado.recentFiles; // Set the lcoal variable recentFiles. 112 | this.checkRecentFile(0); // Check the availability of the files starting with the first one. 113 | } 114 | }, this)); 115 | }, 116 | 117 | /* Remove all the recent files. */ 118 | removeAllFiles: function(event) { 119 | event.stopPropagation(); 120 | var numberofRecentFiles = this.recentFiles.length; 121 | chrome.storage.local.set({ "recentFiles": []}, function() { 122 | for (var i = numberofRecentFiles - 1; i >= 0; i--) { 123 | $("#recent-" + i).remove(); 124 | $("#recent-files-info").attr("class", " "); 125 | $("#recent-files-info").html(chrome.i18n.getMessage("msgNoRecentDocument")); // Set the footer. 126 | } 127 | }); 128 | }, 129 | 130 | /* Remove a specific file from the recent files. */ 131 | removeFile: function(file) { 132 | var t = this; 133 | $("#recent-" + file).attr("class", "recent-file deleted"); // Change the class to do a visual effect. 134 | 135 | this.recentFiles.splice(file, 1); // Remove the file from the array. 136 | chrome.storage.local.set({ "recentFiles": this.recentFiles }); // Set the new recentFiles in chrome.storage.local. 137 | 138 | setTimeout(function() { // After the visual effect. 139 | $("#recent-" + file).remove(); 140 | if ($("#recent-files-displayer .recent-file").length === 0) { // Change the footer. 141 | $("#recent-files-info").attr("class", " "); 142 | $("#recent-files-info").html("No recent document."); 143 | } 144 | }, 100); 145 | }, 146 | 147 | /* An update has been made on recentFiles, we update the manager. */ 148 | update: function() { 149 | chrome.storage.local.get(["newFile", "newFilePath"], $.proxy(function(mado) { 150 | this.recentFiles.push({"path": mado.newFilePath, "id": mado.newFile}); // Put the new one at the end of the recentFiles. 151 | for (var i = 0; i < this.recentFiles.length - 1; i++) { 152 | if (this.recentFiles[i].path == mado.newFilePath) { 153 | this.recentFiles.splice(i, 1); 154 | i--; 155 | } 156 | } 157 | 158 | while (this.recentFiles.length > 7) { // recentFiles has a maximum size of 7. 159 | this.recentFiles.shift(); 160 | } 161 | 162 | chrome.storage.local.set({ "recentFiles": this.recentFiles }); 163 | }, this)); 164 | } 165 | }; 166 | -------------------------------------------------------------------------------- /css/conversion-view-base.css: -------------------------------------------------------------------------------- 1 | /* ============== 2 | MISCELLANOUS 3 | ============== */ 4 | 5 | /* ------- 6 | Fonts 7 | ------- */ 8 | /* BARIOL (CLINIC) */ 9 | /* Bariol Regular */ 10 | @font-face { 11 | /* Typography */ 12 | font-family: Bariol; 13 | src: url('../fonts/bariol_regular.ttf') format('truetype'); 14 | } 15 | 16 | /* Bariol Bold */ 17 | @font-face { 18 | /* Typography */ 19 | font-family: Bariol; 20 | font-weight: bold; 21 | src: url('../fonts/bariol_bold.ttf') format('truetype'); 22 | } 23 | 24 | /* Bariol Light */ 25 | @font-face { 26 | /* Typography */ 27 | font-family: BariolLight; 28 | src: url('../fonts/bariol_light.ttf') format('truetype'); 29 | } 30 | 31 | /* Bariol Italic */ 32 | @font-face { 33 | /* Typography */ 34 | font-family: Bariol; 35 | font-style: italic; 36 | src: url('../fonts/bariol_regular_italic.ttf') format('truetype'); 37 | } 38 | 39 | /* Bariol Bold Italic */ 40 | @font-face { 41 | /* Typography */ 42 | font-family: Bariol; 43 | font-style: italic; 44 | font-weight: bold; 45 | src: url('../fonts/bariol_bold_italic.ttf') format('truetype'); 46 | } 47 | 48 | /* Bariol Light Italic */ 49 | @font-face { 50 | /* Typography */ 51 | font-family: BariolLight; 52 | font-style: italic; 53 | src: url('../fonts/bariol_light_italic.ttf') format('truetype'); 54 | } 55 | 56 | /* ARVO (TRAMWAY) */ 57 | /* Arvo Regular */ 58 | @font-face { 59 | /* Typography */ 60 | font-family: Arvo; 61 | src: url('../fonts/arvo-regular.ttf') format('truetype'); 62 | } 63 | 64 | /* Arvo Bold */ 65 | @font-face { 66 | /* Typography */ 67 | font-family: Arvo; 68 | font-weight: bold; 69 | src: url('../fonts/arvo-bold.ttf') format('truetype'); 70 | } 71 | 72 | /* Arvo Italic */ 73 | @font-face { 74 | /* Typography */ 75 | font-family: Arvo; 76 | font-style: italic; 77 | src: url('../fonts/arvo-italic.ttf') format('truetype'); 78 | } 79 | 80 | /* Arvo Bold Italic */ 81 | @font-face { 82 | /* Typography */ 83 | font-family: Arvo; 84 | font-style: italic; 85 | font-weight: bold; 86 | src: url('../fonts/arvo-bold-italic.ttf') format('truetype'); 87 | } 88 | 89 | #html-conversion { 90 | /* Typography */ 91 | font-size: 1em; 92 | line-height: 1.35em; 93 | } 94 | 95 | /* --------- 96 | General 97 | --------- */ 98 | #html-conversion img { max-width: 100%; } 99 | 100 | #html-conversion .nofile, 101 | #html-conversion .nofile-link { 102 | /* Misc */ 103 | cursor: pointer; 104 | } 105 | 106 | #html-conversion .nofile-visual { 107 | /* Typography */ 108 | color: #1c75ea; 109 | text-decoration: none; 110 | } 111 | 112 | #html-conversion .nofile-visual { text-decoration: underline; } 113 | 114 | #html-conversion .nofile-visual { color: #187af9; } 115 | 116 | /* ------------ 117 | Paragraphs 118 | ------------ */ 119 | #html-conversion p, 120 | #html-conversion ol, /* Organized and unorganized lists follow the same rule : a blank space at the bottom */ 121 | #html-conversion ul { 122 | /* Box-model */ 123 | padding-bottom: 0.7em; 124 | } 125 | 126 | /* -------- 127 | Titles 128 | -------- */ 129 | #html-conversion h1, 130 | #html-conversion h2, 131 | #html-conversion h3 { 132 | /* Typography */ 133 | font-family: "Segoe UI Light", "HelveticaNeue-Light", "Helvetica Neue Light", "Roboto Light", "Open Sans", sans-serif; 134 | } 135 | 136 | #html-conversion h1 { 137 | /* Box-model */ 138 | padding-top: 0.4em; 139 | 140 | /* Typography */ 141 | font-size: 3em; 142 | line-height: 1.35em; 143 | } 144 | 145 | #html-conversion h2 { 146 | /* Box-model */ 147 | padding-top: 0.3em; 148 | 149 | /* Typography */ 150 | font-size: 2em; 151 | line-height: 1.35em; 152 | } 153 | 154 | #html-conversion h3 { 155 | /* Box-model */ 156 | padding-top: 0.4em; 157 | 158 | /* Typography */ 159 | font-size: 1.25em; 160 | line-height: 1.1em; 161 | } 162 | 163 | #html-conversion h4 { 164 | /* Box-model */ 165 | padding-top: 0.7em; 166 | 167 | /* Typography */ 168 | font-size: 1em; 169 | font-weight: bold; 170 | line-height: 1.35em; 171 | } 172 | 173 | #html-conversion h5 { 174 | /* Typography */ 175 | font-size: 0.8em; 176 | font-weight: bold; 177 | line-height: 1.7em; 178 | } 179 | 180 | #html-conversion h6 { 181 | /* Typography */ 182 | font-size: 0.75em; 183 | font-weight: bold; 184 | line-height: 1.8em; 185 | } 186 | 187 | /* ------- 188 | Lists 189 | ------- */ 190 | /* UNORDERED LISTS */ 191 | #html-conversion ul > li { 192 | /* Positioning */ 193 | position: relative; 194 | 195 | /* Box-model */ 196 | padding-left: 3em; 197 | } 198 | 199 | #html-conversion ul > li:before { 200 | /* Positioning */ 201 | position: absolute; 202 | top: 0; 203 | left: 0; 204 | 205 | /* Box-model */ 206 | display: block; 207 | width: 3em; 208 | 209 | /* Typography */ 210 | font-family: 'Mado-Little-Icons-Normal'; 211 | font-size: 16px; 212 | font-style: normal; 213 | font-weight: normal; 214 | font-variant: normal; 215 | text-transform: none; 216 | speak: none; 217 | 218 | /* Misc */ 219 | content: "\e020"; /* Default bullet (disc), primarily for levels 3n + 1 */ 220 | } 221 | 222 | /* Sub-unordered lists (levels 3n + 2) */ 223 | #html-conversion > * > li > ul > li:before, /* Second level list item */ 224 | #html-conversion > * > li > * > li > * > li > * > li > ul > li:before, /* Fifth level list item */ 225 | #html-conversion > * > li > * > li > * > li > * > li > * > li > * > li > * > li > ul > li:before, /* Eighth level list item */ 226 | #html-conversion > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > ul > li:before, /* Eleventh level list item */ 227 | #html-conversion > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > ul > li:before /* Fourteenth level list item */ 228 | { content: "\e021"; } /* Second level bullet */ 229 | 230 | /* Sub-unordered lists (levels 3n) */ 231 | #html-conversion > * > li > * > li > ul > li:before, /* Third level list item */ 232 | #html-conversion > * > li > * > li > * > li > * > li > * > li > ul > li:before, /* Sixth level list item */ 233 | #html-conversion > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > ul > li:before, /* Ninth level list item */ 234 | #html-conversion > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > ul > li:before, /* Twelfth level list item */ 235 | #html-conversion > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > * > li > ul > li:before /* Fifteenth level list item */ 236 | { content: "\e022"; } /* Third level bullet (square) */ 237 | 238 | /* ORDERED LISTS */ 239 | #html-conversion ol { counter-reset: list; } 240 | 241 | #html-conversion ol > li { 242 | /* Positioning */ 243 | position: relative; 244 | 245 | /* Box-model */ 246 | padding-left: 3em; 247 | 248 | /* Misc */ 249 | counter-increment: list; 250 | } 251 | 252 | #html-conversion ol > li:before { 253 | /* Positioning */ 254 | position: absolute; 255 | top: 0; 256 | left: 0; 257 | 258 | /* Box-model */ 259 | display: block; 260 | width: 3em; 261 | 262 | /* Misc */ 263 | content: counter(list); 264 | } 265 | 266 | /* Prevent empty list items to be displayed without a height */ 267 | #html-conversion li:empty { height: 1.3em; } 268 | 269 | /* SUB-LISTS */ 270 | #html-conversion ol ol, 271 | #html-conversion ol ul, 272 | #html-conversion ul ul, 273 | #html-conversion ul ol { 274 | /* Box-model */ 275 | padding-bottom: 0; 276 | } 277 | 278 | /* ------------- 279 | 280 | ------------- */ 281 | #html-conversion blockquote { 282 | /* Positioning */ 283 | margin-bottom: 0.7em; 284 | 285 | /* Box-model */ 286 | padding: 0.7em 0 0 0.7em; 287 | 288 | /* Typography */ 289 | font-style: italic; 290 | } 291 | 292 | /* -------- 293 | Tables 294 | -------- */ 295 | #html-conversion table { margin-bottom: 0.7em; } 296 | 297 | #html-conversion td, 298 | #html-conversion th { 299 | /* Box-model */ 300 | padding: 0.5em; 301 | 302 | /* Visual */ 303 | border: 1px solid #dadada; 304 | } 305 | 306 | /* Zebra stripes for the table's body */ 307 | #html-conversion tbody tr:nth-child(2n+1) { background-color: #f0f0f0; } 308 | 309 | /* ------------------ 310 | Horizontal rules 311 | ------------------ */ 312 | #html-conversion hr { 313 | /* Positioning */ 314 | clear: both; 315 | margin: 0.7em auto 1.4em; /* No matter the size of the rule, it will be centered */ 316 | 317 | /* Box-model */ 318 | height: 1px; 319 | 320 | /* Visual */ 321 | background-color: rgba(0,0,0,.1); 322 | border: none; 323 | } 324 | 325 | /* ------ 326 | Code 327 | ------ */ 328 | /* Rules that preformatted text and code have in common */ 329 | pre, code { 330 | /* Typography */ 331 | font-family: SourceCodePro; 332 | font-size: 0.95em; 333 | color: #000; 334 | 335 | /* Visual */ 336 | background-color: #fff; 337 | border: 1px solid #e0e0e0; 338 | border-radius: 3px; 339 | } 340 | 341 | /* Only for inline code */ 342 | code { 343 | /* Box-model */ 344 | padding: 0 0.35em; 345 | } 346 | 347 | /* Preformatted text is a block */ 348 | pre { 349 | /* Positioning */ 350 | margin-bottom: 0.7em; 351 | 352 | /* Box-model */ 353 | width: 100%; 354 | padding: 0.65em; 355 | overflow-x: auto; 356 | } 357 | 358 | /* Cancel some rules for code inside a preformatted text tag to avoid nested code blocks */ 359 | pre > code { 360 | /* Box-model */ 361 | padding: 0; 362 | 363 | /* Visual */ 364 | background: transparent; 365 | border: none; 366 | border-radius: 0; 367 | } -------------------------------------------------------------------------------- /js/Editor/HelpManager.js: -------------------------------------------------------------------------------- 1 | function HelpManager() { 2 | /* Outlets */ 3 | this.help = $("#help-input"); // The input where the user writes what he wants. 4 | this.helpButton = $("#help-button"); // The help button. 5 | this.helpDisplayer = $("#help-input-displayer"); // The div that contains all the help divs. 6 | this.resultsContainer = $("#help-results-container"); // Will contain the HTML results container. 7 | 8 | /* Variables */ 9 | this.helpSubjects = [ // Subjects concerned by the help, check messages.json in _locales to get the localized values. 10 | "Headers", 11 | "Bold", 12 | "Italic", 13 | "BoldItalic", 14 | "OrderedLists", 15 | "UnorderedLists", 16 | "InlineStyleLinks", 17 | "ReferenceStyleLinks", 18 | "InlineStyleImages", 19 | "ReferenceStyleImages", 20 | "Code", 21 | "Blockquotes", 22 | "InlineHTML", 23 | "HorizontalRules", 24 | "LineBreaks", 25 | "Tables", 26 | "Question" 27 | ]; 28 | 29 | /* Events */ 30 | $(document).click($.proxy(function(e) { 31 | if ($(e.target).closest("#help-button").length && this.helpDisplayer.hasClass("hidden")) { // Click on the help button with the help input hidden. 32 | this.reset(); 33 | this.helpDisplayer.removeClass("hidden"); // Show the help input. 34 | this.help.focus(); // Focus in the help input. 35 | } else if (!this.helpDisplayer.hasClass("hidden") && !$(e.target).closest("#help-input-displayer").length) { // The user doesn't click on the help input nor help results (with help displayed). 36 | this.resultsContainer.addClass("hidden"); // Hide the results container 37 | this.helpDisplayer.addClass("hidden"); // Hide the help input. 38 | } 39 | }, this)); 40 | 41 | Mousetrap.bind(["ctrl+h"], $.proxy(function(e) { // Ctrl+h = display the help. 42 | this.helpButton.click(); 43 | return false; 44 | }, this)); 45 | 46 | this.help.keyup($.proxy(function(e){ // The user press echap, we quit the help. 47 | if (e.keyCode == 27) { 48 | this.helpButton.click(); 49 | } 50 | }, this)); 51 | this.help.on("input propertychange", $.proxy(function(e) { this.searchAnswers(); }, this)); // Launch the help when something is typed on the input. 52 | 53 | $("#result-switch-1, #result-switch-2, #result-switch-3").on("click", $.proxy(function(e) { // Makes a switch when the user clicks it. 54 | this.switch(e.target.id.substr(e.target.id.length - 1)); 55 | }, this)); 56 | } 57 | 58 | HelpManager.prototype = { 59 | constructor: HelpManager, 60 | 61 | /* Return the minimum length in the user language. */ 62 | localizedMinLength: function() { 63 | switch (chrome.i18n.getUILanguage()) { 64 | case "zh-CN": 65 | return 2; 66 | default: 67 | return 3; 68 | } 69 | }, 70 | 71 | /* Reset the help. */ 72 | reset: function() { 73 | this.help.val(""); /* Empty the input with help */ 74 | this.resetAnswerDivs(1); /* Doesn't show answers because there is nothing to search */ 75 | }, 76 | 77 | /* Reset the answers div startign at the parameter begin. 78 | * Begin: the first div to empty. 79 | */ 80 | resetAnswerDivs: function(begin) { 81 | for (var i = begin; i <= 3; i++) { // i <= 3 because we only have 3 divs with help. 82 | if ($("#answer-" + i).html() === "") { // If the help div is empty. 83 | i = 3; // End of the loop. 84 | } else { 85 | /* Reset the div. */ 86 | $("#answer-" + i).html(""); 87 | $("#result-" + i).attr("class", "result"); 88 | $("#example-" + i).html(""); 89 | } 90 | } 91 | }, 92 | 93 | /* Looks for help the user wants. */ 94 | searchAnswers: function () { 95 | if (this.help.val().length === 0) { // Nothing in the input. 96 | this.resultsContainer.attr("class", "hidden"); // Hide the results container, there is nothing in it if there is nothing written in the help input. 97 | this.resetAnswerDivs(3); 98 | } 99 | else { 100 | if (this.help.val().length < this.localizedMinLength()) { // Less than three characters in the input, we're not showing help. 101 | this.resultsContainer.attr("class", "one-result no-result"); // We only show one div to encourage the user. 102 | this.resetAnswerDivs(2); // We reset the two other divs. 103 | switch (this.localizedMinLength() - this.help.val().length) { 104 | case 2: 105 | $("#answer-1").html(chrome.i18n.getMessage("msgTwoMoreCharacters")); // Only one character, user has to give two more characters. 106 | break; 107 | case 1: 108 | $("#answer-1").html(chrome.i18n.getMessage("msgOneMoreCharacter")); // Only two characters, user has to give one more character. 109 | break; 110 | } 111 | } else { 112 | var maxAnswers = 0; // Reset the number of answers that can be diplayed (max: 3) 113 | var loc = chrome.i18n; // Shortcut for the localized strings. 114 | var arr = this.helpSubjects; // Shortcut for the array of subjects. 115 | for (var i = 0; i < arr.length && maxAnswers < 3; i++) { // A line = a syntax, this loop runs through each line. 116 | var j = 0; 117 | var wordSearched = "help" + arr[i]; // Get the subhect of help. 118 | while (loc.getMessage(wordSearched + j) !== "") { // A subject can have different words describing it, we are checking each of them. 119 | if (loc.getMessage(wordSearched + j).toLowerCase().indexOf(this.help.val().toLowerCase()) > -1) { // Everything in lower case to help the condition. 120 | var term = loc.getMessage(wordSearched + j); // What is searched. 121 | var result = loc.getMessage(wordSearched + "Result"); // The result of the search. 122 | var example = loc.getMessage(wordSearched + "Example"); // The example of the search. 123 | var wordPos = loc.getMessage(wordSearched + j).toLowerCase().indexOf(this.help.val().toLowerCase()); 124 | $("#answer-" + (maxAnswers + 1)).html("

    " + term.substring(0, wordPos) + "" + term.substr(wordPos, this.help.val().length) + "" + term.substring(wordPos + this.help.val().length) + "

    " + result); // Put the answer in the appropriate div. 125 | document.getElementById("example-" + (maxAnswers + 1)).innerHTML = example; // Put the answer in the appropriate div. 126 | maxAnswers++; // We display 3 answers, e.g. if the user types "bol" we display the results for "bold" and "bold italic". 127 | break; 128 | } 129 | j++; // We don't want to display many times the same thing if a subject has different words describing it. 130 | } 131 | } 132 | switch (maxAnswers) { 133 | case 0: // Nothing found. 134 | $("#answer-1").html(chrome.i18n.getMessage("msgNoHelpFound")); // Show that we have not find an answer. 135 | this.resultsContainer.attr("class", "one-result no-result"); // We only show one resuult. 136 | this.resetAnswerDivs(2); // We hide the two other divs. 137 | break; 138 | case 1: // One answer found. 139 | this.resultsContainer.attr("class", "one-result"); 140 | this.resetAnswerDivs(2); 141 | break; 142 | case 2: // Two answers found. 143 | this.resultsContainer.attr("class", "two-results"); 144 | this.resetAnswerDivs(3); 145 | break; 146 | case 3: // Three answers found, maximum number possible at the same time. 147 | this.resultsContainer.attr("class", "three-results"); 148 | break; 149 | } 150 | } 151 | } 152 | this.setResultsHeight(); // Depending on the answer, the size of each div will change. 153 | }, 154 | 155 | /* Change the height of each answer div depending on the content. */ 156 | setResultsHeight: function() { 157 | var totalHeight = 0; 158 | for (var i = 1; i <= 3; i++) {// Check all the results, depending on the number of results 159 | if ($("#answer-" + i).html() !== "") { 160 | $("#result-" + i).css("display", "block"); 161 | if ($("#answer-" + i).outerHeight() >= $("#example-" + i).outerHeight()) { // The eight of the answer is bigger than the example. 162 | $("#result-" + i).css("height", $("#answer-" + i).outerHeight() + "px"); // Set the div's height as the answer's height. 163 | } else { 164 | $("#result-" + i).css("height", $("#example-" + i).outerHeight() + "px"); 165 | } 166 | totalHeight += $("#result-" + i).outerHeight(); // Add the height of the current result to the total height 167 | } 168 | else { 169 | $("#result-" + i).css("height", 0); 170 | $("#result-" + i).css("display", "none"); 171 | } 172 | } 173 | this.resultsContainer.css("height", totalHeight + "px"); // Set the resultContainer's height to be the total height. 174 | }, 175 | 176 | /* Switch between example and result */ 177 | switch: function(resultNumber) { 178 | $("#result-" + resultNumber).toggleClass("switched"); 179 | this.help.focus(); 180 | } 181 | }; 182 | -------------------------------------------------------------------------------- /js/Editor/DisplayManager.js: -------------------------------------------------------------------------------- 1 | function DisplayManager(editor) { 2 | /* Outlets */ 3 | this.conversionDiv = $("#html-conversion"); 4 | 5 | /* Variables */ 6 | this.currentGallery = undefined; // Gallery currently visited by getImages(); 7 | this.editor = editor; // The editor. 8 | this.galleries = []; // Galleries where we can read images. 9 | this.imagesDisplayed = new ImageArray(); // Object containing opened images. 10 | this.imgFormats = ["png", "bmp", "jpeg", "jpg", "gif", "png", "svg", "xbm", "webp"]; // Authorized images' type. 11 | this.loadedImagePath = undefined; // Path of the image found. 12 | this.imagePosition = undefined; // Help us to find all the images in a file. 13 | this.styleManager = new StyleManager(); // Object to manage the styles of the conversionDiv. 14 | this.tempConversion = undefined; // Temporary conversion before it is displayed in the conversionDiv. 15 | this.urlManager = new UrlManager(this.conversionDiv); // Managing what to do when user clicks a link. 16 | 17 | /* Events */ 18 | chrome.storage.onChanged.addListener($.proxy(function (changes, namespace) { 19 | for (var key in changes) { 20 | switch (key) { 21 | case "gfm": 22 | this.setSyntax(); // Set the syntax if it has been changed in the Settings window. 23 | break; 24 | } 25 | } 26 | }, this)); 27 | 28 | /* Initialization */ 29 | this.setSyntax(); // Set the syntax . 30 | } 31 | 32 | DisplayManager.prototype = { 33 | constructor : DisplayManager, 34 | 35 | /* Finds images in the document and convert it because Chrome cannot directly access images. */ 36 | displayImages: function() { 37 | if (this.tempConversion.indexOf(" -1) { 38 | this.imagePosition = this.tempConversion.indexOf(" " + chrome.i18n.getMessage("msgNoInternet") + "  -1) { 52 | this.getOfflineImage(); 53 | } else if (this.loadedImagePath.substring(0, 5) != "data:" && this.loadedImagePath.substring(0, 5) != "blob:") { 54 | this.tempConversion = this.tempConversion.substring(0, this.imagePosition - 10) + " " + chrome.i18n.getMessage("msgNotAnImage") + "  " + this.loadedImagePath.replace(/\\/g, "/").substring(this.loadedImagePath.lastIndexOf('/') + 1) + ' ' + chrome.i18n.getMessage("msgNotFound") + "  -1) { 139 | var webImage = window.URL.createObjectURL(xhr.response); 140 | this.imagesDisplayed.addImage(this.loadedImagePath, webImage); // Add a new line. 141 | this.tempConversion = this.tempConversion.substring(0, this.imagePosition) + webImage + this.tempConversion.substring(this.imagePosition + this.loadedImagePath.length); // Replace the path. 142 | } else { 143 | this.tempConversion = this.tempConversion.substring(0, this.imagePosition - 10) + " " + chrome.i18n.getMessage("msgNotAnImage") + "  0) { // There is Markdown in the textarea. 166 | this.tempConversion = marked(this.editor.getMarkdown()); // Get the new conversion. 167 | this.editor.checkHeight(); // Check the scroll. 168 | this.displayImages(); // We will finish displaying it after displaying every images correctly. 169 | } else { // No Markdown here. 170 | this.conversionDiv.html(chrome.i18n.getMessage("msgNoTextInEditor")); // Display the message when there is no text. 171 | } 172 | } 173 | }; 174 | --------------------------------------------------------------------------------