avtopost.kultmost.ru/static/script/share.js
2026-09-03 22:57:14 -04:00

242 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

async function imageWithTextBlob(imageUrl, text, options = {}) {
const response = await fetch(imageUrl, {
mode: 'cors',
credentials: 'same-origin',
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const imageBlob = await response.blob();
const imageBitmap = await createImageBitmap(imageBlob);
// OffscreenCanvas (works in modern browsers; fallback to normal canvas if needed)
const canvas = typeof OffscreenCanvas !== 'undefined'
? new OffscreenCanvas(
options.width ?? imageBitmap.width,
options.height ?? imageBitmap.height
)
: document.createElement('canvas');
const width = canvas.width ?? (options.width ?? imageBitmap.width);
const height = canvas.height ?? (options.height ?? imageBitmap.height);
if (!(canvas instanceof OffscreenCanvas)) {
canvas.width = width;
canvas.height = height;
}
const ctx = canvas.getContext('2d', { alpha: true });
// Draw image
ctx.drawImage(imageBitmap, 0, 0, width, height);
// No text: return original bitmap as blob (rendered)
if (!text?.trim()) {
if (canvas.convertToBlob) {
return canvas.convertToBlob({
type: options.type ?? 'image/png',
quality: options.quality ?? 0.92,
});
}
return await new Promise((resolve) =>
canvas.toBlob((b) => resolve(b), options.type ?? 'image/png', options.quality ?? 0.92)
);
}
// === Text rendering ===
const basePadding = 6;
const fontSize = options.fontSize ?? Math.round(width * 0.05);
ctx.font = `${fontSize}px ${options.fontFamily ?? 'sans-serif'}`;
ctx.textBaseline = 'bottom';
ctx.textAlign = 'center';
const textColor = options.textColor ?? '#5F1798';
const bgColor = options.bgColor ?? '#fff';
const strokeColor = options.strokeColor ?? 'rgba(0,0,0,0.35)';
const shadowBlur = options.shadowBlur ?? 6;
const maxWidth = width - basePadding * 2 - (options.extraPadding ?? 0);
// Wrap text into lines
const words = text.split(' ');
let line = '';
const lines = [];
for (const word of words) {
const testLine = line ? line + ' ' + word : word;
if (ctx.measureText(testLine).width > maxWidth && line) {
lines.push(line);
line = word;
} else {
line = testLine;
}
}
if (line) lines.push(line);
const lineHeight = fontSize + (options.lineGap ?? 6);
const boxHeight = lines.length * lineHeight + basePadding * 2;
const maxLineWidth = Math.max(...lines.map((l) => ctx.measureText(l).width));
const boxWidth = maxLineWidth + basePadding * 2;
const bottomPadding = options.bottomPadding ?? Math.round(fontSize * 0.6);
const boxX = (width - boxWidth) / 2;
const boxY = height - bottomPadding - boxHeight;
const radius = options.radius ?? 8;
// Background rounded rect
ctx.save();
ctx.fillStyle = bgColor;
ctx.shadowColor = strokeColor;
ctx.shadowBlur = shadowBlur;
roundRect(ctx, boxX, boxY, boxWidth, boxHeight, radius, true, false);
ctx.restore();
// Text
ctx.fillStyle = textColor;
ctx.shadowColor = 'transparent';
for (let i = 0; i < lines.length; i++) {
const lineY = boxY + basePadding + (i + 1) * lineHeight - (lineHeight - fontSize) / 2;
const lineX = width / 2;
if (options.outline) {
ctx.lineWidth = options.outlineWidth ?? 2;
ctx.strokeStyle = options.outlineColor ?? 'rgba(255,255,255,0.8)';
ctx.strokeText(lines[i], lineX, lineY);
}
ctx.fillText(lines[i], lineX, lineY);
}
// Convert to blob
if (canvas.convertToBlob) {
return await canvas.convertToBlob({
type: options.type ?? 'image/png',
quality: options.quality ?? 0.92,
});
}
return await new Promise((resolve) =>
canvas.toBlob((b) => resolve(b), options.type ?? 'image/png', options.quality ?? 0.92)
);
}
function roundRect(ctx, x, y, w, h, r, fill, stroke) {
if (typeof r === 'number') r = { tl: r, tr: r, br: r, bl: r };
ctx.beginPath();
ctx.moveTo(x + r.tl, y);
ctx.lineTo(x + w - r.tr, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r.tr);
ctx.lineTo(x + w, y + h - r.br);
ctx.quadraticCurveTo(x + w, y + h, x + w - r.br, y + h);
ctx.lineTo(x + r.bl, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r.bl);
ctx.lineTo(x, y + r.tl);
ctx.quadraticCurveTo(x, y, x + r.tl, y);
ctx.closePath();
if (fill) ctx.fill();
if (stroke) ctx.stroke();
}
document.addEventListener('DOMContentLoaded', () => {
const shareModal = document.getElementById('shareModal');
const closeshareModal = document.getElementById('closeModal');
const fb = document.getElementById('fb');
const vk = document.getElementById('vk');
const tg = document.getElementById('tg');
const ok = document.getElementById('ok');
const wa = document.getElementById('wa');
const inst = document.getElementById('inst');
const copyBtn = document.getElementById('copyBtn');
const copyMsg = document.getElementById('copyMsg');
function buildShareUrl(platform, url, text = '') {
const u = encodeURIComponent(url);
const t = encodeURIComponent(text);
switch (platform) {
case 'tw': return `https://twitter.com/intent/tweet?url=${u}&text=${t}`;
case 'vk': return `https://vk.com/share.php?url=${u}&title=${t}`;
case 'tg': return `https://telegram.me/share/url?url=${u}&text=${t}`;
case 'fb': return `https://www.facebook.com/sharer/sharer.php?u=${u}&quote=${t}`;
case 'ok': return `https://connect.ok.ru/offer?url=${u}&title=${t}`;
case 'wa': return `https://api.whatsapp.com/send?text=${t} ${u}`;
case 'inst': return `https://www.instagram.com/?url=${u}`;
default: return '#';
}
}
const shareBtns = document.querySelectorAll('#shareBtn');
shareBtns.forEach(btn => {
btn.addEventListener('click', async () => {
const link = window.location.origin + btn.dataset.link || window.location.href;
const text = btn.dataset.text || document.title;
const image = btn.dataset.image;
const imagetext = btn.dataset.imagetext || '';
let blob = null;
let file = null;
let objectUrl = null;
try {
if (image) {
const imageUrl = window.location.origin + image;
// В обработчике кнопки
const blob = await imageWithTextBlob(imageUrl, imagetext, {
textColor: '#5F1798',
});
console.log(`Generated blob for "${imagetext}" | size: ${blob.size} | url: ${imageUrl}`);
const filename = `shared-image-${Date.now()}.png`;
file = new File([blob], filename, { type: blob.type });
// Создаём object URL только если хотим (необязательно для navigator.share)
objectUrl = URL.createObjectURL(file);
if (navigator.canShare && navigator.canShare({ files: [file] })) {
await navigator.share({
title: text,
text: text,
url: link,
files: [file],
});
} else {
await navigator.share({
title: text,
text: '',
url: link,
});
}
} else {
await navigator.share({ title: text, text: '', url: link });
}
} catch (err) {
openshareModal(link, text);
} finally {
// Очистка
try {
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
objectUrl = null;
}
} catch (e) { /* ignore */ }
// Удаляем ссылки, чтобы GC мог собрать объекты
blob = null;
file = null;
}
});
});
function openshareModal(link, text) {
vk.href = buildShareUrl('vk', link, text);
tg.href = buildShareUrl('tg', link, text);
fb.href = buildShareUrl('fb', link, text);
ok.href = buildShareUrl('ok', link, text);
wa.href = buildShareUrl('wa', link, text);
inst.href = buildShareUrl('inst', link, text);
shareModal.style.display = 'flex';
copyBtn.dataset.link = link;
shareModal.style.display = 'flex'
}
closeshareModal.addEventListener('click', () => {
shareModal.style.display = 'none';
});
shareModal.addEventListener('click', (e) => {
if (e.target === shareModal) shareModal.style.display = 'none';
});
async function copyToClipboard(text) {
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
} else {
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.left = '-9999px';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
}
showCopyMessage();
} catch (e) {
showToast({message: 'Не удалось скопировать ссылку', status: 'error'});
}
}
function showCopyMessage() {
copyMsg.style.display = 'inline';
setTimeout(() => copyMsg.style.display = 'none', 2000);
}
copyBtn.addEventListener('click', () => {
const link = copyBtn.dataset.link || window.location.href;
copyToClipboard(link);
});
});