Link previews

Hi all, I didn't find any info on whether link previews would be or are already supported officially or through plugins. Are there any plans to support this at some point? Optionally triggered link preview cards can look nice on notes and help quickly find important links and distinguish them by glance in notes that are full of them or otherwise cluttered.

In the meantime, it wasn't too difficult to script basic link preview cards. If you miss the preview cards from other software, you can try the implementation below. It works in general cases, but is not perfect due to use of regex and non-comprehensive (dirty) approach.

space-style for the preview card css:

.link-preview {
  display: flex;
  flex-direction: row;
  text-decoration: none;
  color: inherit;
  border: 1px solid #ddd;
  border-radius: 10px;
  overflow: hidden;
  max-width: 600px;
  background: #fff;
  box-shadow: 0 4px 10px rgba(0,0,0,0.06);
  transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
  max-height: 200px;
}

.link-preview:hover {
  transform: translateY(-1px);
  box-shadow: 0 8px 18px rgba(0,0,0,0.12);
  border-color: #ccc;
}

.link-preview__image-wrapper {
  flex: 0 0 35%;
  max-width: 35%;
  aspect-ratio: 1.91 / 1; /*typical OG preview ratio*/
  overflow: hidden;
  background: #f3f3f3;
}

.link-preview__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.link-preview__content {
  flex: 1;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.link-preview__title {
  color: black;
  font-size: 16px;
  font-weight: 600;
  margin: 0;
  line-height: 1.3;
}

.link-preview__description {
  font-size: 14px;
  color: #555;
  margin: 0;
  line-height: 1.4;
  max-height: 3.2em; /*~2 lines*/
  overflow: hidden;
  text-overflow: ellipsis;
}

.link-preview__url {
  font-size: 12px;
  color: #888;
  margin-top: auto;
}

space-lua for preview generation:

function fetchPreview(url)
  local hash = crypto.sha256(url)
  local key = {'urlPreview',hash}
  local cache = datastore.get(key)
  local img, desc, title;
  if cache != nil then
    img = cache.img
    desc = cache.desc
    title = cache.title
  else
    local c = net.proxyFetch(url)
    img = string.matchRegex(c.body,'(?<="og:image" content=")[^"]*')
    img = (next(img) != nil) and img[1] or "https://placehold.co/1200x630?text=Image+Not+Found"
    title = string.matchRegex(c.body,'(?<="og:title" content=")[^"]*')
    if next(title) == nil then
      title = string.matchRegex(c.body,[[(?<=<title>).+?(?=<\/title>)]])
    end
    title = (next(title) != nil) and title[1] or "(No title)"
    desc = string.matchRegex(c.body,'(?<="og:description" content=")[^"]*')
    if next(desc) == nil then
      desc = string.matchRegex(c.body,'(?<=meta name="description" content=")[^"]*')
    end
    desc = (next(desc) != nil) and desc[1] or "(No description)"
    datastore.set(key,{img = img, title = title, desc = desc})
  end
  
  return widget.html(dom.a{
  href = url,
  class = "link-preview",
  target="_blank",
  rel="noopener noreferrer",

  dom.div{
    class = "link-preview__image-wrapper",

    dom.img{
      src = img,
      alt = title,
      class = "link-preview__image"
    }
  },

  dom.div{
    class = "link-preview__content",

    dom.h3{
      class = "link-preview__title",
      title
    },

    dom.p{
      class = "link-preview__description",
      desc
    },

    dom.span{
      class = "link-preview__url",
      url
    }
  }
  })
end

You can then insert a link preview with ${fetchPreview("https://github.com/silverbulletmd")} for example. It should work with arbitrary URLs.

6 Likes

Nice! Yes I have plans to implement something like this “natively” likely when dumping in a plain URL in a page, similar to how Discourse does this, and this implementation gives some hints on how to do it. Thanks!

4 Likes

Sounds great, thanks! I think in the future, if native functionality is added, this could be something that's enabled per-link with an additional specifier or something, since not every link needs a preview in general.

I wanted to ask also if it's possible to have the inline lua snippet run only once when first encountered in the view? Now it seems that when scrolling back and forth, the preview fetch is triggered each time as the link appears which is unnecessary. Would be nice if one can limit the fetch to just once.

1 Like

You could cache the result using the datastore api: datastore

2 Likes

Thanks for bringing up this api, I've updated the original post with a datastore enhancement. It now doesn't reload each time one scrolls past.

2 Likes