class ProjectCard extends HTMLElement {
static get observedAttributes() {
return ['image', 'image-alt', 'title', 'description'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._rendered = false;
}
connectedCallback() {
this._render();
}
attributeChangedCallback() {
if (this._rendered) {
this._render();
}
}
_render() {
this._rendered = true;
const alt = this.getAttribute('image-alt') || '';
const title = this.getAttribute('title') || '';
const description = this.getAttribute('description') || '';
this.shadowRoot.innerHTML = `
`;
this.shadowRoot.querySelector('.intro-text').textContent = title;
this._renderDescription(description);
}
_renderDescription(description) {
const slot = this.shadowRoot.querySelector('slot');
slot.textContent = '';
const linkPattern = /\[\[([^|\]]+)\|([^\]]+)\]\]/g;
let lastIndex = 0;
let match;
while ((match = linkPattern.exec(description)) !== null) {
if (match.index > lastIndex) {
slot.appendChild(document.createTextNode(description.slice(lastIndex, match.index)));
}
const link = document.createElement('a');
link.href = match[2];
link.textContent = match[1];
if (match[2].startsWith('http')) {
link.target = '_blank';
link.rel = 'noopener';
}
slot.appendChild(link);
lastIndex = match.index + match[0].length;
if (linkPattern.lastIndex === match.index) {
linkPattern.lastIndex++;
}
}
if (lastIndex < description.length) {
slot.appendChild(document.createTextNode(description.slice(lastIndex)));
}
}
_escapeAttribute(value) {
return value
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/