├── assets ├── js │ ├── custom.js │ └── mavo-tinymce.js └── css │ ├── main.css │ ├── extra.css │ └── layout.css ├── README.md ├── LICENSE ├── .gitignore └── index.html /assets/js/custom.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | document.body.addEventListener('click', clickListener) 3 | 4 | function clickListener (event) { 5 | // Save when note is collapsed. 6 | if (event.target && event.target.className.indexOf('note-expand-checkbox') > -1 && !event.target.checked) { 7 | const saveBtn = document.querySelector('.mv-save') 8 | saveBtn.click() 9 | } 10 | } 11 | })(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # boxdash-ui / git-keep 2 | 3 | This project aims to build basic note dashboard functionality similar to GoogleKeep. 4 | 5 | ## Getting started 6 | 7 | Run with: 8 | ``` 9 | python3 -m http.server 10 | ``` 11 | 12 | 13 | ## Features 14 | [x] responsive 15 | [x] background coloring 16 | [x] close to save 17 | [ ] searching / filtering / tagging 18 | [ ] light/dark mode 19 | [ ] edit all mode? instead of one at a time 20 | [ ] other note hotkeys(enter to add new, up /down arrow keys) 21 | [ ] expand whole note on click 22 | [ ] mode to edit multiple at once 23 | [ ] refresh data button 24 | [ ] redo save animation 25 | 26 | ## Bugs 27 | [ ] fix up/down arrows in textboxes, 28 | [ ] fix pasting dynamic html) 29 | 30 | ### Skipped features (for now) 31 | no reminders 32 | no pics 33 | 34 | ### Roadmap 35 | Edit 2 (or more) notes at once and drag between them? 36 | offline support? 37 | gestures support? 38 | more masonry like? 39 | 40 | ## Code Snippets 41 | 42 | Fetch Data 43 | ```js 44 | Mavo.all.todo.primaryBackend.get() 45 | ``` 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tim Scanlin 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/css/main.css: -------------------------------------------------------------------------------- 1 | /* General styles */ 2 | .tint-bg { 3 | background-color: rgba(10, 10, 10, 0.7); 4 | } 5 | .transition-300 { 6 | transition: all 300ms ease-in-out; 7 | } 8 | .transition-transform { 9 | transition: transform 300ms ease-in-out; 10 | } 11 | /* Remove button styling */ 12 | .button-link { 13 | appearance: none; 14 | background: transparent; 15 | border: 0; 16 | margin: 2px 0; 17 | padding: 2px 0; 18 | cursor: pointer; 19 | } 20 | 21 | /* Color styles */ 22 | .cbg-black { 23 | background-color: #202125; 24 | color: white; 25 | } 26 | .cbg-red { 27 | background-color: #5c2b27; 28 | color: white; 29 | } 30 | .cbg-orange { 31 | background-color: #614a18; 32 | color: white; 33 | } 34 | .cbg-yellow { 35 | background-color: #635d18; 36 | color: white; 37 | } 38 | .cbg-green { 39 | background-color: #345921; 40 | color: white; 41 | } 42 | .cbg-teal { 43 | background-color: #16504c; 44 | color: white; 45 | } 46 | .cbg-blue { 47 | background-color: #2d555f; 48 | color: white; 49 | } 50 | .cbg-dark-blue { 51 | background-color: #1e3a5c; 52 | color: white; 53 | } 54 | .cbg-purple { 55 | background-color: #42275c; 56 | color: white; 57 | } 58 | .cbg-pink { 59 | background-color: #5b2242; 60 | color: white; 61 | } 62 | .cbg-gray { 63 | background-color: #3c3f42; 64 | color: white; 65 | } 66 | 67 | 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /assets/css/extra.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /* Misc styles */ 5 | .notes-wrapper .mv-ui.mv-add { 6 | background-color: rgba(0, 0, 0, 0.2); 7 | /* background-color: rgba(251, 253, 255, 0.3); */ 8 | border-width: 1px; 9 | border-color: inherit; 10 | border-style: solid; 11 | border-radius: 2px; 12 | padding: 2px 8px; 13 | margin-top: 8px; 14 | color: inherit; 15 | } 16 | /* Hide add note button since it cannot be styled easily */ 17 | .mv-ui.mv-add.mv-add-notes { 18 | display: none; 19 | } 20 | /* .line-clamp { 21 | display: -webkit-box; 22 | -webkit-line-clamp: 3; 23 | -webkit-box-orient: vertical; 24 | overflow: hidden; 25 | } */ 26 | 27 | /* Custom buttons for list */ 28 | .listItem-wrapper { 29 | width: 100%; 30 | width: calc(100% - 3.2em); 31 | margin: auto; 32 | } 33 | .mv-item-bar.mv-ui[mv-rel="listItem"] { 34 | width: 100%; 35 | top: -1px; 36 | } 37 | .mv-item-bar.mv-ui[mv-rel="listItem"] button { 38 | padding: .1em .2em; 39 | min-width: 1em; 40 | min-height: 1em; 41 | border: 1px solid rgba(0, 0, 0, 0.1); 42 | font-size: 100%; 43 | background: none; 44 | border-radius: 0; 45 | border: 0; 46 | } 47 | .mv-item-bar.mv-ui button:not(:hover):not(:focus) { 48 | /* box-shadow: none; */ 49 | } 50 | .mv-item-bar.mv-ui[mv-rel="listItem"] .mv-add { 51 | display: none; 52 | } 53 | .mv-item-bar.mv-ui[mv-rel="listItem"] .mv-drag-handle { 54 | left: 0; 55 | position: absolute; 56 | color: inherit 57 | } 58 | .mv-item-bar.mv-ui[mv-rel="listItem"] .mv-drag-handle::before { 59 | color: inherit; 60 | transform: scale(1.1) translate(-50%, -56%) scaleY(2); 61 | position: absolute; 62 | top: 50%; 63 | left: 50%; 64 | } 65 | .mv-item-bar.mv-ui[mv-rel="listItem"] .mv-delete { 66 | right: 0; 67 | position: absolute; 68 | } 69 | .mv-item-bar.mv-ui[mv-rel="listItem"][hidden] { 70 | opacity: 1; 71 | pointer-events: initial; 72 | } 73 | .mv-item-bar.mv-ui[mv-rel="listItem"] .mv-drag-handle:hover, 74 | .mv-item-bar.mv-ui[mv-rel="listItem"] .mv-drag-handle:focus { 75 | background: transparent; 76 | } 77 | 78 | /* Mv-bar customizations */ 79 | .mv-bar.mv-ui .mv-status { 80 | font-size: 0.75em; 81 | } 82 | .mv-bar.mv-ui > button::before, .mv-bar.mv-ui > .mv-button::before { 83 | vertical-align: 0em; 84 | } 85 | .mv-bar.mv-ui .mv-logout::before { 86 | position: relative; 87 | top: 2px; 88 | } 89 | .mv-bar.mv-ui .ui-settings::before { 90 | content: "⚙"; 91 | } -------------------------------------------------------------------------------- /assets/js/mavo-tinymce.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | var parser, serializer; 4 | 5 | Mavo.Plugins.register("tinymce", { 6 | ready: $.include(self.tinymce, "https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.9.11/tinymce.min.js").then(() => { 7 | parser = new tinymce.html.DomParser(); 8 | serializer = new tinymce.html.Serializer(); 9 | }) 10 | }); 11 | 12 | Mavo.Elements.register(".tinymce", { 13 | hasChildren: true, 14 | default: true, 15 | edit: function() { 16 | (this.preEdit || Promise.resolve()).then(v => { 17 | if (v === "abort") { 18 | return; 19 | } 20 | 21 | if (this.element.tinymce) { 22 | // Previously edited, we already have an editor 23 | tinymce.EditorManager.execCommand("mceAddEditor", true, this.element.tinymce.id); 24 | return; 25 | } 26 | 27 | const toolbar = this.element.getAttribute("mv-tinymce-toolbar") 28 | 29 | // Init for the first time 30 | tinymce.init({ 31 | target: this.element, 32 | inline: true, 33 | menubar: false, 34 | toolbar: toolbar || "styleselect | bold italic | image link | table | bullist numlist", 35 | plugins: "image code link table lists media tabfocus" 36 | }).then(editors => { 37 | this.element.tinymce = editors[0]; 38 | 39 | this.element.tinymce.on("change keyup paste cut", evt => { 40 | this.value = this.getValue(); 41 | }); 42 | }); 43 | }); 44 | }, 45 | done: function() { 46 | if (this.element.tinymce) { 47 | tinymce.EditorManager.execCommand("mceRemoveEditor", true, this.element.tinymce.id); 48 | } 49 | }, 50 | getValue: (element) => { 51 | return element.tinymce ? element.tinymce.getContent() : element.innerHTML; 52 | }, 53 | setValue: (element, value) => { 54 | const content = serializer.serialize(parser.parse(value)); 55 | 56 | // console.log('@isHidden', element.tinymce,element.tinymce && element.tinymce.isHidden()) 57 | // console.log('@notDirty',element.tinymce && element.tinymce.isNotDirty, element.tinymce && element.tinymce.isDirty()) 58 | 59 | if (!element.tinymce) { 60 | // console.log('hi') 61 | element.innerHTML = content; 62 | } 63 | // This second part is needed because sometimes they can be in a dirty state. 64 | else if (element.tinymce.isHidden() || (!element.tinymce.isHidden() && element.tinymce.isNotDirty)) { 65 | // console.log('hi2') 66 | element.tinymce.setContent(content); 67 | } else { 68 | // console.log('hi3') 69 | } 70 | } 71 | }); 72 | 73 | })(Bliss); 74 | -------------------------------------------------------------------------------- /assets/css/layout.css: -------------------------------------------------------------------------------- 1 | /* Layout styles */ 2 | .notes-wrapper { 3 | display: flex; 4 | flex-flow: column wrap; 5 | align-content: space-between; 6 | /* Your container needs a fixed height, and it 7 | * needs to be taller than your tallest column. */ 8 | height: 960px; 9 | counter-reset: items; 10 | } 11 | .mv-bar.mv-ui { 12 | top: 0; 13 | border-radius: 0; 14 | } 15 | .mv-bar.mv-ui:not(.mv-fixed) { 16 | border-radius: 0; 17 | /* margi */ 18 | } 19 | 20 | /* Note styles */ 21 | .note-container { 22 | width: 48.5%; 23 | /* Optional */ 24 | position: relative; 25 | box-sizing: border-box; 26 | flex-grow: 0; 27 | flex-shrink: 0; 28 | /* flex-basis: 25%; */ 29 | } 30 | .mce-tinymce.mce-container { 31 | display: none; 32 | } 33 | .listItem .tinymce p { 34 | margin: 0; 35 | } 36 | 37 | /* Just to print out numbers */ 38 | div.note-container::before { 39 | counter-increment: items; 40 | content: counter(items); 41 | font-size: 0.5em; 42 | position: absolute; 43 | right: 0; 44 | top: 0; 45 | padding: 2px; 46 | opacity: 0.3; 47 | } 48 | 49 | /* Fancy flex column stuff */ 50 | .note-container:nth-child(2n+1) { order: 1; } 51 | .note-container:nth-child(2n) { order: 2; } 52 | @media (min-width: 900px) { 53 | .note-container:nth-child(4n+1) { order: 1; } 54 | .note-container:nth-child(4n+2) { order: 2; } 55 | .note-container:nth-child(4n+3) { order: 3; } 56 | .note-container:nth-child(4n) { order: 4; } 57 | 58 | .note-container { 59 | width: 24%; 60 | } 61 | } 62 | /* Force new columns */ 63 | .break.note-container { 64 | flex-basis: 100%; 65 | width: 0; 66 | margin: 0; 67 | content: ""; 68 | padding: 0; 69 | } 70 | 71 | /* Note expanding */ 72 | .note-expand-checkbox:checked ~ .note-content { 73 | width: 80%; 74 | height: 80%; 75 | /* transform: translate(calc(40vw - 20rem),0px); */ 76 | /* transform: translate(, 0, 0); */ 77 | position: fixed; 78 | top: 3em; 79 | left: 10%; 80 | z-index: 11; 81 | } 82 | /* Truncate the title so the list doesn't get too long */ 83 | .note-expand-checkbox:not(:checked) ~ .note-content .itemTitle { 84 | display: -webkit-box; 85 | -webkit-line-clamp: 3; 86 | -webkit-box-orient: vertical; 87 | overflow: hidden; 88 | float: right; 89 | } 90 | /* Hide extra items so the list doesn't get too long */ 91 | .note-expand-checkbox:not(:checked) ~ .note-content .listItem:nth-child(n + 4) { 92 | display: none !important; 93 | } 94 | .note-expand-checkbox:checked { 95 | display: block !important; 96 | opacity: 0; 97 | } 98 | .note-expand-checkbox:checked::after { 99 | display: block !important; 100 | position: fixed; 101 | top: 0; 102 | left: 0; 103 | content: ''; 104 | width: 100vw; 105 | height: 100vh; 106 | } 107 | .note-expand-checkbox:checked ~ .mv-item-bar { 108 | display: none; 109 | } 110 | .note-expand-checkbox:checked ~ .note-content { 111 | /* z-index: 11; */ 112 | } 113 | 114 | /* Note dropdown styles */ 115 | .note-dropdown-background-checkbox:checked ~ .note-dropdown-background-container { 116 | display: block !important; 117 | } 118 | /* backdrop to click out of dropdown */ 119 | .note-dropdown-background-checkbox:checked ~ .note-dropdown-background-label::after { 120 | display: block !important; 121 | position: fixed; 122 | top: 0; 123 | left: 0; 124 | content: ''; 125 | width: 100vw; 126 | height: 100vh; 127 | } 128 | 129 | .note-dropdown-options-checkbox:checked ~ .note-dropdown-options-container { 130 | display: block !important; 131 | } 132 | /* backdrop to click out of dropdown */ 133 | .note-dropdown-options-checkbox:checked ~ .note-dropdown-options-label::after { 134 | display: block !important; 135 | position: fixed; 136 | top: 0; 137 | left: 0; 138 | content: ''; 139 | width: 100vh; 140 | height: 100vh; 141 | } 142 | 143 | /* Loading styles */ 144 | [mv-app][mv-progress="Saving"] { 145 | /* opacity: 0; */ 146 | } 147 | [mv-app][mv-progress="Saving"] .mv-bar.mv-ui .mv-save::before { 148 | opacity: .1; 149 | } 150 | [mv-app][mv-progress="Saving"] .mv-bar.mv-ui .mv-save::after { 151 | /* content: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20viewBox%3D%220%200%20300%20100%22%3E%3Cstyle%3E%40keyframes%20grow%20{ 33.3%25%20{stroke-width%3A%2050px} from%2C%2066.6%25%2C%20to%20{%20stroke-width%3A%200}}circle%20{ animation%3A%20grow%20.6s%20infinite%20both%3B fill%3A%20white%3B stroke%3A%20white%3B}%3C%2Fstyle%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2222%22%20%2F%3E%3Ccircle%20cx%3D%22150%22%20cy%3D%2250%22%20r%3D%2222%22%20style%3D%22animation-delay%3A%20.2s%22%20%2F%3E%3Ccircle%20cx%3D%22250%22%20cy%3D%2250%22%20r%3D%2222%22%20style%3D%22animation-delay%3A%20.4s%22%20%2F%3E%3C%2Fsvg%3E"); */ 152 | /* content: 'hello' !important; */ 153 | position: absolute; 154 | top: .5em; 155 | right: .5em; 156 | bottom: .5em; 157 | left: .5em; 158 | } 159 | [mv-progress]::after { 160 | /* Reset loading styles */ 161 | background-image: none; 162 | background-color: red; 163 | content: ''; 164 | backdrop-filter: none; 165 | position: absolute; 166 | transform: none; 167 | top: 0; 168 | left: 0; 169 | bottom: auto; 170 | width: 100%; 171 | height: 1px !important; 172 | line-height: 0; 173 | padding-top: 0 !important; 174 | padding: 0 !important; 175 | margin: 0; 176 | border: 0; 177 | border-radius: 0; 178 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |[count(itemDone)] done out of [count(listItem)] total
28 | 29 | 30 | 31 | 32 |