You make these things look easy, mentaloid
It’s not visible, but I’m clicking the white space in the table.
For anyone who wants to replicate here are the details.
Project details
- Render a table of future events in chronological order.
- Initially displays only the number of days until the event and the event name.
- Clicking the event reveals more event details.
- Clicking on places in the table doesn’t open the logseq markdown editing mode
Requires
kits
Components and instructions
futureEventsTable kits javascript
This javascript goes in a javascript block on the page futureEventsTable.
logseq.kits.setStatic(async function futureEventsTable(div) {
// Start counting from startDate date into the future. You probably want
// to start from today
const fromDate = new Date();
const futureEventsPromise = (async (startDate = fromDate) => {
// Logseq's :journal-day uses the format date YYYYMMDD.
const logseqStartDate = ((date = startDate) => {
const month = (date.getMonth() + 1).toString().padStart(2, "0"),
day = date.getDate().toString().padStart(2, "0"),
year = date.getFullYear().toString();
return [year, month, day].join("");
})();
const futureEventsArray = await (async (startDate = logseqStartDate) => {
if (typeof startDate != "string") {
console.log(
17,
`function futureEventsArray: Expected startDate
to be a string, but it was not.`
);
}
const advancedQuery = `
[:find ?date ?day ?content ?props ?uuid
:keys date day content properties uuid
:where
[?e :block/properties ?props]
[(get ?props :event) ?event]
[(get ?props :date) ?date]
[?e :block/refs ?refs]
[?e :block/content ?content]
[?e :block/uuid ?uuid]
[?refs :block/journal-day ?day]
[(>= ?day ${startDate})]
]`;
const queryResults = await logseq.api.datascript_query(advancedQuery);
//const flatQueryResults = queryResults?.flat();
// The expected format for the :date property is that it includes a single
// linked reference. Logseq returns that as a single-item array.
const flatFutureEventsArray = queryResults?.flat().map((appointment) => ({
...appointment,
date: appointment.date[0],
}));
return flatFutureEventsArray;
})();
const chronologicalEvents = ((activities = futureEventsArray) => {
if (typeof activities.sort != "function") {
console.log(
46,
`function chronologicalEvents: Expected futureEventsArray
to be an object, but it was not.`
);
return [];
}
return [...activities].sort((a, b) => {
return a.day - b.day;
});
})();
const futureEventsWithCountdown = ((
activities = chronologicalEvents,
earlierDate = startDate
) => {
if (activities.length == 0) return -1; // No future activiuties
activities.forEach((activity) => {
const nextActivityDay = activity.day.toString();
const year = parseInt(nextActivityDay.slice(0, 4), 10);
const month = parseInt(nextActivityDay.slice(4, 6), 10) - 1; // Adjust for zero-indexed months
const day = parseInt(nextActivityDay.slice(6, 8), 10);
const nextActivityDate = new Date(year, month, day);
const daysUntil = Math.ceil(
(nextActivityDate - earlierDate) / (1000 * 60 * 60 * 24)
);
//console.log(daysUntil);
activity.daysUntil = daysUntil;
});
return activities;
})();
//console.table(futureEventsWithCountdown);
return futureEventsWithCountdown;
})();
result = await futureEventsPromise;
const table = document.createElement("table");
table.className = "compact future-event-table";
table.innerHTML = `<thead>
<tr>
<th class="days-until">In<br><small>(days)</small></th>
<th>Event</th>
</tr>
</thead>
<tbody>
${result
.map(
(event, index) => `
<tr>
<td rowspan="2" class="days-until"
>${event.daysUntil}</td>
<td class="clickable"
><a onclick="document.getElementById('event-info-${event.uuid}').classList.toggle('closed');"
>${event.properties.event}</a></td>
</tr>
<tr>
<td class="event-info closed" id="event-info-${event.uuid}"
>${event.date} with ${event.properties.with} at ${event.properties.time}</td>
</tr>`
)
.join("")}
</tbody>`;
table.addEventListener("mousedown", function cancel(e) {
e.stopPropagation();
});
div.appendChild(table);
});
futureEventsTable macro
The futureEventsTable kit is invoked via a macro, {{futureEventsTable}}
. Define the macro by putting the following inside the config.edn
macro key:
:macros {
:futureEventsTable "[:div {:class \"kit inline\" :data-kit \"futureEventsTable\" } ]" ;; kits macro
}
Styles
Here are the styles I use with the table. If you waThis goes in custom.css
/*** Required styles
** The following two styles are not optional. They're required for the
** event info disclosure feature to function correctly
*/
.future-event-table td.event-info {
display: block;
transition: all 0.25s;
transition-behavior: allow-discrete;
opacity: 1;
scale: 1
}
.future-event-table td.event-info.closed {
display: none;
opacity: 0;
scale: 0;
height: 0;
}
/*** Optional styles
** The following styles control the appearance of the table elements. Include
** them or change them at your will.
*/
.future-event-table .clickable a {
color: var(--ls-link-text-color);
display: block;
width: 100%;
height: 100%;
}
.future-event-table .clickable a:hover {
color: var(--ls-link-text-hover-color);
background-color: var(--ls-selection-background-color);
border-radius: 6px;
}
.future-event-table td.days-until {
font-weight: bold;
text-align: center;
}
.future-event-table th.days-until {
width: 60px;
}
.future-event-table td[rowspan] {
vertical-align: baseline;
}
Sample data
Here’s some sample data to populate the event table. Just put it in a block anywhere in your graph. Though, some of the code is a bit fragile and is expecting some properties to be arrays, so you might encounter issues if you don’t use linked references with the properties that include them below.
Weird things happen if you try to use a linked reference in the event
property.
The activity
, with
, and location
properties support multiple linked references.
event:: Something fun
activity:: [[something]]
with:: [[@Someone]]
location:: [[.some place]]
date:: [[Saturday, Aug 10th, 2024]]
time:: 1200