Click History

  • 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,
}