Skip to content

Instantly share code, notes, and snippets.

@kaelri
Last active April 9, 2022 04:22
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kaelri/f7427fef75a375a453faa7e9c030e254 to your computer and use it in GitHub Desktop.
Save kaelri/f7427fef75a375a453faa7e9c030e254 to your computer and use it in GitHub Desktop.
Obsidian Project List
.project-cards {
margin: 2em 0;
}
.project-card {
margin: .5em 0;
/* border: 1px solid #444; */
background: hsla(0, 0%, 0%, .1);
padding: .5em .5em .5em 2em;
position: relative;
/* max-width: 40em; */
}
/* Display priority tags as colored circle badges instead of text. */
.project-card-status {
display: block;
position: absolute;
top: .75em;
left: .5em;
color: transparent;
content: '';
width: 1em;
height: 1em;
border-radius: 50%;
margin-right: .25em;
}
.project-card-status[data-status="today"] {
background: #d9534f;
}
.project-card-status[data-status="todo"] {
background: #0275d8;
}
.project-card-status[data-status="done"] {
background: #5cb85c;
}
.project-card-status[data-status="cont"] {
background: #5bc0de;
}
.project-card-status[data-status="future"],
.project-card-status[data-status="wait"] {
background: #444;
}
.project-card-status[data-status="important"],
.project-card-status[data-status="now"] {
background: #ffd946;
}
.project-card .project-card-title {
margin: 0;
font-size: 1rem;
}
.project-card-title a {
color: inherit;
}
.project-card-meta,
.project-card-note {
font-size: smaller;
color: #999;
line-height: 1.5;
margin-top: .25em;
}
.project-card-link {
display: inline-block;
color: inherit;
line-height: 1;
padding: 4px;
margin-right: .5em;
border: 1px solid #444;
/* text-decoration: underline; */
}
.project-card-note-date {
margin-right: .5em;
color: white;
}
.project-card-note-text {
color: #999;
}
.project-card-sep {
color: #666;
}

Project Cards

let projects = dv.pages('"Projects"').where( p => ( [ 'todo', 'today', 'now', 'important' ].indexOf(p.status) > -1 ) );
let order    = 'asc';

// SORT
projects = projects.sort( function(project) {
	
	if ( project.notes && Object.keys(project.notes).length ) {
		var date = moment( Object.keys(project.notes)[0] );
	} else {
		var date = moment();
	}
	
	let sortValue = date.unix();
	
	return sortValue;
	
}, order);

// RENDER
let html = `<section class="project-cards">`;

for (let i = 0; i < projects.length; i++) {

	const project = projects[i];

	// Jump ahead to get the most relevant date.
	let now = moment();

	if ( project.notes && Object.keys(project.notes).length ) {
		projectTimestamp = Object.keys(project.notes)[0];
		let projectDate  = moment( projectTimestamp );

		if ( projectDate.format('YYYY MM DD') == now.format('YYYY MM DD') ) {
			project.status = 'today';
		}
	}
	
	html += `<article class="project-card">`;
		
	// ICON
	if ( project.status ) html += `<span class="project-card-status" data-status="${project.status}">&nbsp;</span>`;

	// TITLE
	let title = project.title || project.file.name;
	html += `<h1 class="project-card-title"><a href="${project.file.name}" data-href="${project.file.name}" class="internal-link">${title}</a></h1>`;

	// CODE
	html += `<div class="project-card-meta">`;

	if ( project.code ) html += `<span class="project-card-code">${project.code}</span>`;

	html += '</div>';

	// NOTES
	if ( project.notes && Object.keys(project.notes).length ) { for (let l = 0; l < Object.keys(project.notes).length; l++) {
		const noteTimestamp = Object.keys(project.notes)[l];
		const noteText      = project.notes[ noteTimestamp ];

		let noteDate        = moment( noteTimestamp );
		let noteHasTime     = ( noteTimestamp.split(' ').length > 1 );

		
		let sameYear        = ( now.format('YYYY') == noteDate.format('YYYY') );

		let displayDate     = noteDate.calendar(null, {
			sameDay: '[Today]',
			nextDay: '[Tomorrow]',
			nextWeek: 'dddd',
			lastDay: '[Yesterday]',
			lastWeek: '[Last] dddd',
			sameElse: ( sameYear ? 'D MMMM' : 'D MMMM YYYY' ),
		});

		if ( noteHasTime ) {
			displayDate += ' <span class="project-card-sep">•</span> ' + noteDate.format( 'h:mm a' );
		}

		html += `<div class="project-card-note">
			<span class="project-card-note-date" title="${noteDate}">${displayDate}</span>
			<span class="project-card-note-text" title="${noteDate}">${noteText}</span>
		</div>`;
			
	}}

	// LINKS
	html += `<div class="project-card-meta">`;

	if ( project.links && Object.keys(project.links).length ) { for (let l = 0; l < Object.keys(project.links).length; l++) {
		let linkText = Object.keys(project.links)[l];
		let linkURL  = project.links[ linkText ];
		html += `<a class="project-card-link" href="${linkURL}">${linkText}</a>`;
			
	}}

	html += '</div>';

	html += '</div>';

	html += '</article>';

}

html += `</section>`;

dv.paragraph( html );

@confluencepoint
Copy link

I found your post on Discord.
Is there a special path for your project file?

For me, only the heading "Project Cards" is displayed.

20210712_02

@kaelri
Copy link
Author

kaelri commented Jul 12, 2021

@confluencepoint Hey there — yes, this lists notes in a top-level folder called "Projects." That can be changed to any other query that the Dataview plugin allows. For example, if your notes have the tag #project, you could list them by changing the initial query to dv.pages('"#project"').

@confluencepoint
Copy link

confluencepoint commented Jul 13, 2021

@confluencepoint Hey there — yes, this lists notes in a top-level folder called "Projects." That can be changed to any other query that the Dataview plugin allows. For example, if your notes have the tag #project, you could list them by changing the initial query to dv.pages('"#project"').

Thanks. I think I need your help again.

I have a top-level folder called "Projects" an two files in there:

20210713_01

  • PROJECT-0001 Some Neat Project.md
---
title: "Some Neat Project"
status: "todo"
code: "PROJECT-0001"
notes: 
  "2021-06-17 14:00": "In Progress"
links: 
  "Thread": "https://www.google.com"
  "Google Docs": "https://www.google.com"
---


I'm a project!
  • PROJECT-0002 Some Extra Neat Project.md
---
title: "Some extra Neat Project"
status: "todo"
code: "PROJECT-0002"
notes: 
  "2021-06-17 15:00": "In Progress"
links: 
  "Thread": "https://www.google.com"
  "Google Docs": "https://www.google.com"
---

I'm a project!
  • The file obsidian-dataview-project-cards.md produces this error:
Evaluation Error: ReferenceError: projectTimestamp is not defined
    at eval (eval at <anonymous> (eval at <anonymous> (app://obsidian.md/app.js:1:1210430)), <anonymous>:30:26)
    at DataviewInlineApi.eval (eval at <anonymous> (app://obsidian.md/app.js:1:1210430), <anonymous>:11833:33)
    at evalInContext (eval at <anonymous> (app://obsidian.md/app.js:1:1210430), <anonymous>:11833:49)
    at DataviewJSRenderer.eval (eval at <anonymous> (app://obsidian.md/app.js:1:1210430), <anonymous>:12221:17)
    at Generator.next (<anonymous>)
    at eval (eval at <anonymous> (app://obsidian.md/app.js:1:1210430), <anonymous>:26:71)
    at new Promise (<anonymous>)
    at __awaiter (eval at <anonymous> (app://obsidian.md/app.js:1:1210430), <anonymous>:22:12)
    at DataviewJSRenderer.render (eval at <anonymous> (app://obsidian.md/app.js:1:1210430), <anonymous>:12218:16)
    at DataviewJSRenderer.eval (eval at <anonymous> (app://obsidian.md/app.js:1:1210430), <anonymous>:12210:24)

The file obsidian-dataview-project-cards.css is in the snippets folder and enabled.

@steven-kraft
Copy link

@confluencepoint Changing projectTimestamp = Object.keys(project.notes)[0]; to let projectTimestamp = Object.keys(project.notes)[0]; fixed the issue for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment