🧩 Plug-in: “Paste: Smart URL”

:puzzle_piece: Plug-in: “Paste: Smart URL (via Prompt)”

Enhances pasting URL into the editor by intelligently formatting it as either :link: Normal link: [](url) or :framed_picture: Image link: ![](url), depending on the url type. Lastly, place the cursor inside [].

command.define {
  name = "Paste: Smart URL (via Prompt)",
  key = "Alt-v",
  run = function()
    -- Ask the user to paste the URL into a prompt dialog
    local input = editor.prompt("Enter or paste URL", "")
    if not input then
      editor.flashNotification("Cancelled", "warn")
      return
    end

    -- Trim whitespace
    local clip = input:match("^%s*(.-)%s*$")
    if clip == "" then
      editor.flashNotification("Empty content", "warn")
      return
    end

    -- Basic URL check: http/https, www., or data:image/
    local function isUrl(u)
      return u:match("^https?://")
          or u:match("^www%.")
          or u:match("^data:image/")
    end

    -- Add scheme for bare www.
    local function ensureScheme(u)
      if u:match("^www%.") then return "https://" .. u end
      return u
    end

    -- Image URL check: ignore ? / #; also allow data:image/
    local function isImageUrl(u)
      if u:match("^data:image/") then return true end
      local path = (u:match("^[^%?#]+") or u):lower()
      return path:match("%.png$") or path:match("%.jpe?g$") or
             path:match("%.gif$") or path:match("%.webp$") or
             path:match("%.bmp$") or path:match("%.tiff?$") or
             path:match("%.svg$")
    end

    if not isUrl(clip) then
      editor.flashNotification("Not a URL", "warn")
      return
    end

    local url = ensureScheme(clip)
    local snippet = isImageUrl(url) and string.format("![](%s)", url)
                                     or string.format("[](%s)",  url)

    -- Remember insertion position (selection-aware), insert, then move cursor inside []
    local sel = editor.getSelection and editor.getSelection() or nil
    local startPos = (sel and sel.from) or editor.getCursor()

    -- Avoid re-centering the view during insertion
    editor.insertAtCursor(snippet, false)

    -- If it's an image link, '[' is the 2nd char ("![](...)"); otherwise it's the 1st ("[](...)")
    local delta = (snippet:sub(1,1) == "!") and 2 or 1
    local targetPos = startPos + delta

    -- Move caret to inside the brackets without changing the view
    if editor.moveCursor then
      editor.moveCursor(targetPos, false)  -- center=false
    elseif editor.setSelection then
      editor.setSelection(targetPos, targetPos)
    end

    editor.flashNotification("Inserted smart link")
  end
}

Were SB to allow direct invocation of editor.copyFromClipboard(), the task could be performed in one step via Alt-v, yet at present it seems feasible only through a editor.prompt(). Is this limitation motivated by security concerns?

4 Likes

Regarding images, here’s a small trick:

When right-click on an image on a website and choose Copy image, one can directly paste it into SB with Ctrl + V, and it will automatically appear in the ![img-full-name](url) format, along with img’s full name.

However, if you right-click on an image and choose Copy image address, you might need to use Alt + V above, to paste it in the same ![](url) format.

So… it seems that this plugin isn’t particularly useful for now: SB has already taken care of this functionality.

Smarter URL is Ready

command.define {
  name = "Paste: Smart URL (via Prompt)",
  key = "Alt-v",
  run = function()
    -- Ask the user to paste the URL into a prompt dialog
    local input = editor.prompt("Enter or paste URL", "")
    if not input then
      editor.flashNotification("Cancelled", "warn")
      return
    end

    -- Trim whitespace
    local clip = input:match("^%s*(.-)%s*$")
    if clip == "" then
      editor.flashNotification("Empty content", "warn")
      return
    end

    -- Basic URL check: http/https, www., or data:image/
    local function isUrl(u)
      return u:match("^https?://")
          or u:match("^www%.")
          or u:match("^data:image/")
    end

    -- Add scheme for bare www.
    local function ensureScheme(u)
      if u:match("^www%.") then return "https://" .. u end
      return u
    end

    -- Image URL check: ignore ? / #; also allow data:image/
    local function isImageUrl(u)
      if u:match("^data:image/") then return true end
      local path = (u:match("^[^%?#]+") or u):lower()
      return path:match("%.png$") or path:match("%.jpe?g$") or
             path:match("%.gif$") or path:match("%.webp$") or
             path:match("%.bmp$") or path:match("%.tiff?$") or
             path:match("%.svg$")
    end

    if not isUrl(clip) then
      editor.flashNotification("Not a URL", "warn")
      return
    end

    -- Helpers for title/tags
    local function urldecode(s)
      s = s:gsub("%+", " ")
      return (s:gsub("%%(%x%x)", function(h)
        local n = tonumber(h, 16)
        return n and string.char(n) or ("%%" .. h)
      end))
    end

    local function trim(s)
      return (s and s:match("^%s*(.-)%s*$")) or s
    end

    local function is_numeric(s) return s:match("^%d+$") ~= nil end

    local TLD_IGNORE = {
      com=true, org=true, net=true, io=true, md=true, app=true, dev=true, edu=true, gov=true,
      cn=true, uk=true, co=true, jp=true, de=true, fr=true, ru=true, nl=true, xyz=true,
      info=true, me=true, tv=true, cc=true, ai=true, us=true, ca=true, au=true, ["in"]=true,
      site=true, top=true, cloud=true, shop=true, blog=true,
      www=true  -- also ignore www label
    }

    local function split(str, pat)
      local t = {}
      str:gsub("([^" .. pat .. "]+)", function(c) t[#t+1] = c end)
      return t
    end

    local function parse_host(u)
      -- extract host from URL
      -- 1) Drop scheme
      local no_scheme = u:gsub("^[a-zA-Z][a-zA-Z0-9+.-]*://", "")
      -- 2) Stop at first / or ? or #
      local host = no_scheme:match("^([^/%?#]+)") or ""
      return host:lower()
    end

    local function build_tags_from_host(host)
      local parts = split(host, "%.")
      local out = {}
      local seen = {}
      for _, p in ipairs(parts) do
        local label = p:lower()
        if not TLD_IGNORE[label] and not seen[label] and label ~= "" then
          out[#out+1] = "#" .. label
          seen[label] = true
        end
      end
      return table.concat(out, " ")
    end

    local function last_non_numeric_segment(path_parts)
      for i = #path_parts, 1, -1 do
        local seg = path_parts[i]
        if seg and seg ~= "" and not is_numeric(seg) then
          return seg
        end
      end
      return nil
    end

    -- title_from_url:
    local function title_from_url(u)
      local path = (u:match("^https?://[^/%?#]+(/[^?#]*)")
                 or u:match("^www%.[^/%?#]+(/[^?#]*)")
                 or "") or ""
      local parts = {}
      for seg in path:gmatch("([^/]+)") do
        parts[#parts+1] = seg
      end
      local slug = last_non_numeric_segment(parts)

      if slug then
        slug = urldecode(slug or "")
        slug = slug:gsub("%-", " ")
        slug = trim((slug:gsub("%s+", " ")))
      else
        slug = ""
      end

      return slug
    end

    local url = ensureScheme(clip)

    -- Case 1: images -> keep original behavior
    if isImageUrl(url) then
      local snippet = string.format("![](%s)", url)

      -- Remember insertion position (selection-aware), insert, then move cursor inside []
      local sel = editor.getSelection and editor.getSelection() or nil
      local startPos = (sel and (sel.from or sel.start)) or editor.getCursor()
      editor.insertAtCursor(snippet, false)

      local targetPos = startPos + 2 -- "![](...)" -> '[' is the 2nd character
      if editor.moveCursor then
        editor.moveCursor(targetPos, false)
      elseif editor.setSelection then
        editor.setSelection(targetPos, targetPos)
      end
      editor.flashNotification("Inserted smart image link")
      return
    end

    -- Case 2: web URL -> build [title](url) + tags (highest priority for non-image)
    local host = parse_host(url)
    local tags = build_tags_from_host(host)       -- e.g. "#community #silverbullet" or "#tex #stackexchange"
    local title = title_from_url(url)

    local suffix = (tags ~= "" and (" " .. tags)) or ""
    local snippet = string.format("[%s](%s)%s", title, url, suffix)

    -- Remember insertion position (selection-aware), insert
    local sel = editor.getSelection and editor.getSelection() or nil
    local startPos = (sel and (sel.from or sel.start)) or editor.getCursor()
    editor.insertAtCursor(snippet, false)

    local function is_nonempty(s) return s and trim(s) ~= "" end
    local targetPos
    if is_nonempty(title) then
      targetPos = startPos + #snippet
    else
      targetPos = startPos + 1 + #title
    end

    if editor.moveCursor then
      editor.moveCursor(targetPos, false)
    elseif editor.setSelection then
      editor.setSelection(targetPos, targetPos)
    end

    editor.flashNotification("Inserted titled link with tags")
  end
}

@ my space/CONFIG/Paste as/Markdown/Image Url

2 Likes

What do you think to not prompt but get copied url in clipboard and smart paste directly?

local input= js.window.navigator.clipboard.readText()
Could you give us the path of code source in your repository: “xczphysics_SilverBullet/CONFIG/Paste as/Markdown/Image Url.md at main · ChenZhu-Xie/xczphysics_SilverBullet · GitHub” ? @ChenZhu-Xie

1 Like

None understands me as well as @Malys does — thank you sincerely!

For quite some time, I was searching for an API analogous to editor.copyFromClipboard() but never succeeded. I had presumed its omission was an intentional safeguard against clipboard interception, thus breaking the symmetry with editor.copyToClipboard().

Yes, the correct approach is simply to use input = js.window.navigator.clipboard.readText(), which not only reduces two keystrokes but performs superbly. I did’t know this shortcut before!

Concerning the proposal to set a GitHub repository as the default: Previously, my shortcut key merely produced a shareable current page URL based on its page.name; I will now upgrade it to generate three types of links — local(folder/file), weblink(Url), and GitHub repository ones.

1 Like

Really deepwiki help me a lot to find supported api function

1 Like

add: Wiki detect + Alias paste

function getSelectedText()
  local sel = editor.getSelection()
  if not sel or sel.from == sel.to then return nil end
  local text = editor.getText()
  return text:sub(sel.from + 1, sel.to)
end

function setSelectedText(newText)
  local sel = editor.getSelection()
  if not sel or sel.from == sel.to then return nil end
  editor.replaceRange(sel.from, sel.to, newText)
end

command.define {
  name = "Paste: Smart URL (via Prompt)",
  key = "Alt-v",
  run = function()
    local input = js.window.navigator.clipboard.readText()
    local clip = input:match("^%s*(.-)%s*$")
    if clip == "" then
      editor.flashNotification("Empty content", "warn")
      return
    end

    -- URL 检查
    local function isUrl(u)
      return u:match("^https?://")
          or u:match("^www%.")
          or u:match("^data:image/")
    end

    local function ensureScheme(u)
      if u:match("^www%.") then return "https://" .. u end
      return u
    end

    local function isImageUrl(u)
      if u:match("^data:image/") then return true end
      local path = (u:match("^[^%?#]+") or u):lower()
      return path:match("%.png$") or path:match("%.jpe?g$") or
             path:match("%.gif$") or path:match("%.webp$") or
             path:match("%.bmp$") or path:match("%.tiff?$") or
             path:match("%.svg$")
    end

    ----------------------------------------------------------------
    -- ✨ add: Wiki detect + Alias paste
    ----------------------------------------------------------------
    if not isUrl(clip) then
      local wiki_content = clip:match("%[%[([^%]]+)%]%]")
      if wiki_content then
        local selected = getSelectedText()
        if selected and selected ~= "" then
          -- selected:[[content|selected]]
          setSelectedText(string.format("[[%s|%s]]", wiki_content, selected))
          editor.flashNotification("Inserted wiki alias link")
          return
        else
          -- no selection:[[content|]]
          local snippet = string.format("[[%s|]]", wiki_content)
          local pos = editor.getCursor()
          editor.insertAtCursor(snippet, false)
          if editor.moveCursor then
            editor.moveCursor((pos + #string.format("[[%s|", wiki_content)), false)
          elseif editor.setSelection then
            local target = pos + #string.format("[[%s|", wiki_content)
            editor.setSelection(target, target)
          end
          editor.flashNotification("Inserted wiki link placeholder")
          return
        end
      end
      editor.flashNotification("Not a URL or wiki link", "warn")
      return
    end
    ----------------------------------------------------------------

    -- Helpers
    local function urldecode(s)
      s = s:gsub("%+", " ")
      return (s:gsub("%%(%x%x)", function(h)
        local n = tonumber(h, 16)
        return n and string.char(n) or ("%%" .. h)
      end))
    end

    local function trim(s)
      return (s and s:match("^%s*(.-)%s*$")) or s
    end

    local function is_numeric(s) return s:match("^%d+$") ~= nil end

    local TLD_IGNORE = {
      com=true, org=true, net=true, io=true, md=true, app=true, dev=true, edu=true, gov=true,
      cn=true, uk=true, co=true, jp=true, de=true, fr=true, ru=true, nl=true, xyz=true,
      info=true, me=true, tv=true, cc=true, ai=true, us=true, ca=true, au=true, ["in"]=true,
      site=true, top=true, cloud=true, shop=true, blog=true,
      www=true
    }

    local function split(str, pat)
      local t = {}
      str:gsub("([^" .. pat .. "]+)", function(c) t[#t+1] = c end)
      return t
    end

    local function parse_host(u)
      local no_scheme = u:gsub("^[a-zA-Z][a-zA-Z0-9+.-]*://", "")
      local host = no_scheme:match("^([^/%?#]+)") or ""
      return host:lower()
    end

    local function build_tags_from_host(host)
      local parts = split(host, "%.")
      local out, seen = {}, {}
      for _, p in ipairs(parts) do
        local label = p:lower()
        if not TLD_IGNORE[label] and not seen[label] and label ~= "" then
          out[#out+1] = "#" .. label
          seen[label] = true
        end
      end
      return table.concat(out, " ")
    end

    local function last_non_numeric_segment(path_parts)
      for i = #path_parts, 1, -1 do
        local seg = path_parts[i]
        if seg and seg ~= "" and not is_numeric(seg) then
          return seg
        end
      end
      return nil
    end

    local function title_from_url(u)
      local path = (u:match("^https?://[^/%?#]+(/[^?#]*)")
                 or u:match("^www%.[^/%?#]+(/[^?#]*)")
                 or "") or ""
      local parts = {}
      for seg in path:gmatch("([^/]+)") do
        parts[#parts+1] = seg
      end
      local slug = last_non_numeric_segment(parts)
      if slug then
        slug = urldecode(slug or "")
        slug = slug:gsub("%-", " ")
        slug = trim((slug:gsub("%s+", " ")))
      else
        slug = ""
      end
      return slug
    end

    local url = ensureScheme(clip)

    -- img URL
    if isImageUrl(url) then
      local snippet = string.format("![](%s)", url)
      editor.insertAtCursor(snippet, false)
      editor.moveCursor(editor.getCursor() - #snippet + 2, false)
      editor.flashNotification("Inserted smart image link")
      return
    end

    -- web URL
    local host = parse_host(url)
    local tags = build_tags_from_host(host)
    local title = title_from_url(url)
    local suffix = (tags ~= "" and (" " .. tags)) or ""
    local snippet = string.format("[%s](%s)%s", title, url, suffix)

    editor.insertAtCursor(snippet, false)
    editor.flashNotification("Inserted titled link with tags")
  end
}

A series of optimized infrastructure shortcuts are being developed to streamline the daily workflow, above is one example.

Different functions are integrated into a single key, yet they remain compatible. I strive to maximize the informational density in the design of the shortcuts.

A single key should, whenever possible, serve multiple roles.