create a link embed
- proof of concept of a separate web components package - link embed has icon/title/subtitle - web components are registered just before app mounts, making them available in the markdown as well as the app
This commit is contained in:
parent
6ffe5939a5
commit
75228e1a3a
9 changed files with 141 additions and 17 deletions
7
libs/embeds/package.json
Normal file
7
libs/embeds/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@goldenwere/static-web-templates-embeds",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts"
|
||||
}
|
1
libs/embeds/src/index.ts
Normal file
1
libs/embeds/src/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './link'
|
101
libs/embeds/src/link.ts
Normal file
101
libs/embeds/src/link.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
import type { AttributeObserver } from './observer'
|
||||
|
||||
export type LinkEmbedAttributes = typeof LinkEmbed.observedAttributes[number]
|
||||
|
||||
/**
|
||||
* A link embed is a link with a title and an optional icon + subtitle
|
||||
*/
|
||||
export class LinkEmbed extends HTMLElement {
|
||||
static observedAttributes = [
|
||||
'icon',
|
||||
'title',
|
||||
'subtitle',
|
||||
'href',
|
||||
'target',
|
||||
] as const
|
||||
|
||||
_observers: AttributeObserver<LinkEmbedAttributes> = {} as any
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
|
||||
const { ownerDocument } = this
|
||||
|
||||
const _link = ownerDocument.createElement('a')
|
||||
_link.setAttribute('rel', 'noreferrer noopener')
|
||||
this._observers.href = {
|
||||
element: _link,
|
||||
observer: (oldVal, newVal) => {
|
||||
const { element } = this._observers.href
|
||||
element!.setAttribute('href', newVal)
|
||||
},
|
||||
}
|
||||
this._observers.target = {
|
||||
element: _link,
|
||||
observer: (oldVal, newVal) => {
|
||||
const { element } = this._observers.href
|
||||
if (!!newVal) {
|
||||
element!.setAttribute('target', newVal)
|
||||
} else {
|
||||
element!.removeAttribute('target')
|
||||
}
|
||||
},
|
||||
}
|
||||
this.append(_link)
|
||||
|
||||
const _icon = ownerDocument.createElement('img')
|
||||
_icon.alt = '' // decorative
|
||||
this._observers.icon = {
|
||||
element: _icon,
|
||||
observer: (oldVal, newVal) => {
|
||||
const { element } = this._observers.icon
|
||||
if (!!newVal) {
|
||||
element!.setAttribute('src', newVal)
|
||||
} else {
|
||||
element!.style.display = 'hidden'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const _textWrapper = ownerDocument.createElement('div')
|
||||
_textWrapper.classList.add('text')
|
||||
_link.append(_textWrapper)
|
||||
|
||||
const _title = ownerDocument.createElement('p')
|
||||
this._observers.title = {
|
||||
element: _title,
|
||||
observer: (oldVal, newVal) => {
|
||||
const { element } = this._observers.title
|
||||
element!.innerHTML = newVal
|
||||
},
|
||||
}
|
||||
|
||||
const _subtitle = ownerDocument.createElement('p')
|
||||
this._observers.subtitle = {
|
||||
element: _subtitle,
|
||||
observer: (oldVal, newVal) => {
|
||||
const { element } = this._observers.subtitle
|
||||
if (!!newVal) {
|
||||
element!.innerHTML = newVal
|
||||
} else {
|
||||
element!.style.display = 'hidden'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_textWrapper.append(
|
||||
_title,
|
||||
_subtitle,
|
||||
)
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: LinkEmbedAttributes, oldVal: any, newVal: any) {
|
||||
this._observers[name].observer(oldVal, newVal)
|
||||
}
|
||||
}
|
||||
|
||||
export const registerLinkEmbed = () => {
|
||||
if (!customElements.get('link-embed')) {
|
||||
customElements.define('link-embed', LinkEmbed)
|
||||
}
|
||||
}
|
7
libs/embeds/src/observer.d.ts
vendored
Normal file
7
libs/embeds/src/observer.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
export type AttributeObserver<T extends string> = Record<T, {
|
||||
element?: HTMLElement,
|
||||
observer: (
|
||||
oldVal: any,
|
||||
newVal: any,
|
||||
) => void
|
||||
}>
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "types",
|
||||
"name": "@goldenwere/static-web-templates-types",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
|
|
23
package-lock.json
generated
23
package-lock.json
generated
|
@ -12,7 +12,11 @@
|
|||
"libs/*"
|
||||
]
|
||||
},
|
||||
"libs/embeds": {
|
||||
"version": "0.0.0"
|
||||
},
|
||||
"libs/types": {
|
||||
"name": "@goldenwere/static-web-templates-types",
|
||||
"version": "0.0.0"
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
|
@ -438,12 +442,8 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@goldenwere/static-web-templates-frontend": {
|
||||
"resolved": "projects/frontend",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@goldenwere/static-web-templates-sites": {
|
||||
"resolved": "projects/sites",
|
||||
"node_modules/@goldenwere/static-web-templates-embeds": {
|
||||
"resolved": "libs/embeds",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@goldenwere/static-web-templates-types": {
|
||||
|
@ -1518,6 +1518,10 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/frontend": {
|
||||
"resolved": "projects/frontend",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||
|
@ -2741,6 +2745,10 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/sites": {
|
||||
"resolved": "projects/sites",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
@ -3351,9 +3359,9 @@
|
|||
}
|
||||
},
|
||||
"projects/frontend": {
|
||||
"name": "@goldenwere/static-web-templates-frontend",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"@goldenwere/static-web-templates-embeds": "*",
|
||||
"@goldenwere/static-web-templates-types": "*",
|
||||
"@types/dompurify": "3.0.5",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
|
@ -3381,7 +3389,6 @@
|
|||
}
|
||||
},
|
||||
"projects/sites": {
|
||||
"name": "@goldenwere/static-web-templates-sites",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"symlink-dir": "6.0.0"
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@goldenwere/static-web-templates-types": "*",
|
||||
"@goldenwere/static-web-templates-embeds": "*",
|
||||
"@types/dompurify": "3.0.5",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "18.16.x",
|
||||
|
|
|
@ -4,6 +4,8 @@ import hljs from 'highlight.js'
|
|||
import { marked } from 'marked'
|
||||
import { markedHighlight } from 'marked-highlight'
|
||||
|
||||
import { registerLinkEmbed } from '@goldenwere/static-web-templates-embeds'
|
||||
|
||||
import main from './main.vue'
|
||||
import './main.sass'
|
||||
|
||||
|
@ -27,6 +29,8 @@ export const createApp = ViteSSG(
|
|||
{ routes: createRoutes() },
|
||||
// function to have custom setups
|
||||
({ app, router, routes, isClient, initialState }) => {
|
||||
registerLinkEmbed()
|
||||
|
||||
app.use(createPinia())
|
||||
initializeRouteStore(routes)
|
||||
},
|
||||
|
|
|
@ -19,15 +19,11 @@ const domPurifyConfig = {
|
|||
'src',
|
||||
'style',
|
||||
'title',
|
||||
'target',
|
||||
|
||||
// project-tile
|
||||
'info',
|
||||
'view-path',
|
||||
'thumbnail-background',
|
||||
'thumbnail-position',
|
||||
'caption',
|
||||
// embeds
|
||||
'subtitle',
|
||||
'summary',
|
||||
'icon',
|
||||
],
|
||||
ALLOWED_TAGS: [
|
||||
'a',
|
||||
|
@ -80,7 +76,7 @@ const domPurifyConfig = {
|
|||
'dl',
|
||||
'dt',
|
||||
|
||||
'project-tile',
|
||||
'link-embed',
|
||||
],
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue