425 lines
12 KiB
Markdown
425 lines
12 KiB
Markdown
---
|
|
author: malys
|
|
description: List of reusable functions.
|
|
name: "Library/Malys/Utilities"
|
|
tags: meta/library
|
|
share.uri: "https://github.com/malys/silverbullet-libraries/blob/main/src/Utilities.md"
|
|
share.hash: 004bb007
|
|
share.mode: pull
|
|
---
|
|
# Utilities
|
|
|
|
* `mls.getmeetingTitle()`: Extracts meeting title from page URL.
|
|
* `mls.embedUrl(url, w, h)`: Embeds a URL using an iframe (width & height optional).
|
|
* `mls.debug(message, prefix)`: Prints debug message (if `LOG_ENABLE` is true).
|
|
* `mls.getCodeBlock(page, blockId, token, text)`: Retrieves code from a Markdown code block by ID, with optional token replacement.
|
|
* `mls.parseISODate(isoDate)`: Parses an ISO date string to a timestamp (potential error with `xoffset`).
|
|
* `mls.getStdlibInternal()`: Gets a list of internal API keys (cached).
|
|
* `mls.positionToLineColumn(text, pos)`: Converts a text position to line/column numbers.
|
|
* `table.appendArray(a, b)`: Appends array `b` to array `a`.
|
|
* `table.unique(array)`: Returns unique elements from an array.
|
|
* `table.uniqueKVBy(array, keyFn)`: Returns unique key-value pairs based on a key function.
|
|
* `table.mergeKV(t1, t2)`: Merges two tables (recursive for nested tables).
|
|
* `table.map(t, fn)`: Applies a function to each element of an array.
|
|
* `table.toMd(data)`: Converts a table (or JSON) to a Markdown table.
|
|
|
|
## Debug
|
|
|
|
### How to enable debug mode
|
|
|
|
* Create space-lua with:
|
|
```lua
|
|
LOG_ENABLE=true
|
|
```
|
|
|
|
* Reload system.
|
|
* Open Chrome Console
|
|
* Add filter “[Client] [DEBUG]“
|
|
## Code
|
|
```space-lua
|
|
-- priority: 20
|
|
mls=mls or {}
|
|
|
|
-- Convert meeting note title
|
|
-- Extracts the meeting title from the current page URL.
|
|
-- Assumes the URL format is like "namespace/Meeting_Note Title".
|
|
-- Splits the URL by "/", takes the last part, and then splits that by "_"
|
|
-- to remove the "Meeting" prefix and joins the remaining parts with a space.
|
|
function mls.getmeetingTitle()
|
|
local t=string.split(string.split(editor.getCurrentPage(),"/")[#string.split(editor.getCurrentPage(),"/")],"_")
|
|
table.remove(t,1)
|
|
t=table.concat(t, " ")
|
|
return t
|
|
end
|
|
|
|
-- Embed external resources
|
|
-- Creates an HTML iframe to embed an external URL within the current page.
|
|
-- Allows specifying width and height, defaulting to "100%" and "400px" respectively.
|
|
function mls.embedUrl(specOrUrl,w,h)
|
|
local width = w or "100%"
|
|
local height = h or "400px"
|
|
return widget.html(dom.iframe {
|
|
src=specOrUrl,
|
|
style="width: " .. width .. "; height: " .. height
|
|
})
|
|
end
|
|
|
|
---------------------------------------------
|
|
---- Debug ---
|
|
---------------------------------------------
|
|
-- Pretty-print any Lua value (tables included)
|
|
-- Recursively prints a Lua value, handling tables with indentation.
|
|
-- Prevents infinite recursion by limiting the depth to 5.
|
|
local function dump(value, depth)
|
|
--print("[DEBUG][TYPE]"..type(value)) -- commented out debug line
|
|
depth = depth or 0
|
|
if type(value) ~= "table" or (type(value) == "string" and value ~= "[object Object]") then
|
|
return value
|
|
end
|
|
|
|
-- Prevent going too deep (avoid infinite recursion)
|
|
if depth > 5 then
|
|
return "{ ... }"
|
|
end
|
|
|
|
local indent = string.rep(" ", depth)
|
|
local next_indent = string.rep(" ", depth + 1)
|
|
|
|
local parts = {}
|
|
table.insert(parts, "{")
|
|
|
|
for k, v in pairs(value) do
|
|
local key = tostring(k)
|
|
local val
|
|
if type(v) == "table" then
|
|
val = dump(v, depth + 1)
|
|
else
|
|
val = v
|
|
end
|
|
|
|
table.insert(parts, next_indent .. tostring(key) .. " = " .. tostring(val) .. ",")
|
|
end
|
|
|
|
table.insert(parts, indent .. "}")
|
|
|
|
return table.concat(parts, "\n")
|
|
end
|
|
|
|
|
|
-- Debug function
|
|
-- Prints a debug message to the console if LOG_ENABLE is true.
|
|
-- Uses the dump function to pretty-print the message.
|
|
-- Allows specifying a prefix for the debug message.
|
|
function mls.debug(message, prefix)
|
|
if not LOG_ENABLE then
|
|
return message
|
|
end
|
|
local log_message = dump(message)
|
|
|
|
local result = "[DEBUG]"
|
|
if prefix then
|
|
result = result .. "[" .. tostring(prefix) .. "]"
|
|
end
|
|
|
|
result = result .. " " .. tostring(log_message)
|
|
print(result)
|
|
return result
|
|
end
|
|
|
|
|
|
---------------------------------------------
|
|
---- Code Block code extraction ---
|
|
---------------------------------------------
|
|
-- Get child of node
|
|
-- Helper function to find a child node of a given type within a parent node.
|
|
local getChild = function(node, type)
|
|
for _, child in ipairs(node.children) do
|
|
if child.type == type then
|
|
return child
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Get text of Node
|
|
-- Helper function to recursively extract the text content from a node.
|
|
local getText = function(node)
|
|
if not node then
|
|
return nil
|
|
end
|
|
if node.text then
|
|
return node.text
|
|
else
|
|
for _, child in ipairs(node.children) do
|
|
local text = getText(child)
|
|
if text then
|
|
return text
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Find codeblock
|
|
-- Recursively searches for a code block (FencedCode node) with a specific blockId in the node's children.
|
|
local findMyFence = function(node,blockId)
|
|
if not node.children then
|
|
return nil
|
|
end
|
|
for _, child in ipairs(node.children) do
|
|
--mls.debug(child) -- commented out debug line
|
|
if child.type == "FencedCode" then
|
|
local info = getText(getChild(child, "CodeInfo"))
|
|
--mls.debug(info) -- commented out debug line
|
|
if info and info:find(blockId) then
|
|
mls.debug(info)
|
|
return getChild(child, "CodeText")
|
|
end
|
|
end
|
|
local result= findMyFence(child,blockId)
|
|
if result ~=nil then
|
|
return result
|
|
end
|
|
--break -- for loop
|
|
end --for
|
|
end
|
|
|
|
-- Get code source in md codeblock
|
|
-- Parses a Markdown page, finds a code block with the given blockId, and returns its content.
|
|
-- Allows replacing a token within the code block with a new text value.
|
|
function mls.getCodeBlock (page,blockId,token,text)
|
|
local tree = markdown.parseMarkdown(space.readPage(page))
|
|
--mls.debug(tree) -- commented out debug line
|
|
if tree then
|
|
local fence = findMyFence(tree,blockId)
|
|
--mls.debug(fence) -- commented out debug line
|
|
if fence then
|
|
local result=fence.children[1].text
|
|
if token == nil or text==nil then
|
|
return result
|
|
else
|
|
return string.gsub(result, token, text)
|
|
end
|
|
end
|
|
end
|
|
return "Error"
|
|
end
|
|
|
|
-- Parse ISO Date
|
|
-- Parses an ISO date string into a Unix timestamp.
|
|
-- This function appears incomplete and has a potential error in the offset calculation.
|
|
-- The `xoffset` variable is not defined.
|
|
function mls.parseISODate(isoDate)
|
|
local pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:%d+:%d+([Z%+%-])(%d?%d?)%:?(%d?%d?)"
|
|
local year, month, day, hour, minute,
|
|
seconds, offsetsign, offsethour, offsetmin = json_date:match(pattern)
|
|
local timestamp = os.time{year = year, month = month,
|
|
day = day, hour = hour, min = minute, sec = seconds}
|
|
local offset = 0
|
|
if offsetsign ~= 'Z' then
|
|
offset = tonumber(offsethour) * 60 + tonumber(offsetmin)
|
|
if xoffset == "-" then offset = offset * -1 end
|
|
end
|
|
|
|
return timestamp + offset
|
|
end
|
|
|
|
-- Get Stdlib Internal
|
|
-- Retrieves a list of internal API keys from a remote file.
|
|
-- Caches the results to avoid repeated fetching.
|
|
-- The URL points to a file in the silverbulletmd/silverbullet repository.
|
|
function mls.getStdlibInternal()
|
|
if _G ~=nil then
|
|
return table.keys(_G)
|
|
end
|
|
-- get list of internal api TODO: remove
|
|
local KEY="stdlib_internal"
|
|
local result=mls.cache.ttl.CacheManager.get(KEY)
|
|
if result == nil then
|
|
local url = "https://raw.githubusercontent.com/silverbulletmd/silverbullet/refs/heads/main/client/space_lua/stdlib.ts"
|
|
local resp = net.proxyFetch(url)
|
|
if resp.status ~= 200 then
|
|
error("Failed to fetch file: " .. resp.status)
|
|
end
|
|
local content = resp.body
|
|
local results = {}
|
|
for line in string.split(content,"\n") do
|
|
local key = string.match(string.trim(line), 'env%.set%("(.*)"')
|
|
if key then
|
|
table.insert(results, key)
|
|
end
|
|
end
|
|
result=table.sort(results)
|
|
mls.cache.ttl.CacheManager.set(KEY,result)
|
|
end
|
|
return table.sort(result)
|
|
end
|
|
|
|
-- Position to Line Column
|
|
-- Converts a character position within a text string to a line and column number.
|
|
function mls.positionToLineColumn(text, pos)
|
|
local line = 1
|
|
local column = 0
|
|
local lastNewline = -1
|
|
|
|
for i = 0, pos - 1 do
|
|
if string.sub(text, i + 1, i + 1) == "\n" then
|
|
line = line + 1
|
|
lastNewline = i
|
|
column = 0
|
|
else
|
|
column = column + 1
|
|
end
|
|
end
|
|
|
|
return { line = line, column = column }
|
|
end
|
|
|
|
-----------------------------------------
|
|
-- TABLE
|
|
-----------------------------------------
|
|
-- Append Array
|
|
-- Appends all elements from one array to another.
|
|
function table.appendArray(a, b)
|
|
if a~= nil and b ~= nil then
|
|
for i = 1, #b do
|
|
table.insert(a, b[i])
|
|
end
|
|
end
|
|
return a
|
|
end
|
|
|
|
-- Unique
|
|
-- Returns a new array containing only the unique elements from the input array.
|
|
function table.unique(array)
|
|
local seen = {}
|
|
local result = {}
|
|
|
|
for i = 1, #array do
|
|
local v = array[i]
|
|
if not seen[v] then
|
|
seen[v] = true
|
|
result[#result + 1] = v
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
-- Unique KV By
|
|
-- Returns a new array containing only the unique key-value pairs from the input array,
|
|
-- based on a provided key function.
|
|
function table.uniqueKVBy(array, keyFn)
|
|
local seen = {}
|
|
local result = {}
|
|
|
|
for i = 1, #array do
|
|
local key = keyFn(array[i])
|
|
if not seen[key] then
|
|
seen[key] = true
|
|
result[#result + 1] = array[i]
|
|
end
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
-- Merge KV
|
|
-- Merges two tables, recursively merging nested tables.
|
|
-- If a key exists in both tables and the values are both tables, the nested tables are merged.
|
|
-- Otherwise, the value from the second table overwrites the value from the first table.
|
|
function table.mergeKV(t1, t2)
|
|
for k, v in pairs(t2) do
|
|
if type(v) == "table" and type(t1[k]) == "table" then
|
|
mergeTablesRecursive(t1[k], v)
|
|
else
|
|
t1[k] = v
|
|
end
|
|
end
|
|
return t1
|
|
end
|
|
|
|
-- Map
|
|
-- Applies a function to each element of an array and returns a new array with the results.
|
|
function table.map(t, fn)
|
|
local result = {}
|
|
for i, v in ipairs(t) do
|
|
result[i] = fn(v, i)
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- To Md
|
|
-- Converts a Lua table to a Markdown table string.
|
|
-- Handles both arrays of arrays and arrays of objects.
|
|
-- Can parse JSON or Lua strings as input.
|
|
function table.toMd(data)
|
|
local tbl
|
|
local input=string.trim(data)
|
|
-- If input is a string, try parsing as JSON
|
|
if string.startsWith(data,"{") then
|
|
-- Lua
|
|
tbl=spacelua.evalExpression(spacelua.parseExpression(data))
|
|
else
|
|
--JSON
|
|
tbl=js.tolua(js.window.JSON.parse(js.tojs(data)))
|
|
end
|
|
if #tbl == 0 then return "" end
|
|
|
|
local md = {}
|
|
|
|
-- Helper to convert a row to Markdown
|
|
local function rowToMd(row)
|
|
local cells = {}
|
|
for _, cell in ipairs(row) do
|
|
table.insert(cells, tostring(cell))
|
|
end
|
|
return "| " .. table.concat(cells, " | ") .. " |"
|
|
end
|
|
|
|
-- Handle array of objects
|
|
local first = tbl[1]
|
|
local headers
|
|
if type(first) == "table" and not (#first > 0) then
|
|
headers = {}
|
|
for k in pairs(first) do table.insert(headers, k) end
|
|
local rows = {headers}
|
|
for _, obj in ipairs(tbl) do
|
|
local row = {}
|
|
for _, h in ipairs(headers) do
|
|
table.insert(row, obj[h] or "")
|
|
end
|
|
table.insert(rows, row)
|
|
end
|
|
tbl = rows
|
|
end
|
|
|
|
-- Header
|
|
table.insert(md, rowToMd(tbl[1]))
|
|
|
|
-- Separator
|
|
local sep = {}
|
|
for _ = 1, #tbl[1] do table.insert(sep, "---") end
|
|
table.insert(md, "| " .. table.concat(sep, " | ") .. " |")
|
|
|
|
-- Data rows
|
|
for i = 2, #tbl do
|
|
table.insert(md, rowToMd(tbl[i]))
|
|
end
|
|
|
|
return table.concat(md, "\n")
|
|
end
|
|
```
|
|
|
|
## Changelog
|
|
|
|
* 2026-01-22:
|
|
* getStdlibInternal compatible with edge version https://community.silverbullet.md/t/risk-audit/3562/14
|
|
* 2026-01-20:
|
|
* feat: add table functions
|
|
* map
|
|
* mergeKV
|
|
* uniqueKVBy
|
|
* unique
|
|
* appendArray
|
|
* feat: add `mls.getStdlibInternal` fucntion
|
|
|