SmartBlocks
SmartBlocks copied to clipboard
Timestamp button for interstitial journaling + Elapsed time calculator v0.22
Version 0.22, June 1st, 2021 Download JSON file (.zip to extract, then import JSON file in your graph) to better preserve the structure of the code: Timestamp button for interstitial journaling v0.22.zip
📋 Description: simple Timestamp buttons
Really simple SmartBlocks duplicating the timestamp button at each use, and properly positioning the cursor to write straight away.
You need to add the first button in you daily template (or create one), see image below.
Just copy/past this: {{🕗↦:42SmartBlock:Time interval button}} where "Time interval button" is the name of the SmartBlock to run here (change to "Time button" if you want the "only beginning time" version, or "Time interval button + elapsed time" if you want an automatic calcul of elapsed time (see next section)).
You can change the time format from <%TIME%> to <%TIMEAMPM%> if you prefer (don't work actually with Elapsed time SmartBlock).
📋 Description: Elapsed time calculator (included from v0.2)
Copy/Paste code below in your graph, or (more reliable) import JSON file. It should be used in combination with 'Timestamp button + elapsed time' SmartBlock and button {{🕗↦:42SmartBlock:Time interval button + elapsed time}} added in your daily template.
The SmartBlock calculates the time elapsed between to timestamps (minutes, no more than 1440).
Then it test if it's less or more than a given limit. There is two kind of limits:
- minimum limit: like a goal, you want that your elapsed time to be greater than this minimum limit.
- maximum limit: you want to spend less time than that on the task.
Limits are linked to trigger words, searched in current block and first children block (case insensitive). There is 3 ways to set trigger words:
- User preset trigger words: (actually) in Javascript code block, in 'Elapsed time' SmartBlock, you can change user settings and enumerate, in a table, some trigger words for a given time limit. You can add as many limits and trigger words as you want.
- Inline trigger word: in user settings, you can define two specific trigger words that you can use inline, in your journaling block, followed by the time limite. (For example "goal:" for minimum time limit. If you write goals:40' in a block where you run the SmartBlock, minimum limit will be fixed to 40'). NB: Inline time limit have priority over preset time limits.
- /pomodoro command You can combinate them to make a counter interval (elapsed time has to be between the minimum limit and the maximum limit).
If there is no trigger word in the block, default time limit is used (see user settings in JS block). Default is 60' (it means that the popup notification appears if elapsed time is more than 60'). Set it to 0 if you want the popup every time.
By default, if there is some trigger word, a popup notification will appear, and you have to confirm for applying formatting (if it's less, or more, or exactly the elapsed time required), if not the popup desappear after 6 seconds. There is a user setting for desactivating this notification and automatic apply the formatting. Nb: you can use keyboard to confirm: the focus is automatically set on the right button, you have just to press Enter to apply formatting.
Of course, formatting (that is a string inserted in the current block, after the elapsed time) can set by user, also in the JS code. By default it's using the css code below (block background color is set to red or green), but you can just insert a word or an icon, or nothing.
This video shows the main features
🔁 Change log
v0.22 (June 1st, 2021)
- fixed: now works with a TODO block (or any other text before first timestamp, provided that it does not contain a colon (the first colon identifies the first timestamp))
✅ Prerequisites for this SmartBlock
Roam42 must be installed (instructions here).
For formating the blocks depending on the elapsed time, you can use this css (very simple example) (it's included in the JSON file):
.exceeded-time,
.insufficient-time {
background-color: #f8d7da !important;
}
.good-time {
background-color: #D2FFD4 !important;
}
📷 Screenshot
(Content of the code block doesn't appear entirely in the screenshot below)
💡 Additional Info
Download JSON file (.zip to extract, then import JSON file in your graph) to better preserve the structure of the code: Timestamp button for interstitial journaling v0.22.zip
✂️ #42SmartBlock code:
Copy/Paste the code below anywhere in your graph (for example in the page 'SmartBlocks'). Proceed in two times: first, the Smartblocks, then the Javascript code, to insert in the code block of the last Smartblock (if you are not sure, you should better import the JSON file):
- #42SmartBlock Time button
- <%TIME%> - <%CURSOR%>
- {{🕗:42SmartBlock:Time button}}
- #42SmartBlock Time interval button
- <%TIME%> {{⇥🕞:42SmartBlock:End time}}
- <%CURSOR%>
- {{🕗↦:42SmartBlock:Time interval button}}
- <%TIME%> {{⇥🕞:42SmartBlock:End time}}
- #42SmartBlock End time
- ⇥ <%TIME%>
- #42SmartBlock Time interval button + elapsed time
- <%TIME%> {{⇥🕞:42SmartBlock:Elapsed time}}
- <%CURSOR%>
- {{🕗↦:42SmartBlock:Time interval button + elapsed time}}
- <%TIME%> {{⇥🕞:42SmartBlock:Elapsed time}}
- #42SmartBlock Elapsed time
-
<%JA: ```javascript ``` %> <%NOBLOCKOUTPUT%>
-
⚠Copy/paste the code below in the code block in the Elapsed time Smartblock (remove 'javascript' at the top of the code):
/******************************************************************
Elapsed time calculator between two timestamps
Version: 0.22
Published: June 1st, 2021
By: Fabrice Gallet
Twitter: @fbgallet
Support my work on: https://www.buymeacoffee.com/fbgallet
/************************* USER SETTINGS **************************/
let defaultTimeLimit = 60; // set to 0 if you want always popup notification
let limitPresets = true; // false if you want disable trigger words search
// change or add as many limit and trigger as you want
let minLimitPresets = [ // search is case insensitive
{limit: 15, trigger: ["Meditation", "Morning page"]},
{limit: 30, trigger: ["read"]},
{limit: 45, trigger: ["Deep work"]} // add a ',' here if you add a new limit
];
let maxLimitPresets = [
{limit: 15, trigger: ["pause", "coffee", "call", "browsing", "twitter"]},
{limit: 40, trigger: ["coding", "revue"]}
];
let inlineMinLimit = "goal:";
let inlineMaxLimit = "max:";
let includeFirstChild = true; // search for trigger words in current block AND first child block
let pomoIsLimit = true; // Pomodotor timer as min trigger
let confirmPopup = true; // false if you want automatic formating without popup notification
let blockExcessFormat = "#[[.exceeded-time]] "; // 🛑 it can be just an icon like this, or a word
let blockNotEnoughFormat = "#[[.insufficient-time]] "; // 🔂
let blockGoodFormat = "#[[.good-time]] "; // ✅
let intervalSeparator = " ⇥ "; // serapator used between the two timestamps
let appendHourTag = false; // add a tag with the round current hour, like #19:00
/**********************************************************************/
let blockUID = roam42.common.currentActiveBlockUID();
let blockContent = document.activeElement.value;
let blockSplit = blockContent.split(":");
let b = blockSplit[0].length - 2;
if (b < 0) { b = 0;}
let beginH = parseInt(blockContent.slice(b+0,b+2));
let beginM = parseInt(blockContent.slice(b+3,b+5));
let beginT = blockContent.slice(b+0,b+5);
let currentH, currentM, currentT;
let title="";
let withPomo = false;
if (blockContent.search(intervalSeparator) == b+5) {
let l = intervalSeparator.length;
currentH = parseInt(blockContent.slice(b+5+l,b+7+l));
currentM = parseInt(blockContent.slice(b+8+l,b+10+l));
currentT = blockContent.slice(b+5+l,b+10+l);
title = blockContent.slice(b+10+l);
}
else {
let current = new Date();
currentH = current.getHours();
currentM = current.getMinutes();
currentT = addZero(currentH) + ":" + addZero(currentM);
title = blockContent.slice(b+5);
}
let elapsedH = currentH - beginH;
if (elapsedH < 0) {elapsedH = 24 - beginH + currentH; }
let elapsedM = currentM - beginM;
let elapsedT = (elapsedH * 60) + elapsedM;
let hourTag = "";
if (appendHourTag) { hourTag = " #[[" + beginH + ":00]]"; }
let newContent = blockSplit[0].slice(0, -2) + beginT + intervalSeparator + currentT + " " + "(**" + elapsedT + "'**) ";
let titleForSearch = title;
// include fisrt children block for searching trigger words
if (includeFirstChild) {
let blockTree = await roam42.common.getBlockInfoByUID(blockUID, true);
if (blockTree[0][0].children) {
titleForSearch += " " + blockTree[0][0].children[0].string;
}
}
let withMin = false;
let withMax = false;
let minIndex = titleForSearch.search(new RegExp(inlineMinLimit, "i"));
let maxIndex = titleForSearch.search(new RegExp(inlineMaxLimit, "i"));
let timeLimitMin=0, timeLimitMax=1000;
if (minIndex != -1) {
withMin = true;
timeLimitMin = extractLimit(titleForSearch.slice(minIndex), inlineMinLimit.length);
}
if (maxIndex != -1) {
withMax = true;
timeLimitMax = extractLimit(titleForSearch.slice(maxIndex), inlineMaxLimit.length);
}
if (pomoIsLimit) {
let indexPomo = title.search("POMO");
if (indexPomo != -1) {
timeLimitMax = extractLimit(titleForSearch.slice(indexPomo), 8);
withMax = true;
withPomo = true;
}
}
if (limitPresets) {
let indexLimit = -1;
let end = maxLimitPresets.length;
if (!withMax || withPomo) {
for (let i=0; i < end; i++) {
let nbTriggers = maxLimitPresets[i].trigger.length;
for (let j=0; j < nbTriggers; j++) {
indexLimit = titleForSearch.search(new RegExp(maxLimitPresets[i].trigger[j], "i"));
if (indexLimit != -1) {
timeLimitMax = maxLimitPresets[i].limit;
withMax = true;
break;
}
}
if (withMax) { break; }
}
}
if (!withMin) {
end = minLimitPresets.length;
for (let i=0; i < end; i++) {
let nbTriggers = minLimitPresets[i].trigger.length;
for (let j=0; j < nbTriggers; j++) {
indexLimit = titleForSearch.search(new RegExp(minLimitPresets[i].trigger[j], "i"));
if (indexLimit != -1) {
timeLimitMin = minLimitPresets[i].limit;
withMin = true;
break;
}
}
if (withMin) { break; }
}
}
}
let triggered = withMax || withMin || withPomo;
if (withMax && withMin && (timeLimitMax <= timeLimitMin)) { withMin = false; }
if (!triggered) {timeLimitMax = defaultTimeLimit;}
let exceeded = ((withMax || !triggered) && (elapsedT > timeLimitMax));
let insufficient = (withMin && (elapsedT < timeLimitMin));
let okUnder = !exceeded && !withMin && triggered;
let okOver = !insufficient && !withMax && triggered;
if (exceeded || insufficient || okUnder || okOver || triggered) {
let textTitle = elapsedT + "' elapsed.";
let textMessage = "Goal was "; // " fixed to: " + timeLimit + "'. This is:";
let buttonCaption = "Too much anyway!";
let badFormat = blockExcessFormat;
if ((!exceeded && !insufficient) || (okUnder && !insufficient) || (okOver && !exceeded)) {
if ((!okOver && !okUnder)) {
textMessage += "between " + timeLimitMin + "' & " + timeLimitMax + "'";
} else if (okUnder) {
textMessage += "less than " + timeLimitMax + "'";
badFormat = blockNotEnoughFormat;
} else {
textMessage += "more than " + timeLimitMin + "'";
buttonCaption = "Not enough anyway!";
}
if (confirmPopup) {
buttonCaption = "<button>" + buttonCaption + "</button>";
iziToast.success({
timeout: 6000, displayMode: 'replace', id: 'timing', zindex: 999,
title: textTitle,
message: textMessage,
position: 'bottomCenter', drag: false, close:true,
buttons: [
['<button>Great!</button>', (instance, toast)=> {
roam42.common.updateBlock(blockUID, newContent + blockGoodFormat + title.trim() + hourTag, true);
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}, true],
[buttonCaption, (instance, toast)=> {
roam42.common.updateBlock(blockUID, newContent + badFormat + title.trim() + hourTag, true);
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}]
],
});
await roam42.common.sleep(150);
await roam42KeyboardLib.pressEsc(150);
}
}
else {
if ((exceeded && withMin) || (insufficient && withMax)) {
textMessage += "between " + timeLimitMin + "' & " + timeLimitMax + "'";
buttonCaption = "Too much!";
if (insufficient) {
badFormat = blockNotEnoughFormat;
buttonCaption = "Not enough!";
}
} else if ((!okUnder && !insufficient) || exceeded) {
buttonCaption = "Too much!";
if (!triggered) {
textMessage = "Default alert time is " + timeLimitMax + "'";}
else {textMessage += "less than " + timeLimitMax + "'";}
} else {
if (!triggered) {
textMessage = "Default alert time is " + timeLimitMax + " '.";}
else { textMessage += "more than " + timeLimitMin + " '."; }
buttonCaption = "Not enough!"
badFormat = blockNotEnoughFormat;
}
if (confirmPopup) {
buttonCaption = "<button>" + buttonCaption + "</button>";
iziToast.warning({
timeout: 6000, displayMode: 'replace', id: 'timing', zindex: 999,
title: textTitle,
message: textMessage,
position: 'bottomCenter', drag: false, close:true,
buttons: [
['<button>Good anyway!</button>', (instance, toast)=> {
roam42.common.updateBlock(blockUID, newContent + blockGoodFormat + title.trim() + hourTag, true);
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
[buttonCaption, (instance, toast)=> {
roam42.common.updateBlock(blockUID, newContent + badFormat + title.trim() + hourTag, true);
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}, true]
],
});
await roam42.common.sleep(150);
await roam42KeyboardLib.pressEsc(150);
}
else {
newContent = newContent + badFormat;
}
}
}
await roam42.common.updateBlock(blockUID, newContent + title.trim() + hourTag, true);
await roam42.common.sleep(150);
await roam42KeyboardLib.pressEsc(150);
function extractLimit (s, shift) {
let t = "";
let i = 0;
while ((i+shift < s.length) && (!isNaN(s.charAt(i+shift)))) {
t += s.charAt(i+shift);
i++;
}
return t;
}
function addZero(i) {
if (i < 10) {
i = "0" + i;
}
return i;
}
Thanks! Just what is needed!
@fbgallet very nice smartblock! Quick suggestion, you can now nest a css code block under {{[[roam/css]]}} and it'll run.
This plug-in is not work at SmartBlocks V2, is there any update for SBv2?
This plug-in is not work at SmartBlocks V2, is there any update for SBv2?
Uptade for SBv2 is published here: https://roamresearch.com/?server-port=3333#/app/Roam-En-Francais/page/-9fGz51_v
@fbgallet I've very much enjoyed the timestamped button. But the Elapsed Time in my Roam has not been working properly for two days. It said "Block threw an error while running". I've updated to SBv2. Could you tell me what I should do to have it fixed? Thanks!
@fbgallet I've very much enjoyed the timestamped button. But the Elapsed Time in my Roam has not been working properly for two days. It said "Block threw an error while running". I've updated to SBv2. Could you tell me what I should do to have it fixed? Thanks!
Hi @helenysli , have you installed the Sb v2 compatible version from the SmartBlock Store (Cmd/Ctrl + p, then search for "store") ? For the current version, you need also Roam42 installed in your graph.
Be sure, before installing the new version, to remove the previous one from your graph. Once the Sb will we installed from the store, the updates will be more easy (one click on "Update" button in the store).
@fbgallet It worked! It turned out that I hadn't actually updated to SBv2. Thank you very much for the great work. It does make my life easier.