Updated previews.
Some checks failed
CI / test (3.11) (push) Failing after 24s
CI / test (3.10) (push) Failing after 13s
CI / test (3.12) (push) Failing after 25s
CI / lint (push) Failing after 25s
CI / test (3.9) (push) Failing after 25s
CI / build (push) Failing after 26s
CI / test (3.8) (push) Failing after 35s
Some checks failed
CI / test (3.11) (push) Failing after 24s
CI / test (3.10) (push) Failing after 13s
CI / test (3.12) (push) Failing after 25s
CI / lint (push) Failing after 25s
CI / test (3.9) (push) Failing after 25s
CI / build (push) Failing after 26s
CI / test (3.8) (push) Failing after 35s
This commit is contained in:
parent
3570ba5b99
commit
213c299603
@ -24,6 +24,11 @@ rant-content {
|
||||
|
||||
.content-text a {
|
||||
color: var(--color-link);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.content-text a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content-text code {
|
||||
|
||||
@ -91,6 +91,13 @@ class CommentItem extends BaseComponent {
|
||||
</footer>
|
||||
</article>
|
||||
`);
|
||||
|
||||
if (comment.links && comment.links.length > 0) {
|
||||
const rantContent = this.$('rant-content');
|
||||
if (rantContent) {
|
||||
rantContent.setLinks(comment.links);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
escapeAttr(str) {
|
||||
|
||||
@ -87,6 +87,13 @@ class RantCard extends BaseComponent {
|
||||
</footer>
|
||||
</article>
|
||||
`);
|
||||
|
||||
if (rant.links && rant.links.length > 0) {
|
||||
const rantContent = this.$('rant-content');
|
||||
if (rantContent) {
|
||||
rantContent.setLinks(rant.links);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
escapeAttr(str) {
|
||||
|
||||
@ -6,37 +6,59 @@
|
||||
*/
|
||||
|
||||
import { BaseComponent } from './base-component.js';
|
||||
import { markdownRenderer } from '../utils/markdown.js';
|
||||
import { extractImageUrls, extractYoutubeUrls, extractNonMediaUrls } from '../utils/url.js';
|
||||
import { categorizeLinks, processTextWithLinks, extractImageUrls, extractYoutubeUrls, extractNonMediaUrls, sanitizeUrl } from '../utils/url.js';
|
||||
|
||||
class RantContent extends BaseComponent {
|
||||
static get observedAttributes() {
|
||||
return ['text'];
|
||||
return ['text', 'links'];
|
||||
}
|
||||
|
||||
init() {
|
||||
this.linksData = null;
|
||||
this.render();
|
||||
}
|
||||
|
||||
setLinks(links) {
|
||||
this.linksData = links;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
const text = this.getAttr('text') || '';
|
||||
const linksAttr = this.getAttr('links');
|
||||
const links = this.linksData || (linksAttr ? JSON.parse(linksAttr) : null);
|
||||
|
||||
this.addClass('rant-content');
|
||||
|
||||
const images = extractImageUrls(text);
|
||||
const youtubeLinks = extractYoutubeUrls(text);
|
||||
const otherLinks = extractNonMediaUrls(text);
|
||||
let images = [];
|
||||
let youtubeLinks = [];
|
||||
let otherLinks = [];
|
||||
let renderedText = '';
|
||||
|
||||
const renderedText = markdownRenderer.render(text);
|
||||
if (links && links.length > 0) {
|
||||
const categorized = categorizeLinks(links);
|
||||
images = categorized.images.map(l => l.url);
|
||||
youtubeLinks = categorized.youtube.map(l => l.url);
|
||||
otherLinks = categorized.other;
|
||||
renderedText = processTextWithLinks(text, links);
|
||||
renderedText = renderedText.replace(/\n/g, '<br>');
|
||||
} else {
|
||||
images = extractImageUrls(text);
|
||||
youtubeLinks = extractYoutubeUrls(text);
|
||||
const extractedOther = extractNonMediaUrls(text);
|
||||
otherLinks = extractedOther.map(url => ({ url, title: url }));
|
||||
renderedText = this.escapeHtml(text).replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
let html = `<div class="content-text">${renderedText}</div>`;
|
||||
|
||||
if (images.length > 0) {
|
||||
html += `
|
||||
<div class="content-images">
|
||||
${images.map(url => `
|
||||
<image-preview src="${url}"></image-preview>
|
||||
`).join('')}
|
||||
${images.map(url => {
|
||||
const safeUrl = sanitizeUrl(url);
|
||||
return safeUrl ? `<image-preview src="${safeUrl}"></image-preview>` : '';
|
||||
}).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -44,9 +66,10 @@ class RantContent extends BaseComponent {
|
||||
if (youtubeLinks.length > 0) {
|
||||
html += `
|
||||
<div class="content-videos">
|
||||
${youtubeLinks.map(url => `
|
||||
<youtube-embed url="${url}"></youtube-embed>
|
||||
`).join('')}
|
||||
${youtubeLinks.map(url => {
|
||||
const safeUrl = sanitizeUrl(url);
|
||||
return safeUrl ? `<youtube-embed url="${safeUrl}"></youtube-embed>` : '';
|
||||
}).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -54,9 +77,12 @@ class RantContent extends BaseComponent {
|
||||
if (otherLinks.length > 0) {
|
||||
html += `
|
||||
<div class="content-links">
|
||||
${otherLinks.slice(0, 3).map(url => `
|
||||
<link-preview url="${url}"></link-preview>
|
||||
`).join('')}
|
||||
${otherLinks.slice(0, 3).map(link => {
|
||||
const url = typeof link === 'string' ? link : link.url;
|
||||
const title = typeof link === 'string' ? link : (link.title || link.url);
|
||||
const safeUrl = sanitizeUrl(url);
|
||||
return safeUrl ? `<link-preview url="${safeUrl}" title="${this.escapeAttr(title)}"></link-preview>` : '';
|
||||
}).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -64,11 +90,32 @@ class RantContent extends BaseComponent {
|
||||
this.setHtml(html);
|
||||
}
|
||||
|
||||
escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
escapeAttr(str) {
|
||||
if (!str) return '';
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
onAttributeChanged(name, oldValue, newValue) {
|
||||
this.render();
|
||||
}
|
||||
|
||||
setText(text) {
|
||||
setText(text, links = null) {
|
||||
this.linksData = links;
|
||||
this.setAttr('text', text);
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,6 +183,13 @@ class RantDetail extends BaseComponent {
|
||||
`);
|
||||
|
||||
this.initComments();
|
||||
|
||||
if (rant.links && rant.links.length > 0) {
|
||||
const rantContent = this.$('rant-content');
|
||||
if (rantContent) {
|
||||
rantContent.setLinks(rant.links);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
escapeAttr(str) {
|
||||
|
||||
@ -100,6 +100,62 @@ function buildAvatarUrl(avatar) {
|
||||
return `/api/proxy-image?url=${encodeURIComponent(`https://avatars.devrant.com/${avatar.i}`)}`;
|
||||
}
|
||||
|
||||
function categorizeLinks(links) {
|
||||
if (!links || !Array.isArray(links)) {
|
||||
return { images: [], youtube: [], other: [] };
|
||||
}
|
||||
const images = [];
|
||||
const youtube = [];
|
||||
const other = [];
|
||||
for (const link of links) {
|
||||
if (!link.url) continue;
|
||||
if (isImageUrl(link.url)) {
|
||||
images.push(link);
|
||||
} else if (isYoutubeUrl(link.url)) {
|
||||
youtube.push(link);
|
||||
} else {
|
||||
other.push(link);
|
||||
}
|
||||
}
|
||||
return { images, youtube, other };
|
||||
}
|
||||
|
||||
function processTextWithLinks(text, links) {
|
||||
if (!text) return '';
|
||||
if (!links || !Array.isArray(links) || links.length === 0) {
|
||||
return escapeHtml(text);
|
||||
}
|
||||
const sortedLinks = [...links].sort((a, b) => b.start - a.start);
|
||||
let result = text;
|
||||
for (const link of sortedLinks) {
|
||||
if (typeof link.start !== 'number' || typeof link.end !== 'number') continue;
|
||||
if (link.start < 0 || link.end > result.length) continue;
|
||||
const before = result.substring(0, link.start);
|
||||
const linkText = result.substring(link.start, link.end);
|
||||
const after = result.substring(link.end);
|
||||
const safeUrl = sanitizeUrl(link.url);
|
||||
if (safeUrl) {
|
||||
const escapedText = escapeHtml(linkText);
|
||||
result = before + `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer" class="inline-link">${escapedText}</a>` + after;
|
||||
}
|
||||
}
|
||||
const parts = result.split(/(<a[^>]*>.*?<\/a>)/g);
|
||||
return parts.map(part => {
|
||||
if (part.startsWith('<a ')) return part;
|
||||
return escapeHtml(part);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
export {
|
||||
extractUrls,
|
||||
isYoutubeUrl,
|
||||
@ -116,5 +172,8 @@ export {
|
||||
makeAbsoluteUrl,
|
||||
isDevrantImageUrl,
|
||||
buildDevrantImageUrl,
|
||||
buildAvatarUrl
|
||||
buildAvatarUrl,
|
||||
categorizeLinks,
|
||||
processTextWithLinks,
|
||||
escapeHtml
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user