├── .gitignore ├── Data ├── PROJECT-0001 Some Neat Project.md └── PROJECT-0002 Some Other Neat Project.md ├── Examples.md ├── Files └── screenshot.png ├── Templates └── Project Template.md ├── Views ├── bookmarks │ ├── view.css │ └── view.js ├── hello-world │ ├── view.css │ └── view.js ├── log-cards │ ├── view.css │ └── view.js ├── project-cards │ ├── view.css │ └── view.js └── project-header │ ├── view.css │ └── view.js └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .obsidian/* 2 | !.obsidian/dataviews 3 | -------------------------------------------------------------------------------- /Data/PROJECT-0001 Some Neat Project.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: project 3 | title: Some Neat Project 4 | status: today 5 | code: PROJECT-0001 6 | notes: 7 | '2021-12-21 14:00': In progress. 8 | links: 9 | 'Thread': https://www.google.com 10 | 'Google Doc': https://www.google.com 11 | --- 12 | 13 | ```dataviewjs 14 | dv.view('project-header'); 15 | ``` 16 | 17 | I'm a project! -------------------------------------------------------------------------------- /Data/PROJECT-0002 Some Other Neat Project.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: project 3 | title: Some Other Neat Project 4 | status: todo 5 | code: PROJECT-0002 6 | notes: 7 | '2021-12-22': Waiting for feedback. 8 | links: 9 | 'Google Sheet': https://www.google.com 10 | 'GitHub': https://www.google.com 11 | --- 12 | 13 | Whale hello there! -------------------------------------------------------------------------------- /Examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | --- 4 | 5 | ## Hello World 6 | 7 | A simple view: 8 | 9 | ```dataviewjs 10 | 11 | let projects = dv.pages().where( p => p.type === 'project' ).sort( p => p.title ); 12 | 13 | dv.view( 'hello-world', projects ); 14 | 15 | ``` 16 | 17 | --- 18 | 19 | ### Project Cards 20 | 21 | A more complex view: 22 | 23 | ```dataviewjs 24 | 25 | let projects = dv.pages().where( p => p.type === 'project' ); 26 | 27 | dv.view( 'project-cards', { projects: projects, order: 'asc' } ); 28 | 29 | ``` 30 | -------------------------------------------------------------------------------- /Files/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaelri/obsidian-test-vault/3acae33c56a76a02449bb3591c54ee03d5ed1cb4/Files/screenshot.png -------------------------------------------------------------------------------- /Templates/Project Template.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | status: todo 4 | code: 5 | notes: 6 | '{{date:YYYY-MM-DD}}': 7 | links: 8 | Link: 9 | --- 10 | -------------------------------------------------------------------------------- /Views/bookmarks/view.css: -------------------------------------------------------------------------------- 1 | .bookmarks { 2 | /* padding-left: 2em; */ 3 | } 4 | 5 | .bookmark { 6 | display: block; 7 | margin: .5rem 0; 8 | color: inherit; 9 | padding: .5rem; 10 | /* border: 1px solid #444; */ 11 | position: relative; 12 | overflow: hidden; 13 | background: hsla(0, 0%, 0%, .1); 14 | } 15 | 16 | .bookmark, 17 | .bookmark:hover, 18 | .markdown-preview-view .bookmark, 19 | .markdown-preview-view .bookmark:hover { 20 | text-decoration: none; 21 | } 22 | 23 | .bookmark-text { 24 | /* font-weight: bold; */ 25 | /* font-size: larger; */ 26 | } 27 | 28 | .bookmark-note { 29 | display: block; 30 | width: 100%; 31 | white-space: nowrap; 32 | overflow: hidden; 33 | text-overflow: ellipsis; 34 | font-size: smaller; 35 | } 36 | 37 | .bookmark-domain { 38 | display: block; 39 | width: 100%; 40 | white-space: nowrap; 41 | overflow: hidden; 42 | text-overflow: ellipsis; 43 | font-size: smaller; 44 | color: #666; 45 | } 46 | -------------------------------------------------------------------------------- /Views/bookmarks/view.js: -------------------------------------------------------------------------------- 1 | const bookmarks = input; 2 | 3 | // RENDER 4 | let html = `
`; 5 | 6 | for (let i = 0; i < bookmarks.length; i++) { 7 | 8 | const bookmark = bookmarks[i]; 9 | 10 | const url = String( bookmark['url'] || bookmark[1] || '' ); 11 | const text = String( bookmark['text'] || bookmark[0] || url ); 12 | const note = String( bookmark['note'] || bookmark[2] || '' ); 13 | 14 | if ( !url.length ) continue; 15 | 16 | html += ``; 17 | html += `${text}`; 18 | 19 | // Parse domain 20 | // https://stackoverflow.com/questions/8498592/extract-hostname-name-from-string 21 | const matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i); 22 | const domain = matches && matches[1]; // domain will be null if no match is found 23 | html += `${domain}`; 24 | 25 | if ( note.length ) { 26 | html += `${note}`; 27 | } 28 | 29 | html += ``; 30 | 31 | } 32 | 33 | html += `
`; 34 | 35 | return html; 36 | -------------------------------------------------------------------------------- /Views/hello-world/view.css: -------------------------------------------------------------------------------- 1 | .view-hello-world { 2 | list-style: none; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | .view-hello-world li { 8 | display: block; 9 | margin: 1em 0; 10 | border: 1px solid #444; 11 | padding: .5em; 12 | max-width: 40em; 13 | } 14 | -------------------------------------------------------------------------------- /Views/hello-world/view.js: -------------------------------------------------------------------------------- 1 | let projects = input; 2 | 3 | let html = ``; 13 | 14 | return html; -------------------------------------------------------------------------------- /Views/log-cards/view.css: -------------------------------------------------------------------------------- 1 | .log-cards { 2 | margin: 2em 0; 3 | } 4 | 5 | .markdown-preview-view .log-cards .internal-link { 6 | text-decoration: none; 7 | } 8 | 9 | .log-card { 10 | margin: .5em 0; 11 | background: hsla(0, 0%, 0%, .1); 12 | padding: .5em; 13 | position: relative; 14 | /* max-width: 40em; */ 15 | } 16 | 17 | .log-card-link, 18 | .log-card-link:hover, 19 | .markdown-preview-view .log-card-link, 20 | .markdown-preview-view .log-card-link:hover { 21 | display: block; 22 | color: inherit; 23 | text-decoration: none; 24 | } 25 | 26 | .log-card .log-card-title { 27 | margin: 0; 28 | font-size: 1rem; 29 | } 30 | 31 | .project-card-title a { 32 | color: inherit; 33 | } 34 | 35 | .log-card-meta { 36 | font-size: smaller; 37 | color: #999; 38 | line-height: 1.5; 39 | margin-top: .25em; 40 | } 41 | -------------------------------------------------------------------------------- /Views/log-cards/view.js: -------------------------------------------------------------------------------- 1 | let pages = input; 2 | 3 | // RENDER 4 | let html = `
`; 5 | 6 | for (let i = 0; i < pages.length; i++) { 7 | const page = pages[i]; 8 | 9 | html += `
`; 10 | 11 | let date = moment(page.file.name); 12 | let title = date.format('D MMMM, YYYY'); 13 | let subtitle = date.format('dddd'); 14 | 15 | html += `

${title}

`; 16 | html += `
${subtitle}
`; 17 | 18 | html += `
`; 19 | 20 | } 21 | 22 | html += `
`; 23 | 24 | return html; 25 | -------------------------------------------------------------------------------- /Views/project-cards/view.css: -------------------------------------------------------------------------------- 1 | .project-cards { 2 | margin: 2em 0; 3 | } 4 | 5 | .markdown-preview-view .project-card .internal-link, 6 | .markdown-preview-view .project-card .internal-link:hover { 7 | text-decoration: none; 8 | } 9 | 10 | .project-card { 11 | margin: .5em 0; 12 | /* border: 1px solid #444; */ 13 | background: hsla(0, 0%, 0%, .1); 14 | padding: .5em .5em .5em 2em; 15 | position: relative; 16 | /* max-width: 40em; */ 17 | border-radius: .25rem; 18 | } 19 | 20 | /* Display priority tags as colored circle badges instead of text. */ 21 | .project-card-status { 22 | display: block; 23 | position: absolute; 24 | top: .75em; 25 | left: .5em; 26 | color: transparent; 27 | content: ''; 28 | width: 1em; 29 | height: 1em; 30 | border-radius: 50%; 31 | margin-right: .25em; 32 | } 33 | 34 | .project-card-status[data-status="today"] { 35 | background: #d9534f; 36 | } 37 | 38 | .project-card-status[data-status="todo"] { 39 | background: #0275d8; 40 | } 41 | 42 | .project-card-status[data-status="done"] { 43 | background: #5cb85c; 44 | } 45 | 46 | .project-card-status[data-status="cont"] { 47 | background: #5bc0de; 48 | } 49 | 50 | .project-card-status[data-status="future"], 51 | .project-card-status[data-status="wait"] { 52 | background: #444; 53 | } 54 | 55 | .project-card-status[data-status="important"], 56 | .project-card-status[data-status="now"] { 57 | background: #ffd946; 58 | } 59 | 60 | .project-card .project-card-title { 61 | margin: 0; 62 | font-size: 1rem; 63 | } 64 | 65 | .project-card-title a { 66 | color: inherit; 67 | text-decoration: none; 68 | } 69 | 70 | .project-card-meta, 71 | .project-card-note { 72 | font-size: smaller; 73 | color: #999; 74 | line-height: 1.5; 75 | margin-top: .25em; 76 | } 77 | 78 | .project-card-link { 79 | display: inline-block; 80 | color: inherit; 81 | line-height: 1; 82 | padding: 4px; 83 | margin-right: .5em; 84 | margin-bottom: .5em; 85 | border: 1px solid #444; 86 | text-decoration: none; 87 | } 88 | 89 | .project-card-note-date { 90 | margin-right: .5em; 91 | color: white; 92 | } 93 | 94 | .project-card-note-text { 95 | color: #999; 96 | } 97 | 98 | .project-card-sep { 99 | color: #666; 100 | } -------------------------------------------------------------------------------- /Views/project-cards/view.js: -------------------------------------------------------------------------------- 1 | let projects = input.projects; 2 | let order = input.order || 'asc'; 3 | 4 | // SORT 5 | projects = projects.sort( project => { 6 | 7 | // DATE 8 | let date = moment( ( project.notes && Object.keys(project.notes).length ) ? Object.keys(project.notes)[0] : null ).unix(); 9 | 10 | // PRIORITY 11 | let priority = project.priority || 9; 12 | 13 | // TITLE 14 | let title = project.title || project.file.name; 15 | 16 | return `${date}-${priority}-${title}`; // default 17 | 18 | }, order); 19 | 20 | // RENDER 21 | let html = `
`; 22 | 23 | for (let i = 0; i < projects.length; i++) { 24 | 25 | const project = projects[i]; 26 | 27 | // Jump ahead to get the most relevant date. 28 | let now = moment(); 29 | 30 | if ( project.status == 'todo' && project.notes && Object.keys(project.notes).length ) { 31 | projectTimestamp = Object.keys(project.notes)[0]; 32 | let projectDate = moment( projectTimestamp ); 33 | 34 | if ( projectDate.format('YYYY MM DD') == now.format('YYYY MM DD') || projectDate.unix() <= now.unix() ) { 35 | project.status = 'today'; 36 | } 37 | } 38 | 39 | html += `
`; 40 | 41 | // ICON 42 | if ( project.status ) html += ` `; 43 | 44 | // TITLE 45 | let title = project.title || project.file.name; 46 | html += `

${title}

`; 47 | 48 | // CODE 49 | html += `
`; 50 | 51 | if ( project.code ) html += `${project.code}`; 52 | 53 | html += '
'; 54 | 55 | // NOTES 56 | if ( project.notes && Object.keys(project.notes).length ) { for (let l = 0; l < Object.keys(project.notes).length; l++) { 57 | const noteTimestamp = Object.keys(project.notes)[l]; 58 | const noteText = project.notes[ noteTimestamp ]; 59 | 60 | let noteDate = moment( noteTimestamp ); 61 | let noteHasTime = ( noteTimestamp.split(' ').length > 1 ); 62 | 63 | 64 | let sameYear = ( now.format('YYYY') == noteDate.format('YYYY') ); 65 | 66 | let displayDate = noteDate.calendar(null, { 67 | sameDay: '[Today]', 68 | nextDay: '[Tomorrow]', 69 | nextWeek: 'dddd', 70 | lastDay: '[Yesterday]', 71 | lastWeek: '[Last] dddd', 72 | sameElse: ( sameYear ? 'D MMMM' : 'D MMMM YYYY' ), 73 | }); 74 | 75 | if ( noteHasTime ) { 76 | displayDate += ' ' + noteDate.format( 'h:mm a' ); 77 | } 78 | 79 | html += `
80 | ${displayDate} 81 | ${noteText} 82 |
`; 83 | 84 | }} 85 | 86 | // LINKS 87 | html += `
`; 88 | 89 | if ( project.links && Object.keys(project.links).length ) { for (let l = 0; l < Object.keys(project.links).length; l++) { 90 | let linkText = Object.keys(project.links)[l]; 91 | let linkURL = project.links[ linkText ]; 92 | html += `${linkText}`; 93 | 94 | }} 95 | 96 | html += '
'; 97 | 98 | html += ''; 99 | 100 | html += '
'; 101 | 102 | } 103 | 104 | html += `
`; 105 | 106 | return html; 107 | -------------------------------------------------------------------------------- /Views/project-header/view.css: -------------------------------------------------------------------------------- 1 | .markdown-preview-view .project-header .internal-link { 2 | text-decoration: none; 3 | } 4 | 5 | .project-header { 6 | margin: .5rem 0; 7 | background: hsla(0, 0%, 0%, .1); 8 | padding: 0 1.5rem 1.5rem; 9 | position: relative; 10 | border-radius: .5rem; 11 | } 12 | 13 | .project-header-inner { 14 | padding: 1rem; 15 | max-width: 100%; 16 | } 17 | 18 | /* Display priority tags as colored circle badges instead of text. */ 19 | .project-header-status { 20 | display: block; 21 | position: absolute; 22 | top: 1.1rem; 23 | left: .75rem; 24 | color: transparent; 25 | content: ''; 26 | width: 1em; 27 | height: 1em; 28 | border-radius: 50%; 29 | margin-right: .25em; 30 | } 31 | 32 | .project-header-status[data-status="today"] { 33 | background: #d9534f; 34 | } 35 | 36 | .project-header-status[data-status="todo"] { 37 | background: #0275d8; 38 | } 39 | 40 | .project-header-status[data-status="done"] { 41 | background: #5cb85c; 42 | } 43 | 44 | .project-header-status[data-status="cont"] { 45 | background: #5bc0de; 46 | } 47 | 48 | .project-header-status[data-status="future"], 49 | .project-header-status[data-status="hold"], 50 | .project-header-status[data-status="wait"] { 51 | background: #444; 52 | } 53 | 54 | .project-header-status[data-status="important"], 55 | .project-header-status[data-status="now"] { 56 | background: #ffd946; 57 | } 58 | 59 | .project-header-title, 60 | .markdown-preview-view .project-header-title { 61 | margin: 0; 62 | line-height: 1.1; 63 | } 64 | 65 | .project-header-meta-title { 66 | margin-top: 1rem; 67 | color: #999; 68 | text-transform: uppercase; 69 | font-size: smaller; 70 | } 71 | 72 | .project-header-meta-text { 73 | margin: .25rem 0; 74 | border-top: 1px solid #444; 75 | padding-top: .25rem; 76 | color: white; 77 | /* font-weight: bold; */ 78 | } 79 | 80 | .project-header-notes, 81 | .markdown-preview-view .project-header-notes { 82 | margin: .25rem 0; 83 | width: 100%; 84 | } 85 | 86 | .project-header-note-date, 87 | .markdown-preview-view .project-header-note-date { 88 | border-style: solid; 89 | border-color: #444; 90 | border-width: 1px 0 0; 91 | padding: .25rem 1rem .25rem 0; 92 | padding-right: 1rem; 93 | color: white; 94 | /* font-weight: bold; */ 95 | white-space: nowrap; 96 | width: 33%; 97 | font-size: smaller; 98 | } 99 | 100 | .project-header-note-text, 101 | .markdown-preview-view .project-header-note-text { 102 | border-style: solid; 103 | border-color: #444; 104 | border-width: 1px 0 0; 105 | padding: .25rem 0; 106 | } 107 | 108 | .project-header-sep { 109 | color: #666; 110 | } 111 | 112 | .project-header-link { 113 | display: block; 114 | margin: .5rem 0; 115 | color: inherit; 116 | padding: .5rem; 117 | border: 1px solid #444; 118 | position: relative; 119 | overflow: hidden; 120 | background: hsla(0, 0%, 0%, .1); 121 | } 122 | 123 | .project-header-link, 124 | .project-header-link:hover, 125 | .markdown-preview-view .project-header-link, 126 | .markdown-preview-view .project-header-link:hover { 127 | text-decoration: none; 128 | } 129 | 130 | .project-header-link-text { 131 | /* font-weight: bold; */ 132 | } 133 | 134 | .project-header-link-url { 135 | display: block; 136 | width: 100%; 137 | white-space: nowrap; 138 | overflow: hidden; 139 | text-overflow: ellipsis; 140 | font-size: smaller; 141 | color: #666; 142 | } -------------------------------------------------------------------------------- /Views/project-header/view.js: -------------------------------------------------------------------------------- 1 | let project = dv.current(); 2 | 3 | // RENDER 4 | html = `
5 |
`; 6 | 7 | // Jump ahead to get the most relevant date. 8 | let now = moment(); 9 | 10 | if ( project.status == 'todo' && project.notes && Object.keys(project.notes).length ) { 11 | projectTimestamp = Object.keys(project.notes)[0]; 12 | let projectDate = moment( projectTimestamp ); 13 | 14 | if ( projectDate.format('YYYY MM DD') == now.format('YYYY MM DD') || projectDate.unix() <= now.unix() ) { 15 | project.status = 'today'; 16 | } 17 | } 18 | 19 | // ICON 20 | if ( project.status ) html += ` `; 21 | 22 | // TITLE 23 | let title = project.title || project.file.name; 24 | html += `

${title}

`; 25 | 26 | // CODE 27 | if ( project.code ) { 28 | html += `
Code
29 |
${project.code}
`; 30 | } 31 | 32 | // NOTES 33 | if ( project.notes && Object.keys(project.notes).length ) { 34 | 35 | html += `
Dates
36 | 37 | `; 38 | 39 | for (let l = 0; l < Object.keys(project.notes).length; l++) { 40 | const noteTimestamp = Object.keys(project.notes)[l]; 41 | const noteText = project.notes[ noteTimestamp ]; 42 | 43 | let noteDate = moment( noteTimestamp ); 44 | let noteHasTime = ( noteTimestamp.split(' ').length > 1 ); 45 | 46 | let sameYear = ( now.format('YYYY') == noteDate.format('YYYY') ); 47 | 48 | let displayDate = noteDate.calendar(null, { 49 | sameDay: '[Today]', 50 | nextDay: '[Tomorrow]', 51 | nextWeek: 'dddd', 52 | lastDay: '[Yesterday]', 53 | lastWeek: '[Last] dddd', 54 | sameElse: ( sameYear ? 'D MMMM' : 'D MMMM YYYY' ), 55 | }); 56 | 57 | if ( noteHasTime ) { 58 | displayDate += ' ' + noteDate.format( 'h:mm a' ); 59 | } 60 | 61 | html += ` 62 | 63 | 64 | `; 65 | 66 | } 67 | 68 | html += `
${displayDate}${noteText}
`; 69 | 70 | } 71 | 72 | // LINKS 73 | html += `
`; 74 | 75 | if ( project.links && Object.keys(project.links).length ) { 76 | 77 | html += `
Links
`; 78 | 79 | for (let l = 0; l < Object.keys(project.links).length; l++) { 80 | let linkText = Object.keys(project.links)[l]; 81 | let linkURL = project.links[ linkText ]; 82 | html += `${linkText}
${linkURL}
`; 83 | 84 | } 85 | 86 | html += `
`; 87 | 88 | } 89 | 90 | html += `
91 |
`; 92 | 93 | return html; 94 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Obsidian Test Vault 2 | 3 | This is an [Obsidian](https://www.obsidian.md) vault that I’m using to test and demo my Obsidian templates and plugins. 4 | 5 | > Check out the [tutorial on Obsidian Hub](https://publish.obsidian.md/hub/03+-+Showcases+%26+Templates/Templates/Plugin-specific+templates/Dataview+templates/Project+Cards#Project+Cards) based on this template. 6 | 7 | ![Screenshot](Files/screenshot.png) 8 | --------------------------------------------------------------------------------