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,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
23
package-lock.json
generated
23
package-lock.json
generated
|
@ -12,7 +12,11 @@
|
||||||
"libs/*"
|
"libs/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"libs/embeds": {
|
||||||
|
"version": "0.0.0"
|
||||||
|
},
|
||||||
"libs/types": {
|
"libs/types": {
|
||||||
|
"name": "@goldenwere/static-web-templates-types",
|
||||||
"version": "0.0.0"
|
"version": "0.0.0"
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/dom-selector": {
|
"node_modules/@asamuzakjp/dom-selector": {
|
||||||
|
@ -438,12 +442,8 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@goldenwere/static-web-templates-frontend": {
|
"node_modules/@goldenwere/static-web-templates-embeds": {
|
||||||
"resolved": "projects/frontend",
|
"resolved": "libs/embeds",
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/@goldenwere/static-web-templates-sites": {
|
|
||||||
"resolved": "projects/sites",
|
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@goldenwere/static-web-templates-types": {
|
"node_modules/@goldenwere/static-web-templates-types": {
|
||||||
|
@ -1518,6 +1518,10 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/frontend": {
|
||||||
|
"resolved": "projects/frontend",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "11.2.0",
|
"version": "11.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||||
|
@ -2741,6 +2745,10 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sites": {
|
||||||
|
"resolved": "projects/sites",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
@ -3351,9 +3359,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"projects/frontend": {
|
"projects/frontend": {
|
||||||
"name": "@goldenwere/static-web-templates-frontend",
|
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@goldenwere/static-web-templates-embeds": "*",
|
||||||
"@goldenwere/static-web-templates-types": "*",
|
"@goldenwere/static-web-templates-types": "*",
|
||||||
"@types/dompurify": "3.0.5",
|
"@types/dompurify": "3.0.5",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
|
@ -3381,7 +3389,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"projects/sites": {
|
"projects/sites": {
|
||||||
"name": "@goldenwere/static-web-templates-sites",
|
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"symlink-dir": "6.0.0"
|
"symlink-dir": "6.0.0"
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@goldenwere/static-web-templates-types": "*",
|
"@goldenwere/static-web-templates-types": "*",
|
||||||
|
"@goldenwere/static-web-templates-embeds": "*",
|
||||||
"@types/dompurify": "3.0.5",
|
"@types/dompurify": "3.0.5",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/node": "18.16.x",
|
"@types/node": "18.16.x",
|
||||||
|
|
|
@ -4,6 +4,8 @@ import hljs from 'highlight.js'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
import { markedHighlight } from 'marked-highlight'
|
import { markedHighlight } from 'marked-highlight'
|
||||||
|
|
||||||
|
import { registerLinkEmbed } from '@goldenwere/static-web-templates-embeds'
|
||||||
|
|
||||||
import main from './main.vue'
|
import main from './main.vue'
|
||||||
import './main.sass'
|
import './main.sass'
|
||||||
|
|
||||||
|
@ -27,6 +29,8 @@ export const createApp = ViteSSG(
|
||||||
{ routes: createRoutes() },
|
{ routes: createRoutes() },
|
||||||
// function to have custom setups
|
// function to have custom setups
|
||||||
({ app, router, routes, isClient, initialState }) => {
|
({ app, router, routes, isClient, initialState }) => {
|
||||||
|
registerLinkEmbed()
|
||||||
|
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
initializeRouteStore(routes)
|
initializeRouteStore(routes)
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,15 +19,11 @@ const domPurifyConfig = {
|
||||||
'src',
|
'src',
|
||||||
'style',
|
'style',
|
||||||
'title',
|
'title',
|
||||||
|
'target',
|
||||||
|
|
||||||
// project-tile
|
// embeds
|
||||||
'info',
|
|
||||||
'view-path',
|
|
||||||
'thumbnail-background',
|
|
||||||
'thumbnail-position',
|
|
||||||
'caption',
|
|
||||||
'subtitle',
|
'subtitle',
|
||||||
'summary',
|
'icon',
|
||||||
],
|
],
|
||||||
ALLOWED_TAGS: [
|
ALLOWED_TAGS: [
|
||||||
'a',
|
'a',
|
||||||
|
@ -80,7 +76,7 @@ const domPurifyConfig = {
|
||||||
'dl',
|
'dl',
|
||||||
'dt',
|
'dt',
|
||||||
|
|
||||||
'project-tile',
|
'link-embed',
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue