Prevent logseq from entering editing mode when clicking a table cell using javascript

You make these things look easy, mentaloid :slight_smile:

2024-08-0916-05-15-1-ezgif.com-video-to-avif-converter

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