Click History

Click History

Note that any click @ the end of any line (whether empty or not) won’t be recorded by event.listen "page:click". This might be a bug.

Alt Click or Ctrl Click or Click widgets (that are rendered) are not recorded.

Non-Clicks such as move cursor through keys (Vim lovers -_-||) are not recorded.

Key func
shift alt ← Move Back
shift alt → Move Forward
ctrl shift alt ← To the Start
ctrl shift alt → To the End
ctrl shift alt delete Clear History

One can set local enableTruncateDuringBrowse = true to enable Click to “Branch New” = “Overwrite Exsiting” Future History immediately, while browsing the click history.

local function getTimes()
  local t = datastore.get({"ClickTimes", "!"}) or {}
  return t.Ctimes or 1
end

local function setTimes(n)
  datastore.set({"ClickTimes", "!"}, { Ctimes = n })
end

local function getBrowse()
  local b = datastore.get({"ClickBrowse", "!"})
  if b then return b end
  local ct = getTimes()
  b = { index = ct, max = ct - 1, active = false }
  datastore.set({"ClickBrowse", "!"}, b)
  return b
end

local function setBrowse(b)
  datastore.set({"ClickBrowse", "!"}, b)
end

local function getRef(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  return rec.ref
end

local function setRef(idx, ref)
  datastore.set({"ClickHistory", tostring(idx)}, { ref = ref, ts = os.time() })
end

local function clearAllHistory()
  local Ctimes = getTimes()
  for i = 1, Ctimes do
    datastore.delete({"ClickHistory", tostring(i)})
  end
  setTimes(1)
  setBrowse({ index = 1, max = 0, active = false })
end

local enableTruncateDuringBrowse = false

local function appendHistory(ref)
  local Ctimes = getTimes()
  local lastRef = getRef(Ctimes - 1)
  
  if lastRef and lastRef == ref then
    return
  end

  local browse = getBrowse()

  if enableTruncateDuringBrowse and browse.active and browse.index <= browse.max then
    for i = browse.index + 1, browse.max do
       datastore.delete({"ClickHistory", tostring(i)})
    end
    Ctimes = browse.index + 1
    setTimes(Ctimes)
    browse.index = Ctimes
    browse.max = Ctimes
    setBrowse(browse)
  end

  setRef(Ctimes, ref)
  setTimes(Ctimes + 1)

  local newTimes = Ctimes + 1
  setBrowse({ index = newTimes, max = newTimes - 1, active = false })
end

local function navigateIndex(idx)
  local ref = getRef(idx)
  if not ref then
    return false
  end
  editor.navigate(ref)
  local pos = tonumber(ref:match("@(.*)"))
  if pos then
      editor.moveCursor(pos, true)
  end
  return true      
end

local function ensureBrowseSession()
  local b = getBrowse()
  if not b.active then
    local Ctimes = getTimes()
    b.max = Ctimes - 1
    b.index = Ctimes
    b.active = true
    setBrowse(b)
  end
  return getBrowse()
end

event.listen {
  name = "page:click",
  run = function(e)
    local d = e.data or {}
    local pageName = editor.getCurrentPage()
    local pos = d.pos
    if not pageName or not pos then return end

    local ref = string.format("%s@%d", pageName, pos)
    appendHistory(ref)

    if d.ctrlKey then
      editor.moveCursor(pos, true)
      editor.flashNotification("pos @ " .. tostring(pos))
      return
    end
  end
}

command.define {
  name = "Click History: Back",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    if b.index > b.max + 1 then
      b.index = b.max + 1
    end
    
    b.index = math.max(b.index - 1, 1)

    setBrowse(b)
    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Back: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowLeft",
  mac = "Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Forward",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = math.min(b.index + 1, b.max)
    setBrowse(b)

    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Forward: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowRight",
  mac = "Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: End",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    if max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end
    setBrowse({ index = max, max = max, active = false })
    if navigateIndex(max) then
      editor.flashNotification(string.format("End: %d / %d", max, max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowRight",
  mac = "Ctrl-Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: Start",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = 1
    setBrowse(b)
    
    if navigateIndex(1) then
      editor.flashNotification(string.format("Start: 1 / %d", b.max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowLeft",
  mac = "Ctrl-Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Clear",
  run = function()
    clearAllHistory()
    editor.flashNotification("Click History cleared.", "info")
  end,
  key = "Ctrl-Shift-Alt-Delete", 
  mac = "Ctrl-Shift-Alt-Delete",
  priority = 1,
}

local Ctimes = getTimes()
setBrowse({ index = Ctimes, max = Ctimes - 1, active = false })
  • Special behavior: When browsing the history, the selected line is centered by default!
  • A similar behavior has also been added for Ctrl + click.

Current history retention period

It will persist until IndexedDB is deleted (the history will not be removed even if you close SilverBullet). This feature is align with datastore api.

The exceptions are if you manually delete the browser history (clear your browser’s cached site data), perform a Client: Wipe Out, or switch to a different SilverBullet instance (Your new machine fetching the same notes).

Ideas coming from

I checked SiYuan has such functionality (many other note-taking apps don’t have).
So I built something alike in SilverBulelt.

1 Like

Thanks. It’s really practical!

1 Like

add a History-Picker command: Click History: Pick

local function getTimes()
  local t = datastore.get({"ClickTimes", "!"}) or {}
  return t.Ctimes or 1
end

local function setTimes(n)
  datastore.set({"ClickTimes", "!"}, { Ctimes = n })
end

local function getBrowse()
  local b = datastore.get({"ClickBrowse", "!"})
  if b then return b end
  local ct = getTimes()
  b = { index = ct, max = ct - 1, active = false }
  datastore.set({"ClickBrowse", "!"}, b)
  return b
end

local function setBrowse(b)
  datastore.set({"ClickBrowse", "!"}, b)
end

local function getRef(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  return rec.ref
end

local function setRef(idx, ref)
  datastore.set({"ClickHistory", tostring(idx)}, { ref = ref, ts = os.time() })
end

local function clearAllHistory()
  local Ctimes = getTimes()
  for i = 1, Ctimes do
    datastore.delete({"ClickHistory", tostring(i)})
  end
  setTimes(1)
  setBrowse({ index = 1, max = 0, active = false })
end

local enableTruncateDuringBrowse = false

local function appendHistory(ref)
  local Ctimes = getTimes()
  local lastRef = getRef(Ctimes - 1)
  
  if lastRef and lastRef == ref then
    return
  end

  local browse = getBrowse()

  if enableTruncateDuringBrowse and browse.active and browse.index <= browse.max then
    for i = browse.index + 1, browse.max do
       datastore.delete({"ClickHistory", tostring(i)})
    end
    Ctimes = browse.index + 1
    setTimes(Ctimes)
    browse.index = Ctimes
    browse.max = Ctimes
    setBrowse(browse)
  end

  setRef(Ctimes, ref)
  setTimes(Ctimes + 1)

  local newTimes = Ctimes + 1
  setBrowse({ index = newTimes, max = newTimes - 1, active = false })
end

local function navigateIndex(idx)
  local ref = getRef(idx)
  if not ref then
    return false
  end
  editor.navigate(ref)
  local pos = tonumber(ref:match("@(.*)$"))
  if pos then
      editor.moveCursor(pos, true)
  end
  return true      
end

local function ensureBrowseSession()
  local b = getBrowse()
  if not b.active then
    local Ctimes = getTimes()
    b.max = Ctimes - 1
    b.index = Ctimes
    b.active = true
    setBrowse(b)
  end
  return getBrowse()
end

event.listen {
  name = "page:click",
  run = function(e)
    local d = e.data or {}
    local pageName = editor.getCurrentPage()
    local pos = d.pos
    if not pageName or not pos then return end

    local ref = string.format("%s@%d", pageName, pos)
    appendHistory(ref)

    if d.ctrlKey then
      editor.moveCursor(pos, true)
      editor.flashNotification("pos @ " .. tostring(pos))
      return
    end
  end
}

command.define {
  name = "Click History: Back",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    if b.index > b.max + 1 then
      b.index = b.max + 1
    end
    
    b.index = math.max(b.index - 1, 1)

    setBrowse(b)
    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Back: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowLeft",
  mac = "Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Forward",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = math.min(b.index + 1, b.max)
    setBrowse(b)

    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Forward: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowRight",
  mac = "Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: End",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    if max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end
    setBrowse({ index = max, max = max, active = false })
    if navigateIndex(max) then
      editor.flashNotification(string.format("End: %d / %d", max, max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowRight",
  mac = "Ctrl-Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: Start",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = 1
    setBrowse(b)
    
    if navigateIndex(1) then
      editor.flashNotification(string.format("Start: 1 / %d", b.max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowLeft",
  mac = "Ctrl-Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Clear",
  run = function()
    clearAllHistory()
    editor.flashNotification("Click History cleared.", "info")
  end,
  key = "Ctrl-Shift-Alt-Delete", 
  mac = "Ctrl-Shift-Alt-Delete",
  priority = 1,
}

local Ctimes = getTimes()
setBrowse({ index = Ctimes, max = Ctimes - 1, active = false })

------------------------------------------------------------
-- Click History Picker Implementation
------------------------------------------------------------

command.define {
  name = "Click History: Pick",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    
    if max < 1 then
      editor.flashNotification("No click history found.", "warning")
      return
    end

    local historyItems = {}
    
    for i = max, 1, -1 do
      local ref = getRef(i)
      if ref then
        local pageName, pos = ref:match("^(.*)@(%d+)$")
        local displayName = ref
        
        if pageName and pos then
          displayName = string.format("[%d] %s @ %d", i, pageName, pos)
        else
           displayName = string.format("%d. %s", i, ref)
        end

        table.insert(historyItems, {
            id = i,
            name = displayName,
            ref = ref
        })
      end
    end

    local sel = editor.filterBox("Pick History", historyItems, "Search history...", "")

    if sel then
      local b = ensureBrowseSession()
      
      b.index = sel.id
      setBrowse(b)
      
      if navigateIndex(sel.id) then
        editor.flashNotification(string.format("Jumped to history: %d / %d", sel.id, max))
      end
    end
  end,
  key = "Ctrl-Alt-h",
  priority = 1,
}
1 Like

I suggest the following modification to display date-time in the pick history:

  • function getRef(idx) → return rec.ref .. “\t–\t” .. os.date(“%d/%m/%Y %H:%M:%S”, rec.ts)
  • function navigateIndex(idx) → local ref = getRef(idx):match(“^(.-)\t%-%-\t”)

The ideal would be to display date-time in a column but I don’t know how to do it …
Thanks for the history feature!

1 Like

Gotcha:

Input Nothing

Input PageName

Input DateTime

Input Order

Code

local function getTimes()
  local t = datastore.get({"ClickTimes", "!"}) or {}
  return t.Ctimes or 1
end

local function setTimes(n)
  datastore.set({"ClickTimes", "!"}, { Ctimes = n })
end

local function getBrowse()
  local b = datastore.get({"ClickBrowse", "!"})
  if b then return b end
  local ct = getTimes()
  b = { index = ct, max = ct - 1, active = false }
  datastore.set({"ClickBrowse", "!"}, b)
  return b
end

local function setBrowse(b)
  datastore.set({"ClickBrowse", "!"}, b)
end

local function getRef(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  return rec.ref
end

local function getTimeString(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  if rec.tstr and rec.tstr ~= "" then
    return rec.tstr
  end
  if rec.ts then
    return os.date("%Y-%m-%d %H:%M:%S", rec.ts)
  end
  return nil
end

local function setRef(idx, ref)
  local now = os.time()
  datastore.set(
    {"ClickHistory", tostring(idx)},
    {
      ref = ref,
      -- ts = now,
      tstr = os.date("%Y-%m-%d %H:%M:%S", now),
    }
  )
end

local function clearAllHistory()
  local Ctimes = getTimes()
  for i = 1, Ctimes do
    datastore.delete({"ClickHistory", tostring(i)})
  end
  setTimes(1)
  setBrowse({ index = 1, max = 0, active = false })
end

local enableTruncateDuringBrowse = false

local function appendHistory(ref)
  local Ctimes = getTimes()
  local lastRef = getRef(Ctimes - 1)
  
  if lastRef and lastRef == ref then
    return
  end

  local browse = getBrowse()

  if enableTruncateDuringBrowse and browse.active and browse.index <= browse.max then
    for i = browse.index + 1, browse.max do
       datastore.delete({"ClickHistory", tostring(i)})
    end
    Ctimes = browse.index + 1
    setTimes(Ctimes)
    browse.index = Ctimes
    browse.max = Ctimes
    setBrowse(browse)
  end

  setRef(Ctimes, ref)
  setTimes(Ctimes + 1)

  local newTimes = Ctimes + 1
  setBrowse({ index = newTimes, max = newTimes - 1, active = false })
end

local function navigateIndex(idx)
  local ref = getRef(idx)
  if not ref then
    return false
  end
  editor.navigate(ref)
  local pos = tonumber(ref:match("@(.*)$"))
  if pos then
      editor.moveCursor(pos, true)
  end
  return true      
end

local function ensureBrowseSession()
  local b = getBrowse()
  if not b.active then
    local Ctimes = getTimes()
    b.max = Ctimes - 1
    b.index = Ctimes
    b.active = true
    setBrowse(b)
  end
  return getBrowse()
end

event.listen {
  name = "page:click",
  run = function(e)
    local d = e.data or {}
    local pageName = editor.getCurrentPage()
    local pos = d.pos
    if not pageName or not pos then return end

    local ref = string.format("%s@%d", pageName, pos)
    appendHistory(ref)

    if d.ctrlKey then
      editor.moveCursor(pos, true)
      editor.flashNotification("pos @ " .. tostring(pos))
      return
    end
  end
}

command.define {
  name = "Click History: Back",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    if b.index > b.max + 1 then
      b.index = b.max + 1
    end
    
    b.index = math.max(b.index - 1, 1)

    setBrowse(b)
    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Back: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowLeft",
  mac = "Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Forward",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = math.min(b.index + 1, b.max)
    setBrowse(b)

    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Forward: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowRight",
  mac = "Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: End",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    if max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end
    setBrowse({ index = max, max = max, active = false })
    if navigateIndex(max) then
      editor.flashNotification(string.format("End: %d / %d", max, max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowRight",
  mac = "Ctrl-Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: Start",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = 1
    setBrowse(b)
    
    if navigateIndex(1) then
      editor.flashNotification(string.format("Start: 1 / %d", b.max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowLeft",
  mac = "Ctrl-Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Clear",
  run = function()
    clearAllHistory()
    editor.flashNotification("Click History cleared.", "info")
  end,
  key = "Ctrl-Shift-Alt-Delete", 
  mac = "Ctrl-Shift-Alt-Delete",
  priority = 1,
}

local Ctimes = getTimes()
setBrowse({ index = Ctimes, max = Ctimes - 1, active = false })

------------------------------------------------------------
-- Click History Picker Implementation
------------------------------------------------------------

command.define {
  name = "Click History: Pick",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    
    if max < 1 then
      editor.flashNotification("No click history found.", "warning")
      return
    end

    local historyItems = {}
    
    for i = max, 1, -1 do
      local ref = getRef(i)
      if ref then
        local pageName, pos = ref:match("^(.*)@(%d+)$")
        local displayName = ref
        local tstr = getTimeString(i) or ""
        
        if pageName and tstr then
          displayName = string.format("%d🖱️%s🕓%s", i, pageName, tstr)
        else
          displayName = string.format("%d. %s", i, ref)
        end

        table.insert(historyItems, {
          id = i,
          name = displayName,
          description = tstr .. "📍" .. pos,
          ref = ref
        })
      end
    end

    local sel = editor.filterBox(
      "Back to",
      historyItems,
      "Select a Click History...",
      "Page @ Pos where you Once Clicked"
    )

    if sel then
      local b = ensureBrowseSession()
      b.index = sel.id
      setBrowse(b)
      if navigateIndex(sel.id) then
        editor.flashNotification(string.format("Jumped to history: %d / %d", sel.id, max))
      end
    end
  end,
  key = "Ctrl-Alt-h",
  priority = 1,
}

SilverSearch reminds me that description can be searched as well, so I did some clean up:

local function getTimes()
  local t = datastore.get({"ClickTimes", "!"}) or {}
  return t.Ctimes or 1
end

local function setTimes(n)
  datastore.set({"ClickTimes", "!"}, { Ctimes = n })
end

local function getBrowse()
  local b = datastore.get({"ClickBrowse", "!"})
  if b then return b end
  local ct = getTimes()
  b = { index = ct, max = ct - 1, active = false }
  datastore.set({"ClickBrowse", "!"}, b)
  return b
end

local function setBrowse(b)
  datastore.set({"ClickBrowse", "!"}, b)
end

local function getRef(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  return rec.ref
end

local function getTimeString(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  if rec.tstr and rec.tstr ~= "" then
    return rec.tstr
  end
  if rec.ts then
    return os.date("%Y-%m-%d %H:%M:%S", rec.ts)
  end
  return nil
end

local function setRef(idx, ref)
  local now = os.time()
  datastore.set(
    {"ClickHistory", tostring(idx)},
    {
      ref = ref,
      -- ts = now,
      tstr = os.date("%Y-%m-%d %H:%M:%S", now),
    }
  )
end

local function clearAllHistory()
  local Ctimes = getTimes()
  for i = 1, Ctimes do
    datastore.delete({"ClickHistory", tostring(i)})
  end
  setTimes(1)
  setBrowse({ index = 1, max = 0, active = false })
end

local enableTruncateDuringBrowse = false

local function appendHistory(ref)
  local Ctimes = getTimes()
  local lastRef = getRef(Ctimes - 1)
  
  if lastRef and lastRef == ref then
    return
  end

  local browse = getBrowse()

  if enableTruncateDuringBrowse and browse.active and browse.index <= browse.max then
    for i = browse.index + 1, browse.max do
       datastore.delete({"ClickHistory", tostring(i)})
    end
    Ctimes = browse.index + 1
    setTimes(Ctimes)
    browse.index = Ctimes
    browse.max = Ctimes
    setBrowse(browse)
  end

  setRef(Ctimes, ref)
  setTimes(Ctimes + 1)

  local newTimes = Ctimes + 1
  setBrowse({ index = newTimes, max = newTimes - 1, active = false })
end

local function navigateIndex(idx)
  local ref = getRef(idx)
  if not ref then
    return false
  end
  editor.navigate(ref)
  local pos = tonumber(ref:match("@(.*)$"))
  if pos then
      editor.moveCursor(pos, true)
  end
  return true      
end

local function ensureBrowseSession()
  local b = getBrowse()
  if not b.active then
    local Ctimes = getTimes()
    b.max = Ctimes - 1
    b.index = Ctimes
    b.active = true
    setBrowse(b)
  end
  return getBrowse()
end

event.listen {
  name = "page:click",
  run = function(e)
    local d = e.data or {}
    local pageName = editor.getCurrentPage()
    local pos = d.pos
    if not pageName or not pos then return end

    local ref = string.format("%s@%d", pageName, pos)
    appendHistory(ref)

    if d.ctrlKey then
      editor.moveCursor(pos, true)
      editor.flashNotification("pos @ " .. tostring(pos))
      return
    end
  end
}

command.define {
  name = "Click History: Back",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    if b.index > b.max + 1 then
      b.index = b.max + 1
    end
    
    b.index = math.max(b.index - 1, 1)

    setBrowse(b)
    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Back: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowLeft",
  mac = "Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Forward",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = math.min(b.index + 1, b.max)
    setBrowse(b)

    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Forward: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowRight",
  mac = "Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: End",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    if max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end
    setBrowse({ index = max, max = max, active = false })
    if navigateIndex(max) then
      editor.flashNotification(string.format("End: %d / %d", max, max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowRight",
  mac = "Ctrl-Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: Start",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = 1
    setBrowse(b)
    
    if navigateIndex(1) then
      editor.flashNotification(string.format("Start: 1 / %d", b.max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowLeft",
  mac = "Ctrl-Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Clear",
  run = function()
    clearAllHistory()
    editor.flashNotification("Click History cleared.", "info")
  end,
  key = "Ctrl-Shift-Alt-Delete", 
  mac = "Ctrl-Shift-Alt-Delete",
  priority = 1,
}

local Ctimes = getTimes()
setBrowse({ index = Ctimes, max = Ctimes - 1, active = false })

------------------------------------------------------------
-- Click History Picker Implementation
------------------------------------------------------------

command.define {
  name = "Click History: Pick",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    
    if max < 1 then
      editor.flashNotification("No click history found.", "warning")
      return
    end

    local historyItems = {}
    
    for i = max, 1, -1 do
      local ref = getRef(i)
      if ref then
        local pageName, pos = ref:match("^(.*)@(%d+)$")
        local displayName = ref
        local tstr = getTimeString(i) or ""
        
        if pageName and tstr then
          displayName = string.format("%d 🖱️ %s", i, pageName)
        else
          displayName = string.format("%d. %s", i, ref)
        end

        table.insert(historyItems, {
          id = i,
          name = displayName,
          description = tstr .. " 📍 " .. pos,
          ref = ref
        })
      end
    end

    local sel = editor.filterBox(
      "Back to",
      historyItems,
      "Select a Click History...",
      "Page @ Pos where you Once Clicked"
    )

    if sel then
      local b = ensureBrowseSession()
      b.index = sel.id
      setBrowse(b)
      if navigateIndex(sel.id) then
        editor.flashNotification(string.format("Jumped to history: %d / %d", sel.id, max))
      end
    end
  end,
  key = "Ctrl-Alt-h",
  priority = 1,
}

Yess ! But the “page:click” event is both too vague (all clicks are recorded) and too precise (you have to click in a channel). Perhaps, an option (keyboard) to pause/restore position recording (in addition to page) would be useful. What do you think?

1 Like
  • add:Toggle Record Mode
    • default: Click to record
    • switch: Ctrl + Click to record — then Click does nothing, as you wish :wink:

local function getTimes()
  local t = datastore.get({"ClickTimes", "!"}) or {}
  return t.Ctimes or 1
end

local function setTimes(n)
  datastore.set({"ClickTimes", "!"}, { Ctimes = n })
end

local function getBrowse()
  local b = datastore.get({"ClickBrowse", "!"})
  if b then return b end
  local ct = getTimes()
  b = { index = ct, max = ct - 1, active = false }
  datastore.set({"ClickBrowse", "!"}, b)
  return b
end

local function setBrowse(b)
  datastore.set({"ClickBrowse", "!"}, b)
end

local function getRef(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  return rec.ref
end

local function getTimeString(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  if rec.tstr and rec.tstr ~= "" then
    return rec.tstr
  end
  if rec.ts then
    return os.date("%Y-%m-%d %H:%M:%S", rec.ts)
  end
  return nil
end

local function setRef(idx, ref)
  local now = os.time()
  datastore.set(
    {"ClickHistory", tostring(idx)},
    {
      ref = ref,
      -- ts = now,
      tstr = os.date("%Y-%m-%d %H:%M:%S", now),
    }
  )
end

local function clearAllHistory()
  local Ctimes = getTimes()
  for i = 1, Ctimes do
    datastore.delete({"ClickHistory", tostring(i)})
  end
  setTimes(1)
  setBrowse({ index = 1, max = 0, active = false })
end

local enableTruncateDuringBrowse = false

local function appendHistory(ref)
  local Ctimes = getTimes()
  local lastRef = getRef(Ctimes - 1)
  
  if lastRef and lastRef == ref then
    return
  end

  local browse = getBrowse()

  if enableTruncateDuringBrowse and browse.active and browse.index <= browse.max then
    for i = browse.index + 1, browse.max do
       datastore.delete({"ClickHistory", tostring(i)})
    end
    Ctimes = browse.index + 1
    setTimes(Ctimes)
    browse.index = Ctimes
    browse.max = Ctimes
    setBrowse(browse)
  end

  setRef(Ctimes, ref)
  setTimes(Ctimes + 1)

  local newTimes = Ctimes + 1
  setBrowse({ index = newTimes, max = newTimes - 1, active = false })
end

local function navigateIndex(idx)
  local ref = getRef(idx)
  if not ref then
    return false
  end
  editor.navigate(ref)
  local pos = tonumber(ref:match("@(.*)$"))
  if pos then
      editor.moveCursor(pos, true)
  end
  return true      
end

local function ensureBrowseSession()
  local b = getBrowse()
  if not b.active then
    local Ctimes = getTimes()
    b.max = Ctimes - 1
    b.index = Ctimes
    b.active = true
    setBrowse(b)
  end
  return getBrowse()
end

-- add:Get/Set Record Mode
-- nil/false: `Click` to record (default)
-- true: `Ctrl + Click` to record
local function getRecordMode()
  local ClickHistoryMode = datastore.get({"ClickHistoryMode", "!"}) or {}
  return ClickHistoryMode.currentMode or false
end

event.listen {
  name = "page:click",
  run = function(e)
    local d = e.data or {}
    local pageName = editor.getCurrentPage()
    local pos = d.pos
    if not pageName or not pos then return end

    local ref = string.format("%s@%d", pageName, pos)
    local ctrlRecordMode = getRecordMode()

    if ctrlRecordMode then
      if d.ctrlKey then
        appendHistory(ref)
        editor.moveCursor(pos, true)
        editor.flashNotification("pos @ " .. tostring(pos))
      end
    else
      if d.ctrlKey then
        editor.moveCursor(pos, true)
        editor.flashNotification("pos @ " .. tostring(pos))
      else
        appendHistory(ref)
      end
    end
  end
}

command.define {
  name = "Click History: Toggle Mode",
  key = "Ctrl-Shift-m", 
  priority = 1,
  run = function()
    local currentMode = getRecordMode()
    local newMode = not currentMode
    datastore.set({"ClickHistoryMode", "!"}, {currentMode = newMode})
    
    if newMode then
      editor.flashNotification("Mode switched: [Ctrl + Click] to record history.")
    else
      editor.flashNotification("Mode switched: [Click] to record history.")
    end
  end,
}

command.define {
  name = "Click History: Back",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    if b.index > b.max + 1 then
      b.index = b.max + 1
    end
    
    b.index = math.max(b.index - 1, 1)

    setBrowse(b)
    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Back: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowLeft",
  mac = "Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Forward",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = math.min(b.index + 1, b.max)
    setBrowse(b)

    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Forward: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowRight",
  mac = "Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: End",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    if max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end
    setBrowse({ index = max, max = max, active = false })
    if navigateIndex(max) then
      editor.flashNotification(string.format("End: %d / %d", max, max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowRight",
  mac = "Ctrl-Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: Start",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = 1
    setBrowse(b)
    
    if navigateIndex(1) then
      editor.flashNotification(string.format("Start: 1 / %d", b.max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowLeft",
  mac = "Ctrl-Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Clear",
  run = function()
    clearAllHistory()
    editor.flashNotification("Click History cleared.", "info")
  end,
  key = "Ctrl-Shift-Alt-Delete", 
  mac = "Ctrl-Shift-Alt-Delete",
  priority = 1,
}

local Ctimes = getTimes()
setBrowse({ index = Ctimes, max = Ctimes - 1, active = false })

------------------------------------------------------------
-- Click History Picker Implementation
------------------------------------------------------------

command.define {
  name = "Click History: Pick",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    
    if max < 1 then
      editor.flashNotification("No click history found.", "warning")
      return
    end

    local historyItems = {}
    
    for i = max, 1, -1 do
      local ref = getRef(i)
      if ref then
        local pageName, pos = ref:match("^(.*)@(%d+)$")
        local displayName = ref
        local tstr = getTimeString(i) or ""
        
        if pageName and tstr then
          displayName = string.format("%d 🖱️ %s", i, pageName)
        else
          displayName = string.format("%d. %s", i, ref)
        end

        table.insert(historyItems, {
          id = i,
          name = displayName,
          description = tstr .. " 📍 " .. pos,
          ref = ref
        })
      end
    end

    local sel = editor.filterBox(
      "Back to",
      historyItems,
      "Select a Click History...",
      "Page @ Pos where you Once Clicked"
    )

    if sel then
      local b = ensureBrowseSession()
      b.index = sel.id
      setBrowse(b)
      if navigateIndex(sel.id) then
        editor.flashNotification(string.format("Jumped to history: %d / %d", sel.id, max))
      end
    end
  end,
  key = "Ctrl-Alt-h",
  priority = 1,
}

Well done ! Thanks a lot.
I suggest 3 modes accessible with a single combination : Ctrl + Shift + m (cyclique) :

  • mode 1 : click: continuous recording (page + pos)
  • mode 2 : ctrl + click : one-off recording only (page + pos)
  • mode 3 : click: continuous recording (page only, without pos)

Thank you.

1 Like

The first two points have already been accomplished.
(or maybe you want 2 individual histories? one continuous/auto, the other discrete/manual?)

As for the third, the default Alt + ← and Alt + → shortcuts essentially achieve the same effect (yet combined with this plug, its behavior is a little subtle -_-||).

Still, given that page-view history is tracked independently of cursor location, the official Page Picker effectively provides this functionality, supported by an existing API/property: Expose Page( Picker)'s hidden attr: lastOpened? - #2 by ChenZhu-Xie, see also index.

Still, since you need it, I’ll help you realize this idea — the third — when I have time.

In this regard, I’ve noticed the following:

  • The ordering of entries in the Page Picker does not seem to perfectly match a strict descending sort of lastOpened.
  • The page-view history allows duplicates, whereas the Page Picker and any lastOpened-based ordering do not.

So… what exactly are we looking for? Or… maybe everything? … (not a question)

ok. I did two things in your code, on my space.

First, in getRecordMode, to create “ClickHistoryMode” in datastore :

  if ClickHistoryMode == {} then
    datastore.set({"ClickHistoryMode", "!"}, {false})
  end

Second, in event.listen to neutralize the position in the page with pos = 1 when I am in continuous mode with Click (hence only one record for each page):

   if ctrlRecordMode then
      local ref = string.format("%s@%d", pageName, pos)
...
    else
      local ref = string.format("%s@%d", pageName, 1)

Thank you for your development and help.

1 Like

add Click History: Page Picker

should help ease overly detailed records.
Even so, it still navigates by default to the last click on that page.
One can modify it to jump on page hierarchy rather than page@pos.

local function getTimes()
  local t = datastore.get({"ClickTimes", "!"}) or {}
  return t.Ctimes or 1
end

local function setTimes(n)
  datastore.set({"ClickTimes", "!"}, { Ctimes = n })
end

local function getBrowse()
  local b = datastore.get({"ClickBrowse", "!"})
  if b then return b end
  local ct = getTimes()
  b = { index = ct, max = ct - 1, active = false }
  datastore.set({"ClickBrowse", "!"}, b)
  return b
end

local function setBrowse(b)
  datastore.set({"ClickBrowse", "!"}, b)
end

local function getRef(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  return rec.ref
end

local function getTimeString(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  if rec.tstr and rec.tstr ~= "" then
    return rec.tstr
  end
  if rec.ts then
    return os.date("%Y-%m-%d %H:%M:%S", rec.ts)
  end
  return nil
end

local function setRef(idx, ref)
  local now = os.time()
  datastore.set(
    {"ClickHistory", tostring(idx)},
    {
      ref = ref,
      -- ts = now,
      tstr = os.date("%Y-%m-%d %H:%M:%S", now),
    }
  )
end

local function clearAllHistory()
  local Ctimes = getTimes()
  for i = 1, Ctimes do
    datastore.delete({"ClickHistory", tostring(i)})
  end
  setTimes(1)
  setBrowse({ index = 1, max = 0, active = false })
end

local enableTruncateDuringBrowse = false

local function appendHistory(ref)
  local Ctimes = getTimes()
  local lastRef = getRef(Ctimes - 1)
  
  if lastRef and lastRef == ref then
    return
  end

  local browse = getBrowse()

  if enableTruncateDuringBrowse and browse.active and browse.index <= browse.max then
    for i = browse.index + 1, browse.max do
       datastore.delete({"ClickHistory", tostring(i)})
    end
    Ctimes = browse.index + 1
    setTimes(Ctimes)
    browse.index = Ctimes
    browse.max = Ctimes
    setBrowse(browse)
  end

  setRef(Ctimes, ref)
  setTimes(Ctimes + 1)

  local newTimes = Ctimes + 1
  setBrowse({ index = newTimes, max = newTimes - 1, active = false })
end

local function navigateIndex(idx)
  local ref = getRef(idx)
  if not ref then
    return false
  end
  editor.navigate(ref)
  local pos = tonumber(ref:match("@(.*)$"))
  if pos then
      editor.moveCursor(pos, true)
  end
  return true      
end

local function ensureBrowseSession()
  local b = getBrowse()
  if not b.active then
    local Ctimes = getTimes()
    b.max = Ctimes - 1
    b.index = Ctimes
    b.active = true
    setBrowse(b)
  end
  return getBrowse()
end

-- add:Get/Set Record Mode
-- nil/false: `Click` to record (default)
-- true: `Ctrl + Click` to record
local function getRecordMode()
  local ClickHistoryMode = datastore.get({"ClickHistoryMode", "!"}) or {}
  return ClickHistoryMode.currentMode or false
end

event.listen {
  name = "page:click",
  run = function(e)
    local d = e.data or {}
    local pageName = editor.getCurrentPage()
    local pos = d.pos
    if not pageName or not pos then return end

    local ref = string.format("%s@%d", pageName, pos)
    local ctrlRecordMode = getRecordMode()

    if ctrlRecordMode then
      if d.ctrlKey then
        appendHistory(ref)
        editor.moveCursor(pos, true)
        editor.flashNotification("pos @ " .. tostring(pos))
      end
    else
      if d.ctrlKey then
        editor.moveCursor(pos, true)
        editor.flashNotification("pos @ " .. tostring(pos))
      else
        appendHistory(ref)
      end
    end
  end
}

command.define {
  name = "Click History: Toggle Mode",
  key = "Ctrl-Shift-m", 
  priority = 1,
  run = function()
    local currentMode = getRecordMode()
    local newMode = not currentMode
    datastore.set({"ClickHistoryMode", "!"}, {currentMode = newMode})
    
    if newMode then
      editor.flashNotification("Mode switched: [Ctrl + Click] to record history.")
    else
      editor.flashNotification("Mode switched: [Click] to record history.")
    end
  end,
}

command.define {
  name = "Click History: Back",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    if b.index > b.max + 1 then
      b.index = b.max + 1
    end
    
    b.index = math.max(b.index - 1, 1)

    setBrowse(b)
    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Back: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowLeft",
  mac = "Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Forward",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = math.min(b.index + 1, b.max)
    setBrowse(b)

    if navigateIndex(b.index) then
      editor.flashNotification(string.format("Forward: %d / %d", b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowRight",
  mac = "Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: End",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    if max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end
    setBrowse({ index = max, max = max, active = false })
    if navigateIndex(max) then
      editor.flashNotification(string.format("End: %d / %d", max, max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowRight",
  mac = "Ctrl-Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: Start",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = 1
    setBrowse(b)
    
    if navigateIndex(1) then
      editor.flashNotification(string.format("Start: 1 / %d", b.max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowLeft",
  mac = "Ctrl-Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Clear",
  run = function()
    clearAllHistory()
    editor.flashNotification("Click History cleared.", "info")
  end,
  key = "Ctrl-Shift-Alt-Delete", 
  mac = "Ctrl-Shift-Alt-Delete",
  priority = 1,
}

local Ctimes = getTimes()
setBrowse({ index = Ctimes, max = Ctimes - 1, active = false })

------------------------------------------------------------
-- Click History: Cursor Picker Implementation
------------------------------------------------------------

command.define {
  name = "Click History: Cursor Picker",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    
    if max < 1 then
      editor.flashNotification("No click history found.", "warning")
      return
    end

    local historyItems = {}
    
    for i = max, 1, -1 do
      local ref = getRef(i)
      if ref then
        local pageName, pos = ref:match("^(.*)@(%d+)$")
        local displayName = ref
        local tstr = getTimeString(i) or ""
        
        if pageName and tstr then
          displayName = string.format("%d 🖱️ %s", i, pageName)
        else
          displayName = string.format("%d. %s", i, ref)
        end

        table.insert(historyItems, {
          id = i,
          name = displayName,
          description = tstr .. " 📍 " .. pos,
          ref = ref
        })
      end
    end

    local sel = editor.filterBox(
      "Back to",
      historyItems,
      "Select a Click History...",
      "Page @ Pos where you Once Clicked"
    )

    if sel then
      local b = ensureBrowseSession()
      b.index = sel.id
      setBrowse(b)
      if navigateIndex(sel.id) then
        editor.flashNotification(string.format("Jumped to history: %d / %d", sel.id, max))
      end
    end
  end,
  key = "Ctrl-Alt-h",
  priority = 1,
}

------------------------------------------------------------
-- Click History: Page Picker Implementation
------------------------------------------------------------

command.define {
  name = "Click History: Page Picker",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    
    if max < 1 then
      editor.flashNotification("No click history found.", "warning")
      return
    end

    local historyItems, seen = {}, {}
    for i = max, 1, -1 do
      local ref = getRef(i)
      if ref then
        local pageName, pos = ref:match("^(.*)@(%d+)$")

        if not seen[pageName] then
          seen[pageName] = true
          local displayName = ref
          local tstr = getTimeString(i) or ""
          
          if pageName and tstr then
            displayName = string.format("%d 🖱️ %s", i, pageName)
          else
            displayName = string.format("%d. %s", i, ref)
          end
  
          table.insert(historyItems, {
            id = i,
            name = displayName,
            description = tstr .. " 📍 " .. pos,
            ref = ref
          })
        end
      end
    end

    local sel = editor.filterBox(
      "Back to",
      historyItems,
      "Select a Click History...",
      "Page @ Pos where you Once Clicked"
    )

    if sel then
      local b = ensureBrowseSession()
      b.index = sel.id
      setBrowse(b)
      if navigateIndex(sel.id) then
        editor.flashNotification(string.format("Jumped to history: %d / %d", sel.id, max))
      end
    end
  end,
  key = "Shift-Alt-h",
  priority = 1,
}
``
  • Refactored Click History: Page Picker
    • Repeated page came back alive (faithfully record history)
  • add 2 simbling shortcuts for it:
    • Click History: Page Back + Page Forward

Tip: Ctrl-Shift-Alt-←/→ to long march first,
then Shift-Alt-←/→ to pace/hop in small steps :wink:

  • add page name + emoji support

local function getTimes()
  local t = datastore.get({"ClickTimes", "!"}) or {}
  return t.Ctimes or 1
end

local function setTimes(n)
  datastore.set({"ClickTimes", "!"}, { Ctimes = n })
end

local function getBrowse()
  local b = datastore.get({"ClickBrowse", "!"})
  if b then return b end
  local ct = getTimes()
  b = { index = ct, max = ct - 1, active = false }
  datastore.set({"ClickBrowse", "!"}, b)
  return b
end

local function setBrowse(b)
  datastore.set({"ClickBrowse", "!"}, b)
end

local function getRef(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  return rec.ref
end

local function getTimeString(idx)
  local rec = datastore.get({"ClickHistory", tostring(idx)}) or {}
  if rec.tstr and rec.tstr ~= "" then
    return rec.tstr
  end
  if rec.ts then
    return os.date("%Y-%m-%d %H:%M:%S", rec.ts)
  end
  return nil
end

local function setRef(idx, ref)
  local now = os.time()
  datastore.set(
    {"ClickHistory", tostring(idx)},
    {
      ref = ref,
      -- ts = now,
      tstr = os.date("%Y-%m-%d %H:%M:%S", now),
    }
  )
end

local function clearAllHistory()
  local Ctimes = getTimes()
  for i = 1, Ctimes do
    datastore.delete({"ClickHistory", tostring(i)})
  end
  setTimes(1)
  setBrowse({ index = 1, max = 0, active = false })
end

local enableTruncateDuringBrowse = false

local function appendHistory(ref)
  local Ctimes = getTimes()
  local lastRef = getRef(Ctimes - 1)
  
  if lastRef and lastRef == ref then
    return
  end

  local browse = getBrowse()

  if enableTruncateDuringBrowse and browse.active and browse.index <= browse.max then
    for i = browse.index + 1, browse.max do
       datastore.delete({"ClickHistory", tostring(i)})
    end
    Ctimes = browse.index + 1
    setTimes(Ctimes)
    browse.index = Ctimes
    browse.max = Ctimes
    setBrowse(browse)
  end

  setRef(Ctimes, ref)
  setTimes(Ctimes + 1)

  local newTimes = Ctimes + 1
  setBrowse({ index = newTimes, max = newTimes - 1, active = false })
end

local function navigateIndex(idx)
  local ref = getRef(idx)
  if not ref then
    return false
  end
  editor.navigate(ref)
  local pos = tonumber(ref:match("@(.*)$"))
  if pos then
      editor.moveCursor(pos, true)
  end
  return true      
end

local function ensureBrowseSession()
  local b = getBrowse()
  if not b.active then
    local Ctimes = getTimes()
    b.max = Ctimes - 1
    b.index = Ctimes
    b.active = true
    setBrowse(b)
  end
  return getBrowse()
end

-- add:Get/Set Record Mode
-- true: `Ctrl + Click` to record
-- nil/false: `Click` to record (default)
local function getRecordMode()
  local ClickHistoryMode = datastore.get({"ClickHistoryMode", "!"}) or {}
  return ClickHistoryMode.currentMode or false
end

event.listen {
  name = "page:click",
  run = function(e)
    local d = e.data or {}
    local pageName = editor.getCurrentPage()
    local pos = d.pos
    if not pageName or not pos then return end

    local ref = string.format("%s@%d", pageName, pos)
    local ctrlRecordMode = getRecordMode()

    if ctrlRecordMode then
      if d.ctrlKey then
        appendHistory(ref)
        editor.moveCursor(pos, true)
        editor.flashNotification("pos @ " .. tostring(pos))
      end
    else
      if d.ctrlKey then
        editor.moveCursor(pos, true)
        editor.flashNotification("pos @ " .. tostring(pos))
      else
        appendHistory(ref)
      end
    end
  end
}

command.define {
  name = "Click History: Toggle Mode",
  key = "Ctrl-Shift-m", 
  priority = 1,
  run = function()
    local currentMode = getRecordMode()
    local newMode = not currentMode
    datastore.set({"ClickHistoryMode", "!"}, {currentMode = newMode})
    
    if newMode then
      editor.flashNotification("Mode switched: [Ctrl + Click] to record history.")
    else
      editor.flashNotification("Mode switched: [Click] to record history.")
    end
  end,
}

local function extractPageName(idx)
    local ref = getRef(idx)
    if not ref then return "" end
    return ref:match("^(.*)@") or ref
end

command.define {
  name = "Click History: Back",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    if b.index > b.max + 1 then
      b.index = b.max + 1
    end
    
    b.index = math.max(b.index - 1, 1)

    setBrowse(b)
    if navigateIndex(b.index) then
      local page = extractPageName(b.index)
      editor.flashNotification(string.format("📃%s🔙Back: %d / %d", page, b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowLeft",
  mac = "Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Forward",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = math.min(b.index + 1, b.max)
    setBrowse(b)

    if navigateIndex(b.index) then
      local page = extractPageName(b.index)
      editor.flashNotification(string.format("📃%s⏩Forward: %d / %d", page, b.index, b.max))
    end
  end,
  key = "Shift-Alt-ArrowRight",
  mac = "Shift-Alt-ArrowRight",
  priority = 1,
}

command.define {
  name = "Click History: End",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    if max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end
    setBrowse({ index = max, max = max, active = false })
    if navigateIndex(max) then
      local page = extractPageName(max)
      editor.flashNotification(string.format("📃%s🛑End: %d / %d", page, max, max))
    end
  end,
  key = "Shift-Alt-End",
  mac = "Shift-Alt-End",
  priority = 1,
}

command.define {
  name = "Click History: Start",
  run = function()
    local b = ensureBrowseSession()

    if b.max < 1 then
      editor.flashNotification("No history", "warning")
      return
    end

    b.index = 1
    setBrowse(b)
    
    if navigateIndex(1) then
      local page = extractPageName(1)
      editor.flashNotification(string.format("📃%s🟢Start: 1 / %d", page, b.max))
    end
  end,
  mac = "Shift-Alt-Home",
  key = "Shift-Alt-Home",
  priority = 1,
}

command.define {
  name = "Click History: Clear",
  run = function()
    clearAllHistory()
    editor.flashNotification("Click History cleared.", "info")
  end,
  key = "Ctrl-Shift-Alt-Delete", 
  mac = "Ctrl-Shift-Alt-Delete",
  priority = 1,
}

local Ctimes = getTimes()
setBrowse({ index = Ctimes, max = Ctimes - 1, active = false })

------------------------------------------------------------
-- Click History: Jump By Page (Last Position)
------------------------------------------------------------

command.define {
  name = "Click History: Page Back",
  run = function()
    local b = ensureBrowseSession()
    if b.max < 1 then return end

    local currentPage = extractPageName(b.index)
    local targetIndex = nil

    for i = b.index - 1, 1, -1 do
      local page = extractPageName(i)
      if page ~= "" and page ~= currentPage then
        targetIndex = i
        break
      end
    end

    if targetIndex then
      b.index = targetIndex
      setBrowse(b)
      if navigateIndex(b.index) then
        local page = extractPageName(b.index)
        editor.flashNotification(string.format("📃%s🔚Page Back: %d / %d", page, b.index, b.max))
      end
    else
      editor.flashNotification("No previous page found", "warning")
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowLeft",
  mac = "Ctrl-Shift-Alt-ArrowLeft",
  priority = 1,
}

command.define {
  name = "Click History: Page Forward",
  run = function()
    local b = ensureBrowseSession()
    if b.max < 1 then return end

    local currentPage = extractPageName(b.index)
    local nextPageIndexStart = nil
    local nextPageName = nil

    for i = b.index + 1, b.max do
      local page = extractPageName(i)
      if page ~= "" and page ~= currentPage then
        nextPageIndexStart = i
        nextPageName = page
        break
      end
    end

    if not nextPageIndexStart then
      editor.flashNotification("No next page found", "warning")
      return
    end

    local targetIndex = nextPageIndexStart
    for i = nextPageIndexStart + 1, b.max do
      local page = extractPageName(i)
      if page == nextPageName then
        targetIndex = i
      else
        break
      end
    end

    b.index = targetIndex
    setBrowse(b)
    if navigateIndex(b.index) then
      local page = extractPageName(b.index)
      editor.flashNotification(string.format("📃%s⏭️Page Forward: %d / %d", page, b.index, b.max))
    end
  end,
  key = "Ctrl-Shift-Alt-ArrowRight",
  mac = "Ctrl-Shift-Alt-ArrowRight",
  priority = 1,
}

------------------------------------------------------------
-- Click History: Cursor Picker Implementation
------------------------------------------------------------

command.define {
  name = "Click History: Cursor Picker",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    
    if max < 1 then
      editor.flashNotification("No click history found.", "warning")
      return
    end

    local historyItems = {}
    
    for i = max, 1, -1 do
      local ref = getRef(i)
      if ref then
        local pageName, pos = ref:match("^(.*)@(%d+)$")
        local displayName = ref
        local tstr = getTimeString(i) or ""
        
        if pageName and tstr then
          displayName = string.format("%d 🖱️ %s", i, pageName)
        else
          displayName = string.format("%d. %s", i, ref)
        end

        table.insert(historyItems, {
          id = i,
          name = displayName,
          description = tstr .. " 📍 " .. pos,
          ref = ref
        })
      end
    end

    local sel = editor.filterBox(
      "Back to",
      historyItems,
      "Select a Click History...",
      "Page @ Pos where you Once Clicked"
    )

    if sel then
      local b = ensureBrowseSession()
      b.index = sel.id
      setBrowse(b)
      if navigateIndex(sel.id) then
        local page = extractPageName(sel.id)
        editor.flashNotification(string.format("📃%s📍Rewind: %d / %d", page, sel.id, max))
      end
    end
  end,
  key = "Ctrl-Alt-h",
  priority = 1,
}

------------------------------------------------------------
-- Click History: Page Picker (Refactored)
------------------------------------------------------------

command.define {
  name = "Click History: Page Picker",
  run = function()
    local Ctimes = getTimes()
    local max = Ctimes - 1
    
    if max < 1 then
      editor.flashNotification("No click history found.", "warning")
      return
    end

    local historyItems = {}
    local nextIndexPageName = nil 

    for i = max, 1, -1 do
      local ref = getRef(i)
      if ref then
        local pageName, pos = ref:match("^(.*)@(%d+)$")
        if not pageName then pageName = ref end

        if pageName ~= nextIndexPageName then
          nextIndexPageName = pageName
          
          local displayName = ref
          local tstr = getTimeString(i) or ""
          
          if pageName and tstr then
            displayName = string.format("%d 🖱️ %s", i, pageName)
          else
            displayName = string.format("%d. %s", i, ref)
          end
  
          table.insert(historyItems, {
            id = i,
            name = displayName,
            description = tstr .. " 📍 " .. tostring(pos or "N/A"),
            ref = ref
          })
        end
      end
    end

    local sel = editor.filterBox(
      "Back to",
      historyItems,
      "Select a Page Jump Point...",
      "Jumps to each page's last click"
    )

    if sel then
      local b = ensureBrowseSession()
      b.index = sel.id
      setBrowse(b)
      if navigateIndex(sel.id) then
        local page = extractPageName(sel.id)
        editor.flashNotification(string.format("📃%s📍Rewind: %d / %d", page, sel.id, max))
      end
    end
  end,
  key = "Shift-Alt-h",
  priority = 1,
}

Who can tell why a click history has evolved into sth so unexpectedly intricate -_-||