Journaling Utilities

Howdy! I’m a big fan of the potential of silverbullet as a tool for barefoot developers, and I also practice some journaling based on the recommendations from my therapist. I think the various journaling apps available try to enforce the “right” way to journal a tad too much, and I like the idea of using SB to highly customize what your journaling experience looks like.

But, I thought it would be useful to have some common elements of these various journaling apps available in SB, to use directly or at least as a reference. Hence, this little library of various sections one might include in their journal templates.

The library itself will go over each section and its intended uses, but to tempt you to install the library I’ll show off a screenshot of my favorite, a pixel calendar that can be used for tracking moods, habits, and more:

Installing

Since this is my first library, I haven’t made a repository yet. So you can just install it using this github url: https://github.com/thepaperpilot/silverbullet-libraries/blob/main/Library/thepaperpilot/CBT-Journal.md

9 Likes

What an awesome set of tools. Nice job.
I’m not a journal writer type, but I mostly impressed by the implementation of each tool on the technical level.
It’s simple but effective :clap::exploding_head:
This is where silverbullet shines over other note taking apps. Giving the user freedom to implement features easily and to customize their Note Taking/ Journaling/ Silverbulleting experience. No matter if you are a coder, therapist, novel author, patient, astronomer, engineer, etc. if someone is willing to step outside the comfort zone and learn a bit of space-lua, css, js, the possibilities are endless.
Looking forward to, what other tools you can come up with.

2 Likes

I agree and also wanted to share some of my changes to this journaling marvel.

By the way, my experience with Lua started same day I installed SB. So, not sure about my code quality! Also sorry for this long post.

Here a summary and then the details:

  1. At the frontmatter, I prefer to save the related values as part of specific "YAML" section (instead of beeing at the "root" level). This required a way to update the frontmatter.
  2. Ids, icons and descriptions from variables (lua tables).
  3. Functions for table visualization with colors and icons.
  4. Changes to the initial CBT library provided by thepaperpilot.

Now the details:

1. Update a FrontMatter Section:

As a section (instead of "root" level), is easy for me to query all values at once and also avoid the issues with repeated values several times in the frontmatter's Cognitive Distortion Section.

After diging the yaml test code, I found two basic frontmatter operations ("set-key" and "delete-key"). So, I created a function with the combined two level operations (delete + insert):

function updFrontmatterSection(path, value)
  --path: Frontmatter "root" section name
  --value: Value: The section values. Can be an array/list/dictionary/nested array of arrays and so on
  local fminfo = index.extractFrontmatter(editor.getText())
  local fmtable = fminfo.frontmatter and fminfo.frontmatter[path]
  if not fmtable then
    fmtable = { }
  end
  if fmtable[value] then
    --print(value .. " found...removing")
    fmtable[value] = false
  else
    --print(value .." not found...adding")
    fmtable[value] = true
  end
  local updated = index.patchFrontmatter(editor.getText(), 
    { { op="delete-key", path=path }, })
  editor.setText(updated)
  local updated = index.patchFrontmatter(editor.getText(), 
    { { op="set-key", path=path, value=fmtable }, })
  editor.setText(updated)
  editor.rebuildEditorState()
end

2. Values from variables

Note: I started as simple lua table, then as arrays/dicts/nested tables.

creature_icons = {daughter="🧚‍♀️", wife="🏃‍♀️", pets="🐾", baby="👶🏻", someoneelse="👤"}

cbt_feeling_icons = { [1]="🙁", [2]="☹️", [3]="😐", [4]="🙂", [5]="😀"}

cbt_feeling_colors = { [1]="red", [2]="orange", [3]="yellow", [4]="green", [5]="purple"}

cbt_sensitivities_icons = {sleep="😴", exercise="🏋🏻‍♂️", bed="🛏️", headache="🤕", medication="💊", food="🍜", coffee="☕", music="🔊", phone="📱", reading="📖", laptop="👨‍💻", money="💵", toughts="🧠", vomit="🤮"}

suds_thermometer = {
level_100 = {value=100, color_hex="#ff032d", color_rbg="rgba(255, 3, 45)", color_hsl="hsl(350, 100, 51)", icon = "", desc="100 Highest anxiety/distress that you have ever felt.", short_desc="100 Highest a/d."},
level_90 = {value=90, color_hex="#ff032d", color_rbg="rgba(255, 3, 45)", color_hsl="hsl(350, 100, 51)", icon = "", desc=" 90 Extremely anxious/distressed.", short_desc="90 Extremely a/d."},
--...and so on
}

-- desc value of each cbt_cdistortions refers to another variable. This is only for easy code-reading because the long description of the distortion
cbt_cdistortions = {
    filtering = {name="Filtering", icon = "⤵️", desc=filtering_desc},
    discounting_positive= {name="Discounting the positive", icon = "😒", desc=discounting_positive_desc},
    polarization= {name="Polarization or all-or-nothing thinking", icon = "3", desc=polarization_desc},
    overgeneralization= {name="Overgeneralization", icon = "4", desc=overgeneralization_desc},
    jumping2conclusions= {name="Jumping to conclusions", icon = "5", desc=jumping2conclusions_desc},
    catastrophizing= {name="Catastrophizing", icon = "6", desc=catastrophizing_desc},
    personalization= {name="Personalization", icon = "7", desc=personalization_desc},
    fallacies= {name="Fallacies", icon = "8", desc=fallacies_desc},
    blame= {name="Blame", icon = "9", desc=blame_desc},
    should_statements= {name="Should statements", icon = "10", desc=should_statements_desc},
    emotional_reasoning= {name="Emotional reasoning", icon = "11", desc=emotional_reasoning_desc},
    global_labeling= {name="Global labeling", icon = "12", desc=global_labeling_desc},
    always_being_right= {name="Always being right", icon = "13", desc=always_being_right_desc},
}

--And here the loooong descriptions
filtering_desc=[[Mental filtering is draining and straining all positives in a situation and, instead, dwelling on its negatives.

Even if there are more positive aspects than negative in a situation or person, you focus on the negatives exclusively.

For example, focusing on a minor suggestion for improvement after receiving many compliments during a performance review at work may affect your mood negativeldiscounting_positivey.]]

discounting_positive_desc=[[Discounting positives is similar to mental filtering. The main difference is that you dismiss it as something of no value when you do think of positive aspects.

For example, if someone compliments the way you look today, you may think they’re just being nice.]] 
--and so on

3. Fuctions


function renderIcons(iconsTable, values)
  --more generic than other rendering functions
  local icons = ""
  for k, v in pairs(values) do
    if v and iconsTable[k] then
      print(iconsTable[v])
      icons = icons .. iconsTable[k]
    end
  end
  return icons
end

function renderCBTSensitivities(table)
  local icons = ""
  for k, v in pairs(table) do
    if v and cbt_sensitivities_icons[k] then
      icons = icons .. cbt_sensitivities_icons[k] 
    end
  end
  return icons
end

function renderCBTFeeling(feeling)
  if feeling and cbt_feeling_icons[feeling] then
    return cbt_feeling_icons[feeling] .. ":" .. feeling
  else
    return "❓" .. (feeling and ":" .. feeling or "")
  end
end


function getCBTCdistortion(distortion)
  if distortion then
    return(cbt_cdistortions[distortion])
  end
end

function getSUDSThermometerLevel(value)
  local temp_level = "no_value"

  if value and type(value) == "number" then
    if value < 9 then
        temp_level = "level_0"
    elseif value <= 19 then
        temp_level = "level_10"
    elseif value <= 29 then
        temp_level = "level_20"
    elseif value <= 39 then
        temp_level = "level_30"
    elseif value <= 49 then
        temp_level = "level_40"
    elseif value <= 59 then
        temp_level = "level_50"
    elseif value <= 69 then
        temp_level = "level_60"
    elseif value <= 79 then
        temp_level = "level_70"
    elseif value <= 89 then
        temp_level = "level_80"
    elseif value <= 99 then
        temp_level = "level_90"
    elseif value >= 100 then
        temp_level = "level_100"
    end
  end

  return temp_level
  
end

function getSUDSThermometer(value)
  local temp_value = getSUDSThermometerLevel(value)
  
  --if not value then value = 20 end
  local wd = {}
  
  wd = widget.htmlBlock(dom.div {
    style = 'display: inline-block;width: fit-content;background-color:' .. suds_thermometer[temp_value].color_hex .. ' ;margin:10px 0;"',
    dom.text {
      style = "color:#7607a7",
      suds_thermometer[temp_value].desc  
    }  
  })
  
  return wd
end

4 Changes to the CBT Lib

  • Library/thepaperpilot/CBT-Journal/Tracker Section
-- priority: 10

function cbt_journal.tracker_section(section, trackers)
  local buttons = {}
  local fminfo = index.extractFrontmatter(editor.getText())

  function add_button(text, value)
    local fmsection = fminfo.frontmatter and fminfo.frontmatter[section]
    local current = false
    if not fmsection then
      print("section not found")
      fmsection = { }
    else
      current = fmsection[value]
    end
    
    table.insert(buttons, dom.button {
      style = "margin-right: 0.5em; padding: .2em; font-size: 200%;" .. (current and "background-color: var(--editor-highlight-background-color)" or ""),
      onclick = function()
        updFrontmatterSection(section, value)
      end,
      text
    })
  end

  for key, text in pairs(trackers) do
    add_button(text, key)
  end

  return widget.html(dom.div(buttons))
end

slashcommand.define {
  name = "Tracker Section",
  run = function()
    editor.insertAtCursor("${cbt_journal.tracker_section({\n  \n})}\n", false, true)
    editor.moveCursor(editor.getCursor() - 5)
  end
} 

Previuos change will allow something like: ${cbt_journal.tracker_section("sensitivities", cbt_sensitivities_icons)} to show the icons/buttons and save the frontmatter as:

sensitivities:
  hydration: false
  reading: false
  brush_teeth: false
  sleep: true
  exercise: true
  bed: true
  headache: true
  • Library/thepaperpilot/CBT-Journal/Cognitive Distortions Section
    I like this one.
-- priority: 10

function cbt_journal.cognitive_distortions_section()
  local section = "cognitiveDistortions"
  local options = {}
  local fminfo = index.extractFrontmatter(editor.getText())
  local expanded = fminfo.frontmatter and fminfo.frontmatter.cognitiveDistortionsExpanded
  
  local fmsection = fminfo.frontmatter and fminfo.frontmatter[section] or {}
  local count = 0
  for k, v in pairs(fmsection) do 
    if v then
      count = count + 1
    end 
  end
  
  function add_options()
    for k, v in pairs(cbt_cdistortions) do
        add_option(k, cbt_cdistortions[k].name, cbt_cdistortions[k].desc)
    end
  end

  function add_option(value, name, description)
    local current = false
    if fmsection and fmsection[value] then
      --print("section found:")
      current = fmsection[value]
    else
      --print(value .. " not found on fmsection")
    end
        
    if expanded then
      --print(name)
      table.insert(options, dom.button {
        --style = "padding: .5em; text-align: left;",
        style = "padding: .5em; text-align: left;" .. (current and "background-color: var(--editor-highlight-background-color);" or ""),
        onclick = function()
          print("Update")
          updFrontmatterSection(section, value)
        end,
        widget.html(markdown.markdownToHtml("## " .. name .. "\n\n" .. description))
      })
    elseif current then
      table.insert(options, dom.li {
        name
      })
    end
  end

  add_options()
  
  if not expanded then
    if count > 0 then
      table.insert(options, dom.button {
        style = "padding: .5em",
        onclick = function()
          local updated = index.patchFrontmatter(editor.getText(),
          {
            { op="set-key", path="cognitiveDistortionsExpanded", value=true },
          })
          editor.setText(updated)
          editor.rebuildEditorState()
        end,
        "Edit"
      })
    else
      return widget.html(dom.button {
        style = "padding: .5em",
        onclick = function()
          local updated = index.patchFrontmatter(editor.getText(),
        {
          { op="set-key", path="cognitiveDistortionsExpanded", value=true },
        })
          editor.setText(updated)
          editor.rebuildEditorState()
        end,
        "Experience any cognitive distortions today?"
      })
    end
  else
    table.insert(options, dom.button {
      style = "padding: .5em",
      onclick = function()
        local updated = index.patchFrontmatter(editor.getText(),
        {
          { op="set-key", path="cognitiveDistortionsExpanded", value=false },
        })
        editor.setText(updated)
        editor.rebuildEditorState()
      end,
      "Done editing"
    })
  end
  
  return widget.html(dom.div {
    style = "display: flex; flex-flow: column; gap: .5em",
    table.unpack(options)
  })
end

slashcommand.define {
  name = "Cognitive Distortions Section",
  run = function()
    editor.insertAtCursor("${cbt_journal.cognitive_distortions_section()}\n", false, true)
  end
}

the frontmatter will be similar to:

cognitiveDistortionsExpanded: false
cognitiveDistortions:
  filtering: true
  discounting_positive: true
  • Library/thepaperpilot/CBT-Journal/Check-Ins Section
    Goal: Improve the table rendering from the yaml sections (instead of the root) and produce something like

-- priority: 10

function cbt_journal.checkins_section(prefix)
  return query[[
    from index.tag "page"
    where _.name:startsWith(prefix)
    select {
      ["check-in"] = "[[" .. _.name .. "|" .. _.name:gsub("^" .. prefix, "") .. "]]",
      temperature=_.temperature,
      feeling=renderCBTFeeling(_.feeling),
      who=renderCreatures(_.who),
      sensitivities=renderCBTSensitivities(_.sensitivities),
    }
    order by created desc
  ]];
end

slashcommand.define {
  name = "Check-Ins Section",
  run = function()
    editor.insertAtCursor("${cbt_journal.checkins_section()}\n", false, true)
  end
}

All parts are moving on my space! I think for now is better to share as reply to this post instead a PR to the library.

2 Likes

Wow, that's really cool! I think the set-key and delete-key ops were added after I made this library; I'll definitely be taking your changes to fix the duplicate items issue.