Merge pull request 'production' (#7) from production into main

This commit is contained in:
vladislav 2026-09-07 06:48:27 +00:00
commit 8153b08690
52 changed files with 1383 additions and 1135 deletions

View File

@ -775,9 +775,10 @@
"\n",
"[Service]\n",
"WorkingDirectory=/app/production\n",
"ExecStart=/app/production/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30081 --workers 8\n",
"ExecStart=/app/production/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30081 --workers 3\n",
"Restart=always\n",
"EnvironmentFile=/app/production/env\n",
"CPUWeight=60\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
@ -798,23 +799,23 @@
},
"outputs": [],
"source": [
"%%bash\n",
"ssh kultmost 'tee /etc/systemd/system/fastapi.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI prod service\n",
"After=network.target\n",
"# %%bash\n",
"# ssh kultmost 'tee /etc/systemd/system/fastapi.service >/dev/null' <<'EOF'\n",
"# [Unit]\n",
"# Description=FastAPI prod service\n",
"# After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=/app/production\n",
"ExecStart=/app/production/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30080 --workers 8\n",
"Restart=always\n",
"EnvironmentFile=/app/production/env\n",
"# [Service]\n",
"# WorkingDirectory=/app/production\n",
"# ExecStart=/app/production/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30080 --workers 8\n",
"# Restart=always\n",
"# EnvironmentFile=/app/production/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF\n",
"# [Install]\n",
"# WantedBy=default.target\n",
"# EOF\n",
"\n",
"ssh kultmost 'systemctl daemon-reload && systemctl enable --now fastapi.service && systemctl status --no-pager -l fastapi.service'\n"
"# ssh kultmost 'systemctl daemon-reload && systemctl enable --now fastapi.service && systemctl status --no-pager -l fastapi.service'\n"
],
"display_name": ".venv"
},
@ -837,23 +838,23 @@
},
"outputs": [],
"source": [
"%%bash\n",
"ssh kultmost 'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI stag service\n",
"After=network.target\n",
"# %%bash\n",
"# ssh kultmost 'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"# [Unit]\n",
"# Description=FastAPI stag service\n",
"# After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=/app/staging\n",
"ExecStart=/app/staging/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"Restart=always\n",
"EnvironmentFile=/app/staging/env\n",
"# [Service]\n",
"# WorkingDirectory=/app/staging\n",
"# ExecStart=/app/staging/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"# Restart=always\n",
"# EnvironmentFile=/app/staging/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF\n",
"# [Install]\n",
"# WantedBy=default.target\n",
"# EOF\n",
"\n",
"ssh kultmost 'systemctl daemon-reload && systemctl enable --now staging.service && systemctl status --no-pager -l staging.service'\n"
"# ssh kultmost 'systemctl daemon-reload && systemctl enable --now staging.service && systemctl status --no-pager -l staging.service'\n"
],
"display_name": ".venv"
},
@ -876,21 +877,21 @@
},
"outputs": [],
"source": [
"%%bash\n",
"'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI stag service\n",
"After=network.target\n",
"# %%bash\n",
"# 'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"# [Unit]\n",
"# Description=FastAPI stag service\n",
"# After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=$pwd\n",
"ExecStart=$pwd/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"Restart=always\n",
"EnvironmentFile=$pwd/env\n",
"# [Service]\n",
"# WorkingDirectory=$pwd\n",
"# ExecStart=$pwd/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"# Restart=always\n",
"# EnvironmentFile=$pwd/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF"
"# [Install]\n",
"# WantedBy=default.target\n",
"# EOF"
],
"display_name": ".venv"
},
@ -905,23 +906,23 @@
},
"outputs": [],
"source": [
"%%bash\n",
"ssh kultmost 'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"[Unit]\n",
"Description=FastAPI stag service\n",
"After=network.target\n",
"# %%bash\n",
"# ssh kultmost 'tee /etc/systemd/system/staging.service >/dev/null' <<'EOF'\n",
"# [Unit]\n",
"# Description=FastAPI stag service\n",
"# After=network.target\n",
"\n",
"[Service]\n",
"WorkingDirectory=/app/staging\n",
"ExecStart=/app/staging/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"Restart=always\n",
"EnvironmentFile=/app/staging/env\n",
"# [Service]\n",
"# WorkingDirectory=/app/staging\n",
"# ExecStart=/app/staging/.venv/bin/uvicorn main:app --host 127.0.0.1 --port 30082 --reload\n",
"# Restart=always\n",
"# EnvironmentFile=/app/staging/env\n",
"\n",
"[Install]\n",
"WantedBy=default.target\n",
"EOF\n",
"# [Install]\n",
"# WantedBy=default.target\n",
"# EOF\n",
"\n",
"ssh kultmost 'systemctl daemon-reload && systemctl enable --now staging.service && systemctl status --no-pager -l staging.service'\n"
"# ssh kultmost 'systemctl daemon-reload && systemctl enable --now staging.service && systemctl status --no-pager -l staging.service'\n"
],
"display_name": ".venv"
},
@ -974,7 +975,7 @@
},
"outputs": [],
"source": [
"!ssh kultmost 'systemctl restart fastapi.service'"
"# !ssh kultmost 'systemctl restart fastapi.service'"
],
"display_name": ".venv"
},
@ -989,7 +990,7 @@
},
"outputs": [],
"source": [
"!ssh kultmost 'systemctl status fastapi.service'"
"# !ssh kultmost 'systemctl status fastapi.service'"
],
"display_name": ".venv"
},

View File

@ -80,7 +80,6 @@
"import uuid\n",
"import bcrypt\n",
"from typing import List, Any, Dict, Optional, Union\n",
"from datetime import timedelta\n",
"from urllib.parse import urlencode\n",
"from bson import ObjectId\n",
"from PIL import Image\n",
@ -473,6 +472,24 @@
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "edb712bd",
"metadata": {},
"outputs": [],
"source": [
"@app.get(\"/cron/08\", response_model=dict)\n",
"async def cron_08(background_tasks: BackgroundTasks):\n",
" async for doc in posts_collection.find({\n",
" '_id': {'$gte': ObjectId.from_datetime(datetime.now() - timedelta(days=1))},\n",
" '$or':[{'vk':[]},{'tg':[]},{'fb':[]},{'inst':[]},{'max':[]}]\n",
" }):\n",
" background_tasks.add_task(publish_social_post, bson_clear(doc))\n",
" return {'ok': True}\n"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,

11
main.py
View File

@ -19,7 +19,6 @@ from starlette.middleware.gzip import GZipMiddleware
import uuid
import bcrypt
from typing import List, Any, Dict, Optional, Union
from datetime import timedelta
from urllib.parse import urlencode
from bson import ObjectId
from PIL import Image
@ -303,6 +302,16 @@ async def posts_temp(user,request):
request=request, name="profile/posts.html", context={ "user": user, 'cards': data}
)
@app.get("/cron/08", response_model=dict)
async def cron_08(background_tasks: BackgroundTasks):
async for doc in posts_collection.find({
'_id': {'$gte': ObjectId.from_datetime(datetime.now() - timedelta(days=1))},
'$or':[{'vk':[]},{'tg':[]},{'fb':[]},{'inst':[]},{'max':[]}]
}):
background_tasks.add_task(publish_social_post, bson_clear(doc))
return {'ok': True}
@app.get("/cron/day")
async def cron_every_day(token: str = Security(get_token)):
res = await competitions_col.update_many(

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

270
static/script/select.js Normal file
View File

@ -0,0 +1,270 @@
(function() {
'use strict';
const root = document.getElementById('dd-modal-root') || document.body;
// Утилиты парсинга
function parseList(attr) {
if (!attr) return [];
try {
const fixed = attr.replace(/'/g, '"');
return JSON.parse(fixed);
} catch (e) {
return String(attr).split(',').map(s => s.trim()).filter(Boolean);
}
}
function parseSavedValue(el) {
const v = el.getAttribute('data-value');
if (!v) return [];
try {
return JSON.parse(v);
} catch (e) {
return v.includes(',')
? v.split(',').map(s => s.trim()).filter(Boolean)
: [v];
}
}
async function openModalForInput(triggerEl) {
const listRaw = triggerEl.getAttribute('data-list') || '[]';
const options = parseList(listRaw);
const isRadio = triggerEl.getAttribute('data-radio') === '1' ||
triggerEl.getAttribute('data-radio') === 'true';
const takeAll = triggerEl.getAttribute('data-takeall') === '1' ||
triggerEl.getAttribute('data-takeall') === 'true';
const allowCustom = triggerEl.getAttribute('data-custom') === '1' ||
triggerEl.getAttribute('data-custom') === 'true';
const title = triggerEl.getAttribute('data-title') || 'Выберите';
const saveLabel = triggerEl.getAttribute('data-label') || 'Сохранить';
const customPlaceholder = triggerEl.getAttribute('data-custom_placeholder') || 'Добавить...';
const initial = parseSavedValue(triggerEl);
return new Promise(resolve => {
const backdrop = document.createElement('div');
backdrop.className = 'dd-modal-backdrop';
const modal = document.createElement('div');
modal.className = 'dd-modal';
modal.innerHTML = `
<div class="dd-header">
<strong>${title}</strong>
<button class="dd-close" aria-label="Закрыть">&times;</button>
</div>
<div class="dd-custom-container" style="margin-top:8px;"></div>
<div class="dd-body"></div>
<div class="dd-actions" style="margin-top:12px;">
<button class="dd-save">${saveLabel}</button>
</div>
`;
backdrop.appendChild(modal);
root.appendChild(backdrop);
const body = modal.querySelector('.dd-body');
const customContainer = modal.querySelector('.dd-custom-container');
const saveBtn = modal.querySelector('.dd-save');
const closeBtn = modal.querySelector('.dd-close');
const groupName = 'dd-group-' + Date.now() + Math.random().toString(36).slice(2);
let currentOptions = options.slice();
let selected = initial.slice();
function renderOptions() {
body.innerHTML = '';
// "Выбрать все"
if (takeAll) {
const wrapper = createOption('Выбрать все', true, groupName, isRadio);
const takeAllInput = wrapper.querySelector('input');
takeAllInput.id = 'takeAll';
takeAllInput.checked = selected.length === 0 || selected.includes('Выбрать все');
body.appendChild(wrapper);
}
currentOptions.forEach(opt => {
const label = typeof opt === 'object' ? opt.name : String(opt);
const tooltip = typeof opt === 'object' ? opt.tooltip : null;
const count = typeof opt === 'object' ? opt.count : null;
const wrapper = createOption(label, false, groupName, isRadio, tooltip, count);
const input = wrapper.querySelector('input');
input.checked = selected.includes(label);
body.appendChild(wrapper);
});
}
function createOption(label, isTakeAll, name, isRadio, tooltip = null, count = null) {
const wrapper = document.createElement('label');
wrapper.className = 'dd-option';
const input = document.createElement('input');
input.type = isRadio ? 'radio' : 'checkbox';
input.name = name;
input.value = label;
input.className = 'offer-checkbox';
const span = document.createElement('span');
span.textContent = label;
const div = document.createElement('div');
div.className = 'some-class';
// count
if (count !== undefined && count !== null) {
const countNode = document.createElement('span');
countNode.textContent = count;
div.appendChild(countNode);
}
// tooltip
if (tooltip) {
const img = document.createElement('img');
img.className = 'tooltip-trigger';
img.dataset.tooltip = tooltip;
img.src = '/static/help.svg';
img.alt = 'Подсказка';
div.appendChild(img);
}
span.appendChild(div);
wrapper.append(input, span);
return wrapper;
}
if (allowCustom) {
customContainer.innerHTML = `
<div style="display:flex;gap:8px;align-items:center;flex-direction:column;">
<input type="text" class="dd-custom-input" placeholder="${customPlaceholder}"
style="flex:1;padding:6px;border:1px solid #ccc;width:100%;border-radius:4px;">
<button class="dd-custom-add" style="padding:6px 8px;width:100%;border-radius:5px;background:#5F1798;color:white;">
Добавить
</button>
</div>
`;
const customInput = customContainer.querySelector('.dd-custom-input');
const customAdd = customContainer.querySelector('.dd-custom-add');
const addCustom = (val) => {
val = String(val).trim();
if (!val) return;
if (!currentOptions.some(o => String(o) === val && typeof o !== 'object')) {
currentOptions.push(val);
triggerEl.setAttribute('data-list', JSON.stringify(currentOptions));
}
if (isRadio) {
selected = [val];
} else if (!selected.includes(val)) {
selected.push(val);
}
renderOptions();
updateCheckedState();
};
customAdd.addEventListener('click', () => {
addCustom(customInput.value);
customInput.value = '';
customInput.focus();
});
customInput.addEventListener('keydown', e => {
if (e.key === 'Enter') {
e.preventDefault();
addCustom(customInput.value);
customInput.value = '';
}
});
}
function updateCheckedState() {
Array.from(body.querySelectorAll('input')).forEach(inp => {
inp.checked = selected.includes(inp.value);
});
}
function collectChecked() {
return Array.from(body.querySelectorAll('input:checked')).map(i => i.value);
}
function closeModal() {
const result = collectChecked();
triggerEl.setAttribute('data-value', isRadio ? (result[0] ?? '') : JSON.stringify(result));
if (triggerEl.type && triggerEl.type !== 'hidden') {
triggerEl.value = isRadio ? (result[0] ?? '') : result.join(', ');
}
backdrop.remove();
resolve(result);
}
// Обработчики закрытия
closeBtn.addEventListener('click', closeModal);
saveBtn.addEventListener('click', closeModal);
backdrop.addEventListener('click', e => {
if (e.target === backdrop) closeModal();
});
// Логика "Выбрать все"
function setupTakeAll() {
const takeAllInput = document.getElementById('takeAll');
if (!takeAllInput) return;
const allInputs = () => Array.from(body.querySelectorAll('input.offer-checkbox'));
takeAllInput.addEventListener('change', () => {
if (takeAllInput.checked) {
selected = [];
allInputs().forEach(inp => {
if (inp !== takeAllInput) inp.checked = false;
});
closeModal();
}
});
// Сброс "Выбрать все" при выборе других
body.addEventListener('change', e => {
const inp = e.target;
if (inp.type !== 'checkbox' || inp === takeAllInput) return;
if (inp.checked) {
takeAllInput.checked = false;
} else {
const anyChecked = allInputs().some(x => x !== takeAllInput && x.checked);
takeAllInput.checked = !anyChecked;
}
});
}
renderOptions();
setupTakeAll();
updateCheckedState();
});
}
// Главный обработчик
document.addEventListener('click', async function(e) {
const trigger = e.target.closest('[data-select="1"]');
if (!trigger) return;
const input = trigger.querySelector('input');
if (input) {
input.readOnly = true;
input.blur();
}
e.preventDefault();
const result = await openModalForInput(trigger);
if (result && result.length) {
trigger.dispatchEvent(new Event('change', { bubbles: true }));
}
}, true);
})();

138
static/script/share.js Normal file
View File

@ -0,0 +1,138 @@
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
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 = options.basePadding ?? 8; // internal left/right/top padding
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);
// Extra internal bottom padding inside the rounded box
const internalBottomPadding = options.internalBottomPadding ?? Math.round(fontSize * 0.35);
const boxHeight = lines.length * lineHeight + basePadding + internalBottomPadding;
const maxLineWidth = Math.max(...lines.map((l) => ctx.measureText(l).width));
const boxWidth = maxLineWidth + basePadding * 2;
// Distance from the very bottom of the image to the bottom of the text box
const bottomPadding = options.bottomPadding ?? Math.round(fontSize * 0.9);
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();
}

View File

@ -2,7 +2,7 @@ importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox
workbox.setConfig({ debug: false });
const CACHE_NAME = 'app-cache-v2.1.5';
const CACHE_NAME = 'app-cache-v2.1.6';
const MIN_VALID_SIZE = 500;
const ASSETS_TO_CACHE = [
@ -19,6 +19,8 @@ const ASSETS_TO_CACHE = [
'/static/event3.jpg',
'/static/event4.jpg',
'/static/event5.jpg',
'/static/script/share.js',
'/static/script/select.js',
'/static/event6.jpg',
'/static/event7.webp',
'/partner',

View File

@ -97,8 +97,10 @@ picture{cursor: zoom-in;}
</head>
<body {% if request.cookies.zoom%}style="zoom: {{ request.cookies.zoom }}%"{%endif%} onload="adjustBodyStyle()">
<script>
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat
function setSecureCookie(name, value, days = 7) {const date = new Date();date.setTime(date.getTime() + (days * 86400000));document.cookie = `${name}=${encodeURIComponent(value)}; expires=${date.toUTCString()}; path=/; SameSite=Strict; Secure`;}
var userAgent = navigator.userAgent;var isFirefox = userAgent.indexOf("Firefox") !== -1;function isOldFirefox() {var match = userAgent.match(/Firefox\/(\d+)/);return match && parseInt(match[1], 10) < 126;};function iOS() {return ['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(navigator.platform) || (userAgent.includes("Mac") && "ontouchend" in document);};function adjustBodyStyle() {var body = document.body;var width;if (iOS()) {width = screen.width;} else {width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 360;};const zoom = (width < 700) ? width * 100 / 400 : (width < 900) ? width * 100 / 800 : width * 100 / 1600;if (isFirefox && isOldFirefox()) {body.style.MozTransform = `scale(${zoom}%)`;body.style.MozTransformOrigin = '0% 0%';body.style.width = (width < 901) ? '360px' : '1600px';body.style.position = 'absolute';};setSecureCookie("zoom", zoom, 7);body.style.zoom = `${zoom}%`;};adjustBodyStyle();window.addEventListener('resize', adjustBodyStyle, { passive: true });
// @license-end
</script>
{% block content %}{% endblock %}
<div id="modal" class="modal" style="display: none;cursor: zoom-out;">
@ -111,6 +113,7 @@ var userAgent = navigator.userAgent;var isFirefox = userAgent.indexOf("Firefox")
</a>
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('picture').forEach(picture => {
picture.addEventListener('click', function() {
@ -144,14 +147,17 @@ var userAgent = navigator.userAgent;var isFirefox = userAgent.indexOf("Firefox")
});
});
function closeModalPic() {const modalImg = document.getElementById('modalImg');modalImg.src = '';document.getElementById('modal').style.display = 'none';}
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
{% raw %}
function showToast(e={}){let{message:t="",status:a="info",duration:l=4e3}=e,n=document.querySelector(".toast-container");n||((n=document.createElement("div")).className="toast-container",document.body.appendChild(n));let s=document.createElement("div");s.className=`toast ${a}`;let d=document.createElement("div");d.className="accent";let c=document.createElement("div");c.className="toast-header";let i=document.createElement("div");i.textContent=t;let o=document.createElement("button");o.className="toast-close",o.innerHTML="✕",c.appendChild(i),c.appendChild(o),s.appendChild(d),s.appendChild(c),n.appendChild(s),requestAnimationFrame(()=>s.classList.add("show"));let r=!1,p=()=>{r||(r=!0,s.classList.remove("show"),setTimeout(()=>{s.parentNode&&s.parentNode.removeChild(s)},380))};return o.addEventListener("click",p),s.addEventListener("click",e=>{e.target===s&&p()}),l>0&&setTimeout(p,l),{close:p}}
{% endraw %}
{% if notify %}
showToast({message: '{{notify.text}}', status: '{{notify.status}}', duration:{{notify.duration}}});
{% endif %}
// @license-end
</script>
<div id="shareModal"
style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.4);align-items:center;justify-content:center;z-index: 3000;">
@ -172,14 +178,13 @@ function showToast(e={}){let{message:t="",status:a="info",duration:l=4e3}=e,n=do
</div>
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
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);
@ -204,7 +209,7 @@ async function imageWithTextBlob(imageUrl, text, options = {}) {
// Draw image
ctx.drawImage(imageBitmap, 0, 0, width, height);
// No text: return original bitmap as blob (rendered)
// No text: return original bitmap as blob
if (!text?.trim()) {
if (canvas.convertToBlob) {
return canvas.convertToBlob({
@ -218,7 +223,7 @@ async function imageWithTextBlob(imageUrl, text, options = {}) {
}
// === Text rendering ===
const basePadding = 6;
const basePadding = options.basePadding ?? 8; // internal left/right/top padding
const fontSize = options.fontSize ?? Math.round(width * 0.05);
ctx.font = `${fontSize}px ${options.fontFamily ?? 'sans-serif'}`;
ctx.textBaseline = 'bottom';
@ -246,11 +251,17 @@ async function imageWithTextBlob(imageUrl, text, options = {}) {
if (line) lines.push(line);
const lineHeight = fontSize + (options.lineGap ?? 6);
const boxHeight = lines.length * lineHeight + basePadding * 2;
// Extra internal bottom padding inside the rounded box
const internalBottomPadding = options.internalBottomPadding ?? Math.round(fontSize * 0.35);
const boxHeight = lines.length * lineHeight + basePadding + internalBottomPadding;
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);
// Distance from the very bottom of the image to the bottom of the text box
const bottomPadding = options.bottomPadding ?? Math.round(fontSize * 0.9);
const boxX = (width - boxWidth) / 2;
const boxY = height - bottomPadding - boxHeight;
const radius = options.radius ?? 8;
@ -266,6 +277,7 @@ async function imageWithTextBlob(imageUrl, text, options = {}) {
// 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;
@ -289,7 +301,6 @@ async function imageWithTextBlob(imageUrl, text, options = {}) {
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();
@ -306,8 +317,6 @@ function roundRect(ctx, x, y, w, h, r, fill, stroke) {
if (fill) ctx.fill();
if (stroke) ctx.stroke();
}
document.addEventListener('DOMContentLoaded', () => {
const shareModal = document.getElementById('shareModal');
const closeshareModal = document.getElementById('closeModal');
@ -340,11 +349,9 @@ shareBtns.forEach(btn => {
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;
@ -355,10 +362,8 @@ shareBtns.forEach(btn => {
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,
@ -386,15 +391,12 @@ shareBtns.forEach(btn => {
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);
@ -406,15 +408,12 @@ shareBtns.forEach(btn => {
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) {
@ -434,151 +433,20 @@ shareBtns.forEach(btn => {
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);
});
});
// @license-end
</script>
<script>
(async () => {
const parts_cookie = `; ${document.cookie}`.split(`; token=`);
const token_old = parts_cookie.pop().split(';')[0];
if (!token_old) return;
try {
let response = await fetch('/api/me');
if (!response.ok) {
response = await fetch('/api/old/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
}
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
if (!data.username) {
response = await fetch('/api/old/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Network response was still not ok');
}
}
} catch (error) {
console.error('There was a problem with the fetch operation:', error);
}
})();
</script>
<script>
let deferredPrompt = null;
let hasUserInteracted = false;
let promptTimeout = null;
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(reg => {
reg.addEventListener('updatefound', () => {
const newWorker = reg.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
console.log('New Service Worker ready');
}
});
});
});
navigator.serviceWorker.addEventListener('message', event => {
if (event.data.type === 'SW_UPDATED') {
console.log('New Service Worker activated – reloading...');
window.location.reload();
}
});
}
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
console.log('beforeinstallprompt: готово к установке');
});
const markInteractionAndTryShow = () => {
if (hasUserInteracted) return;
hasUserInteracted = true;
document.removeEventListener('click', markInteractionAndTryShow);
document.removeEventListener('keydown', markInteractionAndTryShow);
document.removeEventListener('touchstart', safeTouchHandler);
tryShowInstallPrompt();
};
let touchMoved = false;
const safeTouchHandler = (e) => {
if (e.touches.length > 1) return;
document.addEventListener('touchmove', () => touchMoved = true, { once: true });
document.addEventListener('touchend', () => {
if (!touchMoved) markInteractionAndTryShow();
touchMoved = false;
}, { once: true });
};
document.addEventListener('click', markInteractionAndTryShow, { passive: true });
document.addEventListener('keydown', markInteractionAndTryShow, { passive: true });
document.addEventListener('touchstart', safeTouchHandler, { passive: true });
let scrollAllowed = false;
const scrollHandler = () => {
if (scrollAllowed && !hasUserInteracted) markInteractionAndTryShow();
};
document.addEventListener('scroll', scrollHandler, { passive: true });
document.addEventListener('click', () => scrollAllowed = true, { once: true });
document.addEventListener('touchstart', () => scrollAllowed = true, { once: true });
function tryShowInstallPrompt() {
if (!deferredPrompt) {
console.log('Промпт ещё не готов');
return;
}
if (promptTimeout) return;
promptTimeout = setTimeout(() => {
console.log('Показываем install-промпт');
deferredPrompt.prompt();
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('PWA установлен!');
} else {
console.log('Установка отклонена');
}
deferredPrompt = null;
promptTimeout = null;
});
}, 1500);
}
</script>
<script>
(function() {
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
(function() {
'use strict';
const root = document.getElementById('dd-modal-root') || document.body;
@ -709,21 +577,11 @@ shareBtns.forEach(btn => {
img.alt = 'Подсказка';
div.appendChild(img);
}
// куда-то добавляем div
span.appendChild(div);
wrapper.append(input, span);
return wrapper;
}
// Кастомные значения
if (allowCustom) {
customContainer.innerHTML = `
<div style="display:flex;gap:8px;align-items:center;flex-direction:column;">
@ -858,6 +716,98 @@ shareBtns.forEach(btn => {
}, true);
})();
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
let deferredPrompt = null;
let hasUserInteracted = false;
let promptTimeout = null;
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(reg => {
reg.addEventListener('updatefound', () => {
const newWorker = reg.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
console.log('New Service Worker ready');
}
});
});
});
navigator.serviceWorker.addEventListener('message', event => {
if (event.data.type === 'SW_UPDATED') {
console.log('New Service Worker activated – reloading...');
window.location.reload();
}
});
}
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
console.log('beforeinstallprompt: готово к установке');
});
const markInteractionAndTryShow = () => {
if (hasUserInteracted) return;
hasUserInteracted = true;
document.removeEventListener('click', markInteractionAndTryShow);
document.removeEventListener('keydown', markInteractionAndTryShow);
document.removeEventListener('touchstart', safeTouchHandler);
tryShowInstallPrompt();
};
let touchMoved = false;
const safeTouchHandler = (e) => {
if (e.touches.length > 1) return;
document.addEventListener('touchmove', () => touchMoved = true, { once: true });
document.addEventListener('touchend', () => {
if (!touchMoved) markInteractionAndTryShow();
touchMoved = false;
}, { once: true });
};
document.addEventListener('click', markInteractionAndTryShow, { passive: true });
document.addEventListener('keydown', markInteractionAndTryShow, { passive: true });
document.addEventListener('touchstart', safeTouchHandler, { passive: true });
let scrollAllowed = false;
const scrollHandler = () => {
if (scrollAllowed && !hasUserInteracted) markInteractionAndTryShow();
};
document.addEventListener('scroll', scrollHandler, { passive: true });
document.addEventListener('click', () => scrollAllowed = true, { once: true });
document.addEventListener('touchstart', () => scrollAllowed = true, { once: true });
function tryShowInstallPrompt() {
if (!deferredPrompt) {
console.log('Промпт ещё не готов');
return;
}
if (promptTimeout) return;
promptTimeout = setTimeout(() => {
console.log('Показываем install-промпт');
deferredPrompt.prompt();
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('PWA установлен!');
} else {
console.log('Установка отклонена');
}
deferredPrompt = null;
promptTimeout = null;
});
}, 1500);
}
// @license-end
</script>
<div id="dd-modal-root"></div>
@ -868,6 +818,7 @@ shareBtns.forEach(btn => {
</div>
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
const modal = document.getElementById('modalToolTip');
const modalText = document.getElementById('modalToolTipText');
@ -897,6 +848,7 @@ shareBtns.forEach(btn => {
}
});
});
// @license-end
</script>
</body>
</html>

View File

@ -207,6 +207,7 @@
</div>
</form>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
/* ---------- Utility helpers (removed) ---------- */
@ -342,8 +343,10 @@ function bindLinkValidation({
});
});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
const competition = JSON.parse({{competition|tojson}})
const competitionId = `{{competition_id}}`
@ -432,8 +435,10 @@ document.getElementById('preview_poster').src = `/b/competition/${competition.po
// date_finish.value = date_finish_date.toISOString().slice(0, 10);
});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
var type = competition.type_;
function toggleType(clickedId) {
@ -449,8 +454,10 @@ document.getElementById('preview_poster').src = `/b/competition/${competition.po
type = 'paint';
}
}
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
const minInput = document.getElementById('min_val');
const maxInput = document.getElementById('max_val');
@ -638,6 +645,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
// @license-end
</script>
{%- endmacro %}

View File

@ -3,6 +3,7 @@
{% macro section(user) -%}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
var type = 'paint';
function toggleType(clickedId) {
@ -18,6 +19,7 @@
type = 'paint';
}
}
// @license-end
</script>
<form class="c_sbrxk">
@ -348,10 +350,13 @@ function bindLinkValidation({
});
// @license-end
</script>
{% if user.send_address %}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.getElementById('address').value = '{{ user.send_address[-1] }}'
// @license-end
</script>
{% endif %}
<script defer>
@ -373,8 +378,10 @@ document.getElementById('address').value = '{{ user.send_address[-1] }}'
}
setTomorrowAtNoon();
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
// Основная логика
document.addEventListener('DOMContentLoaded', () => {
const minInput = document.getElementById('min_val');
@ -568,6 +575,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Инициализация
// updateRange();
});
// @license-end
</script>
{%- endmacro %}

View File

@ -153,6 +153,7 @@
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
(function(){
const input = document.getElementById('memberSearch');
const container = document.querySelector('.c_cnedr');
@ -178,6 +179,7 @@
filter();
})();
// @license-end
</script>

View File

@ -0,0 +1,52 @@
{% macro details(user) -%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{user._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{user.phone.split(',')[-1].strip()}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ user.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{user.email}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{user.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if user.vk_id %}https://vk.ru/im?sel={{user.vk_id}}{%else%}#{%endif%}" {% if
not user.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})" {%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{%- endmacro %}
{% macro style() -%}
<style>
.menua{width: 100%;display: flex;flex-direction: column;gap: 7px;margin: 10px 20px;}
.icona{display: flex;gap: 10px;align-items: center;}
.icona>span{color: #5F1798;}
</style>
{%- endmacro %}

View File

@ -1,5 +1,6 @@
{% macro section(user) -%}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function toggleMenu() {
const button = document.getElementById('menu_button');
const lines = button.querySelectorAll('.line');
@ -23,11 +24,13 @@ document.addEventListener('DOMContentLoaded', () => {
menu2.classList.toggle('open', newState);
});
});
// @license-end
</script>
{% if user %}
{% if user.notify %}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function removeShow() {
document.getElementById('notificationsDropdown').classList.remove('show');
document.getElementById('closeNotify').classList.remove('show');
@ -156,7 +159,8 @@ document.addEventListener('DOMContentLoaded', () => {
addNotification(item.title, item.text, item.dateStr, item.link, item.read);
});
});
</script>
// @license-end
</script>
{% endif %}
{% endif %}
<header class="c_xirl1">
@ -198,6 +202,7 @@ document.addEventListener('DOMContentLoaded', () => {
{% if user and 'back' in user.get('scopes',[]) %}
<span id="backBtn" class="c_qmp95 b_fokm1 btn">Переключиться на Админа</span>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('backBtn').addEventListener('click', async () => {
try {
@ -213,7 +218,8 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
});
</script>
// @license-end
</script>
{% endif %}
<span
id="shareBtn" data-imagetext="🎨 Рекомендую! 👍"

View File

@ -71,6 +71,7 @@
console.error('Network error during logout', err);
}
}
// @license-end
</script>
<script defer>
async function switchProfile() {
@ -86,6 +87,7 @@
console.error(err);
}
}
// @license-end
</script>
<script defer>
document.addEventListener('DOMContentLoaded', () => {
@ -122,5 +124,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
});
// @license-end
</script>
{% endmacro %}

View File

@ -2,6 +2,7 @@
{% import "/components/header.html" as header %}
{% import "/components/footer.html" as footer %}
{% import "components/share.html" as share %}
{% import "/components/contacts.html" as contacts %}
{% block title %}{{exhibition.title}} | Картины{% endblock %}
@ -42,6 +43,14 @@
</div>
<header class="c_77wsp b_qx462">
<h2 class="c_aiv78 b_6jj0l">{{exhibition.title}}</h2>
<div class="b_xz6em">
<img src="/a/{{exhibition.user._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Организатор</span>
<span class="c_zm0m4 b_09est">{{exhibition.user.username}}</span>
</div>
</div>
{{ contacts.details(exhibition.user) }}
{% if exhibition.get('tizer_file_id') or exhibition.get('tizer_link') %}
<a class="c_wcdb7 b_ofiw3" href="{% if exhibition.get('tizer_link') %}{{exhibition.tizer_link}}{% else %}/b/exhibition/{{ exhibition.tizer_file_id }}{% endif %}" rel="noopener noreferrer" style="width: 100%;margin: 10px 0;">
<span class="b_j2ln2">Релиз выставки</span>
@ -118,6 +127,7 @@
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, {
method: 'PUT'
@ -148,8 +158,10 @@
});
});
}
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
(function(){
const input = document.getElementById('memberSearch');
const container = document.querySelector('.c_cnedr');
@ -175,6 +187,7 @@
filter();
})();
// @license-end
</script>
@ -195,6 +208,7 @@
<meta name="description" content="{{exhibition.short_desc}}">
<meta property="og:site_name" content="Конкурсы | KULTMOST.АВТОПОСТ">
{{ header.style() }}
{{ contacts.style() }}
<style>
.b_0jst1 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_vabj9 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }

View File

@ -1,11 +1,11 @@
{% extends "base.html" %}
{% import "/components/header.html" as header %}
{% import "/components/footer.html" as footer %}
{% import "/components/contacts.html" as contacts %}
{% import "components/share.html" as share %}
{% block title %}Выставки{% endblock %}
{% block content %}
{{ header.section(user) }}
<section class="c_cr9xg">
@ -18,22 +18,17 @@
{% for exhibition in exhibitions %}
{% set link="/exhibition/{}".format(exhibition._id) %}
<article class="c_9fgdh b_ax8vz" id="l{{exhibition._id}}">
{% if link %}
<a href="{{link}}">
{% endif %}
{% if link %}<a href="{{link}}">{% endif %}
<div class="b_ornr9">
<picture>
<img crossorigin="anonymous" fetchpriority="high" loading="eager" alt="{{ exhibition.title }}" class="b_ornr9"
src="/b/exhibition/{{ exhibition.poster_file_id }}" alt="Выставка {{exhibition.title }}">
<img crossorigin="anonymous" fetchpriority="high" loading="eager" alt="{{ exhibition.title }}"
class="b_ornr9" src="/b/exhibition/{{ exhibition.poster_file_id }}"
alt="Выставка {{exhibition.title }}">
</picture>
</div>
{% if link %}
</a>
{% endif %}
{% if link %}</a>{% endif %}
<div class="c_z3dpx b_73p10">
{% if link %}
<a href="{{link}}" class="b_73p10">
{% endif %}
{% if link %}<a href="{{link}}" class="b_73p10">{% endif %}
<div class="{% if now > exhibition.date_finish %}b_vabj9_2{%else%}b_vabj9{% endif %}">
{% if now > exhibition.date_finish %}
<span class="c_95csi b_0jst1_2">Закончилась</span>
@ -47,17 +42,22 @@
<h3 class="c_hysjg b_8f3uh">{{ exhibition.title }}</h3>
</header>
<p class="c_2fr39 b_tfnz2">{{ exhibition.topic }}</p>
{% set user = exhibition.get('user') %}
{% if link %}</a>{% endif %}
{% set creator = exhibition.get('user') %}
{% if creator %}
<div class="c_dbbe4 b_otttb">
{% if user %}
<img class="c_pm80w b_a68fq" src="/a/{{user._id}}" alt="{{ user.username | e }}" crossorigin="anonymous" fetchpriority="high" loading="eager">
<img class="c_pm80w b_a68fq" src="/a/{{creator._id}}" alt="{{ creator.username | e }}"
crossorigin="anonymous" fetchpriority="high" loading="eager">
<div class="c_4402p b_ntazj">
<p class="c_kvah4 b_ghosy">{{ user.username | e }}</p>
<p class="c_kvah4 b_ghosy">{{ creator.username | e }}</p>
</div>
</div>
{{ contacts.details(creator) }}
{% endif %}
</div>
{% if exhibition.get('tizer_file_id') or exhibition.get('tizer_link') %}
<a class="c_aq9et" href="{% if exhibition.get('tizer_link') %}{{exhibition.tizer_link}}{% else %}/b/exhibition/{{ exhibition.tizer_file_id }}{% endif %}" rel="noopener noreferrer">
<a class="c_aq9et"
href="{% if exhibition.get('tizer_link') %}{{exhibition.tizer_link}}{% else %}/b/exhibition/{{ exhibition.tizer_file_id }}{% endif %}"
rel="noopener noreferrer">
<span class="c_som9s">Релиз выставки</span>
</a>
{%endif%}
@ -66,21 +66,20 @@
</a>
<p>Место проведения: {{ exhibition.get('place') }}</p>
<div class="b_otttb">
<span class="c_2fr39 b_tfnz2">С {{ ( exhibition.datetime_start.strftime('%d.%m.%Y')) if exhibition.datetime_start else ''}}</span>
<span class="c_2fr39 b_tfnz2">По {{ ( exhibition.date_finish.strftime('%d.%m.%Y')) if exhibition.date_finish else ''}}</span>
<span class="c_2fr39 b_tfnz2">С {{ ( exhibition.datetime_start.strftime('%d.%m.%Y')) if
exhibition.datetime_start else ''}}</span>
<span class="c_2fr39 b_tfnz2">По {{ ( exhibition.date_finish.strftime('%d.%m.%Y')) if
exhibition.date_finish else ''}}</span>
</div>
{% if link %}
</a>
{% endif %}
<div class="fl">
<span class="c_2fr39 b_tfnz2" style="z-index: 2;">
{{ share.share('/exhibition/{}'.format(exhibition._id), exhibition.title, '/b/exhibition/{}'.format(exhibition.poster_file_id), "Приглашаем на выставку") }}
{{ share.share('/exhibition/{}'.format(exhibition._id), exhibition.title,
'/b/exhibition/{}'.format(exhibition.poster_file_id), "Приглашаем на выставку") }}
</span>
</div>
<p class="c_drpqj b_mp1wb">{{ (exhibition._id.generation_time.strftime('%d.%m.%Y')) if exhibition._id else ''}}</p>
</div>
</article>
</article>
{% endfor %}
</div>
</section>
@ -89,18 +88,10 @@
{% block head %}
{{ header.style() }}
<style>/* Base (common) styles */
.c_aq9et {
cursor: pointer;
transition: transform 0.1s linear, box‑shadow 0.1s linear;
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
}
.c_aq9et:active {
transform: scale(0.9);
box-shadow: 5px 5px 5px rgba(95, 23, 152, 0.32);
}
{{ contacts.style() }}
<style>
.c_aq9et {cursor: pointer;transition: transform 0.1s linear, box‑shadow 0.1s linear;box-shadow: 0 0 0 rgba(0, 0, 0, 0);}
.c_aq9et:active {transform: scale(0.9);box-shadow: 5px 5px 5px rgba(95, 23, 152, 0.32);}
article>a{width: 100%;}
picture>img{ width: 100%; object-fit: scale-down; }
.fl{display: flex;justify-content: space-between;width: 100%;}
@ -130,25 +121,20 @@ picture>img{ width: 100%; object-fit: scale-down; }
.b_ornr9{height: auto;}
}
</style>
<style>/* Base (common) styles */
<style>
.b_p0hpm { color: #5F1798; font-family: Inter; font-size: 42px; font-style: normal; font-weight: 700; line-height: normal; text-transform: uppercase; }
/* Mobile */
@media (max-width: 699px) {
.c_dbu4q { align-content: flex-start; align-items: flex-start; align-self: stretch; display: flex; flex-wrap: wrap; gap: 20px; justify-content: center; }
.c_fwe1m { align-items: center; display: flex; flex-direction: column; gap: 20px; width: 335px; }
.c_s7it2 { align-items: flex-start; align-self: stretch; display: flex; gap: 20px; justify-content: center; }
.c_cr9xg { align-items: center; background: #FFF; display: flex; flex-direction: column; gap: 30px; }
}
/* Tablet */
@media (min-width: 700px) and (max-width: 900px) {
.c_dbu4q { align-content: flex-start; align-items: flex-start; align-self: stretch; display: flex; flex-wrap: wrap; gap: 20px; }
.c_fwe1m { align-items: flex-start; display: flex; flex-direction: column; gap: 20px; width: 335px; }
.c_s7it2 { align-items: flex-start; align-self: stretch; display: flex; gap: 20px; }
.c_cr9xg { align-items: center; background: #FFF; display: flex; flex-direction: column; gap: 30px; padding-left: 50px; }
}
/* Desktop */
@media (min-width: 901px) {
.c_dbu4q { align-content: flex-start; align-items: flex-start; display: flex; flex-wrap: wrap; gap: 20px; width: 100%; }
.c_fwe1m { align-items: flex-start; display: flex; flex-direction: column; flex-shrink: 0; gap: 20px; width: 335px; }

View File

@ -203,12 +203,16 @@
</form>
{% if user.send_address %}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.getElementById('address').value = '{{ user.send_address[-1] }}'
// @license-end
</script>
{% endif %}
{% if user.places %}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.getElementById('place').value = '{{ user.places[-1] }}'
// @license-end
</script>
{% endif %}
<script defer>
@ -363,6 +367,7 @@ function bindLinkValidation({
linkErrorSelector: '#link-error-1'
});
// @license-end
</script>
<script defer>
@ -384,6 +389,7 @@ function bindLinkValidation({
}
setTomorrowAtNoon();
// @license-end
</script>
<script defer>
@ -551,6 +557,7 @@ function bindLinkValidation({
}
});
// @license-end
</script>

View File

@ -293,6 +293,7 @@ bindLinkValidation({
linkInputSelector: '#tizer_link',
linkErrorSelector: '#link-error-1'
});
// @license-end
</script>
<script defer>
function setTomorrowAtNoon() {
@ -313,6 +314,7 @@ bindLinkValidation({
}
setTomorrowAtNoon();
// @license-end
</script>
<script defer>
@ -545,6 +547,7 @@ bindLinkValidation({
}
});
// @license-end
</script>

View File

@ -44,6 +44,7 @@ fetch('/api/me')
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
// @license-end
</script>
{% endif %}
{% endblock %}

View File

@ -60,19 +60,19 @@
</div>
</form>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function sendToNextPage(e) {
e.preventDefault();
const email = document.getElementById('email').value.trim();
if (email) {
localStorage.setItem('restoreEmail', email);
}
window.location.href = "/restore{% if next %}?next={{next}}{% endif %}";
}
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
@ -210,9 +210,11 @@ function sendToNextPage(e) {
});
}
});
// @license-end
</script>
{{ footer.section() }}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
fetch('/api/me')
.then(response => {
if (response.ok) {
@ -229,6 +231,7 @@ fetch('/api/me')
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
// @license-end
</script>
{% endblock %}

View File

@ -20,19 +20,19 @@
<iframe id="modalVideoFrame" width="853" height="480" allow="autoplay; encrypted-media; fullscreen; picture-in-picture;" frameborder="0" allowfullscreen style="height: 100%;position: absolute;left: 0;transition: none;min-width: 100%;object-fit: scale-down;"></iframe>
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('openModalButtonVideo').addEventListener('click', function() {
const modalVideoFrame = document.getElementById('modalVideoFrame');
const modal = document.getElementById('modalVideo');
modalVideoFrame.src = 'https://vk.com/video_ext.php?oid=-213856894&id=456239020&hd=2';
modal.style.display = 'flex';
});
});
});
function videocloseModalButton() {
document.getElementById('modalVideo').style.display = 'none';
}
}
// @license-end
</script>
{%- endmacro %}

View File

@ -19,12 +19,14 @@
</div>
{% if news%}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('title').value = '{{news.title or ""}}';
document.getElementById('description').value = `{{news.description or ""}}`;
document.getElementById('short_description').value = `{{news.short_description or ""}}`;
});
// @license-end
</script>
{% endif %}
<div class="c_rtopc">
@ -38,6 +40,7 @@
</form>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview');
@ -290,6 +293,7 @@
});
});
});
// @license-end
</script>

View File

@ -21,9 +21,11 @@
</div>
{% if post%}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.getElementById('title').value = `{{post.title or ""}}`
document.getElementById('description').value = `{{post.description or ""}}`
document.getElementById('short_description').value = `{{post.short_description or ""}}`
// @license-end
</script>
{% endif %}
<div class="c_rtopc">
@ -37,6 +39,7 @@
</form>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview');
@ -289,6 +292,7 @@
});
});
});
// @license-end
</script>

View File

@ -281,6 +281,7 @@
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const images = [
{% for media in news.orig_media %}
{ src: "/b/news/{{media}}" },
@ -386,6 +387,7 @@
createSlides();
init();
setTimeout(updateTransform, 100); // даём браузеру время на рендер
</script>
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -207,6 +207,7 @@
</section>
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, {
method: 'PUT'
@ -237,8 +238,10 @@
});
});
}
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const images = [
{% for media in paint.medias_original %}
{ src: "/b/posts/{{media}}" },
@ -344,8 +347,10 @@
createSlides();
init();
setTimeout(updateTransform, 100); // даём браузеру время на рендер
</script>
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function deletePost(postId) {
fetch(`/api/post/${postId}`, {
method: 'DELETE'
@ -374,6 +379,7 @@
});
});
}
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -52,6 +52,7 @@
</div>
</form>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const post = JSON.parse({{ post|tojson }});
const postId = post._id;
@ -415,6 +416,7 @@
window.addEventListener('beforeunload', () => {
filesOrdered.forEach(revokeObjectURLSafe);
});
// @license-end
</script>

View File

@ -21,10 +21,12 @@
<label for="grade" class="c_z3ybu floating-label">Звание</label>
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
showToast({message: 'Выберите из списка актуальное звание', status: 'info', duration:3000});
});
</script>
// @license-end
</script>
{% endif %}
<div class="c_aihm6 floating-group">
<input name="title" id="title" class="c_xhd5k" required data-toast="Поле Название должно быть заполнено"/>
@ -133,6 +135,7 @@
</div>
</form>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview');
const createBtn = document.getElementById('createBtn');
@ -466,8 +469,10 @@
window.addEventListener('beforeunload', () => {
filesOrdered.forEach(revokeObjectURLSafe);
});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('genre').dataset.list=JSON.stringify([
{
@ -588,6 +593,7 @@
}
])
})
// @license-end
</script>
{{ footer.section() }}
{% endblock %}

View File

@ -39,6 +39,7 @@
{%- endmacro %}
{% macro scrips() -%}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function deletePost(postId) {
fetch(`/api/post/${postId}`, {
method: 'DELETE'
@ -67,6 +68,7 @@
});
});
}
// @license-end
</script>
{%- endmacro %}
{% macro style() -%}

View File

@ -105,6 +105,7 @@
</table>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function toggleDetails(cell) {
const mainRow = cell.parentElement;
const detailsRow = mainRow.nextElementSibling;
@ -139,6 +140,7 @@ document.querySelectorAll('.main-row').forEach(row => {
}
});
});
// @license-end
</script>
<style>

View File

@ -60,6 +60,7 @@
</table>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function normalizeText(str) {
return str
.toLowerCase()
@ -105,7 +106,8 @@ function normalizeText(str) {
row.classList.toggle("hidden", !match);
}
}
</script>
// @license-end
</script>

View File

@ -63,6 +63,7 @@
{% endfor %}
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
(function(){
const input = document.getElementById('memberSearch');
const container = document.querySelector('.c_tihtk');
@ -89,8 +90,10 @@
// initial: show all
filter();
})();
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.querySelectorAll('.post').forEach(postEl => {
const fg = postEl.querySelector('.c_nb6gy.floating-group');
if (!fg) return;
@ -133,6 +136,7 @@ document.querySelectorAll('.post').forEach(postEl => {
}
});
});
// @license-end
</script>
<script defer>
@ -364,6 +368,7 @@ function loadAndClick() {
}
});
// @license-end
</script>
</div>
</div>
@ -404,6 +409,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
});
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -4,6 +4,7 @@
{% import "components/profile_card.html" as profile_card %}
{% import "components/profile_nav.html" as profile_nav %}
{% import "competition/list.html" as competition %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Где картины{% endblock %}
@ -25,7 +26,8 @@
</figcaption>
</figure>
{% for doc in data %}
<figure class="c_4b1hy b_uu2ub" data-delivery-id="{{doc._id}}" data-sender="{{doc.sender._id}}" data-receiver="{{doc.receiver._id}}">
<figure class="c_4b1hy b_uu2ub" data-delivery-id="{{doc._id}}" data-sender="{{doc.sender._id}}"
data-receiver="{{doc.receiver._id}}">
<figcaption class="c_3g8gh b_pvw5a" data-id="{{doc._id}}">
{% if doc.status == 'Ожидает отправки' %}
<div class="b_thlt1">
@ -55,56 +57,20 @@
</div>
</div>
{% if doc.get('user') and user._id == doc.user._id%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{doc.receiver._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{doc.receiver.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ doc.receiver.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{doc.receiver.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{doc.receiver.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if doc.receiver.vk_id %}https://vk.ru/im?sel={{doc.receiver.vk_id}}{%else%}#{%endif%}"
{% if not doc.receiver.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% endif %}
{{ contacts.details(doc.receiver) }}
{% endif %}
{% if doc.get('user') %}
<div class="b_xz6em">
<img src="/a/{{doc.user._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Художник</span>
<span class="c_zm0m4 b_09est">{{doc.user.username}}</span>
</div>
</div>
{% if user._id != doc.user._id%}
{{ contacts.details(doc.user) }}
{% endif %}
{% endif %}
{% else %}
{% if doc.address %}
<p class="b_6spaf">Адрес: {{doc.address}}</p>
@ -127,56 +93,8 @@
</div>
</div>
{% if user._id != doc.sender._id%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{doc.sender._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{doc.sender.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ doc.sender.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{doc.sender.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{doc.sender.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if doc.sender.vk_id %}https://vk.ru/im?sel={{doc.sender.vk_id}}{%else%}#{%endif%}"
{% if not doc.sender.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{doc.sender.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{doc.sender.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{doc.sender.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% endif %}
{{ contacts.details(doc.sender) }}
{% endif %}
{% endif %}
{% if doc.receiver %}
<div class="b_xz6em">
@ -188,61 +106,14 @@
</div>
</div>
{% if user._id != doc.receiver._id%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{doc.receiver._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{doc.receiver.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ doc.receiver.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{doc.receiver.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{doc.receiver.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if doc.receiver.vk_id %}https://vk.ru/im?sel={{doc.receiver.vk_id}}{%else%}#{%endif%}"
{% if not doc.receiver.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% endif %}
{{ contacts.details(doc.receiver) }}
{% endif %}
{% endif %}
{% endif %}
<p class="b_6spaf">
{{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}}
<b>{{ '%02d' % ((doc._id.generation_time.hour + 3) % 24) if doc._id else '' }}:{{ '%02d' % doc._id.generation_time.minute if doc._id else '' }}</b>
<b>{{ '%02d' % ((doc._id.generation_time.hour + 3) % 24) if doc._id else '' }}:{{ '%02d' %
doc._id.generation_time.minute if doc._id else '' }}</b>
</p>
</figcaption>
</figure>
@ -251,7 +122,6 @@
</div>
</div>
</section>
{{ footer.section(user) }}
{% endblock %}
@ -259,6 +129,7 @@
{{ header.style() }}
{{ competition.style() }}
{{ profile_nav.style() }}
{{ contacts.style() }}
<style>/* Base (common) styles */
.c_aihm6{width: 100%;}
.mw{margin: 0 20px;
@ -394,15 +265,6 @@ picture>img{ width: 100%; object-fit: scale-down; }
}
</style>
<style>
.menua{width: 100%;
display: flex;
flex-direction: column;
gap: 7px;
margin: 10px 20px;}
.icona{display: flex;
gap: 10px;
align-items: center;}
.icona>span{color: #5F1798;}
.c_hysjg {
color: #0ED646;
font-family: Inter;

View File

@ -4,11 +4,13 @@
{% import "components/profile_card.html" as profile_card %}
{% import "components/profile_nav.html" as profile_nav %}
{% import "competition/list.html" as competition %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Где картины{% endblock %}
{% macro render_doc(doc) %}
<figure class="c_4b1hy b_uu2ub" data-exhibition_id="{{doc.get('exhibition_id')}}" data-delivery-id="{{doc._id}}" data-sender="{{doc.sender._id}}" data-receiver="{{doc.receiver._id}}">
<figure class="c_4b1hy b_uu2ub" data-exhibition_id="{{doc.get('exhibition_id')}}" data-delivery-id="{{doc._id}}"
data-sender="{{doc.sender._id}}" data-receiver="{{doc.receiver._id}}">
<img class="b_1fvkt" src="/b/posts/{{doc.img}}">
<figcaption class="c_3g8gh b_pvw5a" data-id="{{doc._id}}">
{% if doc.status == 'Ожидает отправки' %}
@ -33,10 +35,8 @@
<h2 class="c_9hvtd b_f5fb2">{{doc.title}}</h2>
{% if doc.status == 'На выставке' %}
<label class="offer-label">
<input type="checkbox"
onchange="saledPaintings('{{doc.post_id}}')"
class="offer-checkbox"
{% if doc.status == 'Продано' %}checked{%endif%}>
<input type="checkbox" onchange="saledPaintings('{{doc.post_id}}')" class="offer-checkbox" {% if
doc.status=='Продано' %}checked{%endif%}>
<span>Продано</span>
</label>
{% endif %}
@ -50,56 +50,20 @@
</div>
</div>
{% if doc.get('user') and user._id == doc.user._id%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{doc.receiver._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{doc.receiver.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ doc.receiver.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{doc.receiver.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{doc.receiver.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if doc.receiver.vk_id %}https://vk.ru/im?sel={{doc.receiver.vk_id}}{%else%}#{%endif%}"
{% if not doc.receiver.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% endif %}
{{ contacts.details(doc.receiver) }}
{% endif %}
{% if doc.get('user') %}
<div class="b_xz6em">
<img src="/a/{{doc.user._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Художник</span>
<span class="c_zm0m4 b_09est">{{doc.user.username}}</span>
</div>
</div>
{% if user._id != doc.user._id%}
{{ contacts.details(doc.user) }}
{% endif %}
{% endif %}
{% else %}
{% if doc.address %}
<p class="b_6spaf">Адрес: {{doc.address}}</p>
@ -123,56 +87,8 @@
</div>
</div>
{% if user._id != doc.sender._id%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{doc.sender._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{doc.sender.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ doc.sender.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{doc.sender.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{doc.sender.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if doc.sender.vk_id %}https://vk.ru/im?sel={{doc.sender.vk_id}}{%else%}#{%endif%}"
{% if not doc.sender.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{doc.sender.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{doc.sender.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{doc.sender.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% endif %}
{{ contacts.details(doc.sender) }}
{% endif %}
{% endif %}
{% if doc.receiver %}
@ -185,75 +101,22 @@
</div>
</div>
{% if user._id != doc.receiver._id%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{doc.receiver._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{doc.receiver.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ doc.receiver.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{doc.receiver.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{doc.receiver.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if doc.receiver.vk_id %}https://vk.ru/im?sel={{doc.receiver.vk_id}}{%else%}#{%endif%}"
{% if not doc.receiver.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% endif %}
{{ contacts.details(doc.receiver) }}
{% endif %}
{% endif %}
{# Checkbox instead of per-item button #}
{% if doc.status in ['Ожидает отправки', 'Возврат художнику'] and user._id == doc.sender._id %}
<label class="offer-label delivery-checkbox-label">
<input type="checkbox"
class="offer-checkbox delivery-checkbox send-checkbox"
data-id="{{doc._id}}"
data-sender="{{doc.sender._id}}"
data-receiver="{{doc.receiver._id}}">
<input type="checkbox" class="offer-checkbox delivery-checkbox send-checkbox" data-id="{{doc._id}}"
data-sender="{{doc.sender._id}}" data-receiver="{{doc.receiver._id}}">
<span>Выбрать для отправки</span>
</label>
{% endif %}
{% if doc.status == 'Ожидает получения' and user._id == doc.receiver._id %}
<label class="offer-label delivery-checkbox-label">
<input type="checkbox"
class="offer-checkbox delivery-checkbox accept-checkbox"
data-id="{{doc._id}}">
<input type="checkbox" class="offer-checkbox delivery-checkbox accept-checkbox" data-id="{{doc._id}}">
<span>Выбрать для получения</span>
</label>
{% endif %}
@ -261,7 +124,8 @@
<p class="b_6spaf">
{{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}}
<b>{{ '%02d' % ((doc._id.generation_time.hour + 3) % 24) if doc._id else '' }}:{{ '%02d' % doc._id.generation_time.minute if doc._id else '' }}</b>
<b>{{ '%02d' % ((doc._id.generation_time.hour + 3) % 24) if doc._id else '' }}:{{ '%02d' %
doc._id.generation_time.minute if doc._id else '' }}</b>
</p>
</figcaption>
</figure>
@ -362,6 +226,7 @@
</button>
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, {
method: 'PUT'
@ -392,8 +257,10 @@
});
});
}
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function selectAll(elem, send=true){
if (send){
if (elem.checked){
@ -420,8 +287,10 @@
updateStickyButtons();
}
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
// ─────────────────────────────────────────────
// Helpers: collect selected IDs
// ─────────────────────────────────────────────
@ -668,6 +537,7 @@
}
});
});
// @license-end
</script>
<script defer>
function toggleTrackField() {
@ -717,8 +587,10 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
document.getElementById('exhibition_id').value = exhibition_id;
}
filterFigure(exhibition_id);
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const deliveryType = document.getElementById('delivery_type');
const track = document.getElementById('track');
@ -734,6 +606,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
deliveryType.addEventListener('change', toggleTrack);
toggleTrack();
// @license-end
</script>
</div>
@ -748,17 +621,9 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
{{ header.style() }}
{{ competition.style() }}
{{ profile_nav.style() }}
{{ contacts.style() }}
<style>
/* Base (common) styles */
.menua{width: 100%;
display: flex;
flex-direction: column;
gap: 7px;
margin: 10px 20px;}
.icona{display: flex;
gap: 10px;
align-items: center;}
.icona>span{color: #5F1798;}
.c_aihm6{width: 100%;}
.mw{margin: 0 20px;
width: 100%;}

View File

@ -4,6 +4,7 @@
{% import "components/profile_card.html" as profile_card %}
{% import "components/profile_nav.html" as profile_nav %}
{% import "competition/list.html" as competition %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Выставки{% endblock %}
@ -40,14 +41,17 @@
<h3 class="c_hysjg b_8f3uh" id="preview_title">{{i.title}}</h3>
</header>
<p class="c_2fr39 b_tfnz2" id="preview_topic">{{i.topic}}</p>
<div class="c_dbbe4 b_otttb">
</a>
{% if i.user %}
<div class="c_dbbe4 b_otttb">
<img crossorigin="anonymous" fetchpriority="high" loading="eager" class="c_pm80w b_a68fq" src="/a/{{i.user._id}}" alt="{{ i.user.username | e }}">
<div class="c_4402p b_ntazj">
<p class="c_kvah4 b_ghosy">{{ i.user.username }}</p>
</div>
{% endif %}
</div>
{{ contacts.details(i.user) }}
{% endif %}
<a class="b_73p10" href="/exhibition/{{i._id}}">
<p class="c_2fr39 b_tfnz2">Подробнее</p>
</a>
<p class="c_drpqj b_mp1wb">{{ ( i._id.generation_time.strftime('%d.%m.%Y')) if i._id else ''}}</p>
@ -58,7 +62,6 @@
</div>
</div>
</section>
{{ footer.section(user) }}
{% endblock %}
@ -66,28 +69,21 @@
{{ header.style() }}
{{ profile_nav.style() }}
{{ competition.style() }}
{{ contacts.style() }}
<style>
/* Mobile */
@media (max-width: 699px) {
.c_mrl59 { align-items: center; background: #5F1798; border-radius: 25px; bottom: -15px; display: flex; height: 50px; justify-content: center; position: absolute; right: -15px; width: 50px; }
}
/* Tablet */
@media (min-width: 700px) and (max-width: 900px) {
.c_iiehk { align-items: center; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 335px; }
.c_nnsh6>a>img{display: none;}
}
/* Desktop */
@media (min-width: 901px) {
.c_iiehk { align-items: flex-end; border-radius: 5px; display: flex; flex-direction: column; gap: 20px; width: 310px; }
.c_nnsh6>a>img{display: none;}
}</style>
{{ footer.style() }}
{% endblock %}

View File

@ -217,6 +217,7 @@
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.getElementById('female_name_lastname').value = `{% if user.get('female_name_lastname') is not none %}{{user.get('female_name_lastname','') }}{% endif %}`
document.getElementById('birthday').value = `{% if user.get('birthday') is not none %}{{user.get('birthday','') }}{% endif %}`
document.getElementById('living_place').value = `{% if user.get('living_place') is not none %}{{user.get('living_place','') }}{% endif %}`
@ -248,12 +249,16 @@ document.getElementById('own_workshop').value = `{% if user.get('own_workshop')
document.getElementById('workshop_tour').value = `{% if user.get('workshop_tour') is not none %}{{user.get('workshop_tour','') }}{% endif %}`
document.getElementById('biography').value = `{% if user.get('biography') is not none %}{{user.get('biography','') }}{% endif %}`
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("day"),t=document.getElementById("month"),l=document.getElementById("year"),n=document.getElementById("birthday"),a=new Date().getFullYear(),u=["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"];function i(t=31){e.innerHTML="";for(let l=1;l<=t;l++){let n=document.createElement("option");n.value=String(l),n.text=String(l),e.appendChild(n)}}function o(){t.innerHTML="",u.forEach((e,l)=>{let n=document.createElement("option");n.value=String(l+1),n.text=e,t.appendChild(n)})}function v(e=1900,t=a){l.innerHTML="";for(let n=t;n>=e;n--){let u=document.createElement("option");u.value=String(n),u.text=String(n),l.appendChild(u)}}let d=e=>String(e).padStart(2,"0");function p(e){if(!e)return;let n=e.split("-");if(3!==n.length)return;let[a,u,i]=n.map(Number);g(l,String(a)),g(t,String(u)),s(a,u,i)}function r(){let a=Number(l.value)||1900,u=Number(t.value)||1,o=Number(e.value)||1,v=new Date(a,u,0).getDate();e.options.length!==v&&i(v),o>v&&(o=v);let p=`${a}-${d(u)}-${d(o)}`;n.value=p,g(e,String(o))}function s(t,l,n){let a=new Date(Number(t),Number(l),0).getDate(),u=Number(e.value)||1;i(a),g(e,String(Math.min(n||u,a)))}function g(e,t){let l=Array.from(e.options).find(e=>e.value===t||e.text===t);if(l)e.value=l.value;else{let n=document.createElement("option");n.text=t,n.value=t,e.appendChild(n),e.value=t}}o(),v();let h=n.value?Number(n.value.split("-")[0]):l.options.length?Number(l.options[0].value):a,c=n.value?Number(n.value.split("-")[1]):1;n.value&&n.value.split("-")[2];let f=new Date(h,c,0).getDate();i(f),n.value?p(n.value):(!l.value&&l.options.length&&(l.value=l.options[0].value),!t.value&&t.options.length&&(t.value=t.options[0].value),!e.value&&e.options.length&&(e.value=e.options[0].value),r()),n.addEventListener("change",()=>{p(n.value)}),l.addEventListener("change",()=>{let e=Number(l.value),n=Number(t.value);s(e,n),r()}),t.addEventListener("change",()=>{let e=Number(l.value),n=Number(t.value);s(e,n),r()}),e.addEventListener("change",r)});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
const createBtn = document.getElementById('createBtn');
@ -328,6 +333,7 @@ window.location.reload();
});
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -3,6 +3,7 @@
{% import "/components/footer.html" as footer %}
{% import "components/profile_card.html" as profile_card %}
{% import "components/profile_nav.html" as profile_nav %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Приглашения{% endblock %}
@ -199,6 +200,7 @@
</button>
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function selecteAll(elem){
if (elem.checked){
document.querySelectorAll('.yes').forEach(el=>{
@ -215,6 +217,7 @@
updateSelectedCount()
}
// @license-end
</script>
{%endif%}
{% if doc.get('type_')=='paint' %}
@ -253,6 +256,7 @@
{# Sticky bottom action bar #}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function toggleTrackField() {
const deliveryType = document.getElementById('delivery_type');
const trackGroup = document.getElementById('track-group');
@ -275,8 +279,10 @@ document.addEventListener('DOMContentLoaded', toggleTrackField);
// Run every time the select changes
document.getElementById('delivery_type')?.addEventListener('change', toggleTrackField);
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const query = window.location.href.split('?')[1] || '';
let exhibition_id = null;
if (query) {
@ -302,8 +308,10 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
document.getElementById('exhibition_id').value = exhibition_id;
}
filterFigure(exhibition_id);
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const deliveryType = document.getElementById('delivery_type');
const track = document.getElementById('track');
@ -319,8 +327,10 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
deliveryType.addEventListener('change', toggleTrack);
toggleTrack();
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
// ─────────────────────────────────────────────
// Helpers: collect selected IDs
// ─────────────────────────────────────────────
@ -561,6 +571,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
}
});
});
// @license-end
</script>
{% endif %}
</div>
@ -568,6 +579,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const LS_KEY = 'selected_posts_ids_{{doc._id}}';
function safeParseArray(raw) {
@ -728,6 +740,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
document.addEventListener('DOMContentLoaded', () => {
setAllCheckboxesFromStorage();
});
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}
@ -742,45 +755,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
</div>
{% endif %}
</div>
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="tel:{{user.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg"
height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ user.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{user.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{user.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if user.social_vk %}https://vk.ru/im?sel={{user.social_vk.id}}{%else%}#{%endif%}">
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{{ contacts.details(user) }}
{% endmacro %}
{% macro render_links(doc) %}
{% if doc.get('exhibition')%}
@ -793,6 +768,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
{% block head %}
{{ header.style() }}
{{ profile_nav.style() }}
{{ contacts.style() }}
<style>
/* Base (common) styles */
.c_aihm6{width: 100%;}
@ -929,15 +905,6 @@ picture>img{ width: 100%; object-fit: scale-down; }
}
</style>
<style>
.menua{width: 100%;
display: flex;
flex-direction: column;
gap: 7px;
margin: 10px 20px;}
.icona{display: flex;
gap: 10px;
align-items: center;}
.icona>span{color: #5F1798;}
.c_hysjg {
color: #0ED646;
font-family: Inter;

View File

@ -56,6 +56,7 @@
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const deliveryType = document.getElementById('delivery_type');
const trackWrap = document.getElementById('trackWrap');
const track = document.getElementById('track');
@ -74,8 +75,10 @@
deliveryType.addEventListener('change', toggleTrack);
toggleTrack();
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('click', async (e) => {
const btn = e.target.closest('[data-id]');
if (!btn) return;
@ -143,6 +146,7 @@
}
});
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -3,11 +3,11 @@
{% import "/components/footer.html" as footer %}
{% import "components/profile_card.html" as profile_card %}
{% import "components/profile_nav.html" as profile_nav %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Приглашения{% endblock %}
{% block content %}
{{ header.section(user) }}
<section class="c_dg3k4">
@ -76,6 +76,7 @@
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function accept_inv(inv_id) {
try {
const res = await fetch(`/api/invitation/accept/${inv_id}`, {
@ -95,63 +96,27 @@ async function accept_inv(inv_id) {
showToast({ message: 'Ошибка сети/сервера', status: 'error' });
}
}
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}
{% macro render_user_contacts(user) %}
{% if user %}
<div class="c_dbbe4 b_otttb">
{% if user %}
<img class="c_pm80w b_a68fq" src="/a/{{user._id}}" alt="{{ user.username | e }}" crossorigin="anonymous"
fetchpriority="high" loading="eager">
<div class="c_4402p b_ntazj">
<p class="c_kvah4 b_ghosy">{{ user.username | e }}</p>
</div>
{% endif %}
</div>
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="tel:{{user.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg"
height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ user.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{user.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{user.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if user.social_vk %}https://vk.ru/im?sel={{user.social_vk.id}}{%else%}#{%endif%}">
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{{ contacts.details(user) }}
{% endif %}
{% endmacro %}
{% block head %}
{{ header.style() }}
{{ profile_nav.style() }}
{{ contacts.style() }}
<style>
/* Base (common) styles */
.c_aq9et {

View File

@ -32,6 +32,7 @@
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const competitionId = '{{competition._id}}';
const stopBtn = document.getElementById('stop-btn');
@ -56,11 +57,13 @@
console.error('Ошибка при остановке приема заявок', e);
}
});
</script>
// @license-end
</script>
{% elif competition.status == 1 %}
<div class="c_d4rhd b_wy3rt ba" id="stop-btn"><span class="c_4sl8i b_bvl0d">Завершить конкурс</span>
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const competitionId = '{{competition._id}}';
const stopBtn = document.getElementById('stop-btn');
@ -85,7 +88,8 @@
console.error('Ошибка при остановке приема заявок', e);
}
});
</script>
// @license-end
</script>
{% elif competition.status == 2 %}
<div class="c_d4rhd b_wy3rt ba"><span class="c_4sl8i b_bvl0d">Конкурс завершен</span></div>
{%endif%}
@ -219,8 +223,10 @@ document.querySelectorAll('.c_6cezu.b_6spaf').forEach(toggle => {
}
});
});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const competition_id = '{{competition._id}}';
document.querySelectorAll('.b_k2cle button').forEach(button => {
@ -270,6 +276,7 @@ document.querySelectorAll('.b_k2cle button').forEach(button => {
});
});
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -119,6 +119,7 @@
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const competitionId = '{{competition._id}}';
const memberId = '{{member._id}}';
@ -164,6 +165,7 @@ async function selectMember(status) {
passBtn.addEventListener('click', () => selectMember('pass'));
failBtn.addEventListener('click', () => selectMember('fail'));
// @license-end
</script>
{%endif%}
</div>
@ -223,8 +225,10 @@ document.querySelectorAll('.c_6cezu.b_6spaf').forEach(toggle => {
}
});
});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const competition_id = '{{competition._id}}';
document.querySelectorAll('.b_k2cle button').forEach(button => {
@ -271,6 +275,7 @@ document.querySelectorAll('.b_k2cle button').forEach(button => {
});
});
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -26,6 +26,7 @@
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.querySelectorAll('.c_2fr39.b_tfnz2#deleteBtn').forEach(btn => {
btn.addEventListener('click', async (e) => {
const article = btn.closest('article');
@ -87,6 +88,7 @@ document.querySelectorAll('#rePublishCompetition').forEach(btn => {
});
});
// @license-end
</script>

View File

@ -98,6 +98,7 @@
{% endfor %}
];
document.getElementById('filter-galerists').dataset.list=JSON.stringify(galerists)
// @license-end
</script>
<script defer>
const living_places = [
@ -109,6 +110,7 @@ document.getElementById('filter-galerists').dataset.list=JSON.stringify(galerist
{% endfor %}
];
document.getElementById('filter-city').dataset.list=JSON.stringify(living_places)
// @license-end
</script>
<script async>
document.addEventListener('DOMContentLoaded', () => {
@ -242,8 +244,10 @@ genre = genres.map(g => {
document.getElementById('filter-genre').dataset.list=JSON.stringify(genre)
})
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const initialPostsData = [
{% for p in posts %}
{
@ -272,9 +276,11 @@ genre = genres.map(g => {
title: "", posts: []
{% endif %}
};
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const selectedPostIds = new Set((exhibition?.posts || []).map(String));
const container = document.querySelector('.c_tihtk');
const showMoreBtn = document.getElementById('show-more-btn');
@ -491,6 +497,7 @@ genre = genres.map(g => {
document.getElementById('apply-filters').addEventListener('click', applyFilters);
showMoreBtn.addEventListener('click', loadMore);
});
// @license-end
</script>
{{ footer.section(user) }}

View File

@ -5,6 +5,7 @@
{% import "components/profile_nav.html" as profile_nav %}
{% import "competition/list.html" as competition %}
{% import "components/label.html" as label %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Отобранные картины | Выставка {{exhibition.title}}{% endblock %}
@ -101,55 +102,7 @@
</div>
{% endif %}
</div>
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{post.user._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{post.user.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ post.user.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{post.user.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{post.user.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if post.user.vk_id %}https://vk.ru/im?sel={{post.user.vk_id}}{%else%}#{%endif%}"
{% if not post.user.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{post.user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{post.user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{post.user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{{ contacts.details(post.user) }}
{% if post.status=='В каталоге' %}
<button onclick="selectPost(this, '{{post._id}}')" class="c_ctfod b_289i1">
<span class="c_d2l0b b_al8e7">Убрать с выставки</span>
@ -174,6 +127,7 @@
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, {
method: 'PUT'
@ -204,9 +158,11 @@
});
});
}
// @license-end
</script>
{#
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function btnAccept(btn, itemId){
const figcaption = btn.parentElement;
const statusDiv = figcaption.querySelectorAll('div')[0];
@ -245,9 +201,11 @@
showToast({ message: 'Не удалось подключиться к серверу', duration: 2500 });
}
};
// @license-end
</script>
#}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function sendInvitations(exhibition_id) {
const data = new FormData();
data.set('exhibition_id', exhibition_id);
@ -296,6 +254,7 @@
select.checked = false
}
}
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}
@ -304,6 +263,7 @@
{{ header.style() }}
{{ profile_nav.style() }}
{{ competition.style() }}
{{ contacts.style() }}
<style>
.menua{width: 100%;
display: flex;

View File

@ -41,14 +41,14 @@
<h3 class="c_hysjg b_8f3uh" id="preview_title">{{i.title}}</h3>
</header>
<p class="c_2fr39 b_tfnz2" id="preview_topic">{{i.topic}}</p>
{% if i.user %}
<div class="c_dbbe4 b_otttb">
{% if user %}
<img crossorigin="anonymous" fetchpriority="high" loading="eager" class="c_pm80w b_a68fq" src="/a/{{i.user._id}}" alt="{{ i.user.username | e }}">
<div class="c_4402p b_ntazj">
<p class="c_kvah4 b_ghosy">{{ i.user.username }}</p>
</div>
{% endif %}
</div>
{% endif %}
<p class="c_2fr39 b_tfnz2">Подробнее</p>
</a>
<div class="b_otttb"><a class="c_2fr39 b_tfnz2" href="/form/exhibition/{{i._id}}">Редактировать</a><span onclick="deleteExhibition(this, '{{i._id}}')" class="c_2fr39 b_tfnz2">Удалить</span></div>
@ -61,6 +61,7 @@
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function deleteExhibition(btn, _id) {
const article = btn.closest('article');
try {
@ -75,6 +76,7 @@ async function deleteExhibition(btn, _id) {
console.error('Delete failed:', err);
}
}
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -130,6 +130,7 @@
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function postSetting(id, key, value) {
const payload = {
id: id,
@ -169,6 +170,7 @@ document.addEventListener('change', function (e) {
postSetting(id, key, checked);
}
});
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -137,9 +137,12 @@
</div>
</form>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener("DOMContentLoaded",()=>{let e=document.querySelector(".desc"),t=document.getElementById("preview"),l=document.getElementById("inpA"),d=document.getElementById("labelA"),a=document.getElementById("inpB"),r=document.getElementById("role"),s=document.getElementById("gradeC"),n=document.getElementById("gradeR"),i=e=>e.style.display="flex",o=e=>e.style.display="none";document.getElementById("wrap").addEventListener("click",y=>{document.querySelectorAll("#wrap > div").forEach(e=>e.classList.remove("selected"));let c=y.target.closest("#wrap > div");if(!c)return;c.classList.add("selected");let g=c.dataset.text||"";e.textContent=g;let m=c.dataset.img||"";m&&!m.startsWith("blob:")?t.src=m:m||t.removeAttribute("src");let v=c.id;if(o(l),o(a),i(s),o(n),"2"===v||"3"===v){i(l);let E=c.dataset.label;void 0!==E?d.textContent=E:d.textContent=""}"2"===v&&(r.value="user"),"2"===v&&(r.value="reporter",i(a),o(s),i(n)),"3"===v&&(r.value="galerist",o(s))})});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function showOfferModal(offerHtml = '<p>Текст оферты...</p>') {
return new Promise((resolve) => {
const backdrop = document.createElement('div');
@ -209,14 +212,20 @@ document.querySelectorAll('#openOffer').forEach(el => {
});
});
});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("phone"),t=e=>(e||"").replace(/\D+/g,"");e.addEventListener("input",()=>{let a=t(e.value);a.length>10&&(a=a.slice(-10)),e.value=a}),e.addEventListener("paste",a=>{a.preventDefault();let n=(a.clipboardData||window.clipboardData).getData("text"),l=t(n);l.length>10&&(l=l.slice(-10)),e.value=l,e.dispatchEvent(new Event("input",{bubbles:!0}))})});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("day"),t=document.getElementById("month"),l=document.getElementById("year"),n=document.getElementById("birthday"),a=new Date().getFullYear(),u=["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"];function i(t=31){e.innerHTML="";for(let l=1;l<=t;l++){let n=document.createElement("option");n.value=String(l),n.text=String(l),e.appendChild(n)}}function o(){t.innerHTML="",u.forEach((e,l)=>{let n=document.createElement("option");n.value=String(l+1),n.text=e,t.appendChild(n)})}function v(e=1900,t=a){l.innerHTML="";for(let n=t;n>=e;n--){let u=document.createElement("option");u.value=String(n),u.text=String(n),l.appendChild(u)}}let d=e=>String(e).padStart(2,"0");function p(e){if(!e)return;let n=e.split("-");if(3!==n.length)return;let[a,u,i]=n.map(Number);g(l,String(a)),g(t,String(u)),s(a,u,i)}function r(){let a=Number(l.value)||1900,u=Number(t.value)||1,o=Number(e.value)||1,v=new Date(a,u,0).getDate();e.options.length!==v&&i(v),o>v&&(o=v);let p=`${a}-${d(u)}-${d(o)}`;n.value=p,g(e,String(o))}function s(t,l,n){let a=new Date(Number(t),Number(l),0).getDate(),u=Number(e.value)||1;i(a),g(e,String(Math.min(n||u,a)))}function g(e,t){let l=Array.from(e.options).find(e=>e.value===t||e.text===t);if(l)e.value=l.value;else{let n=document.createElement("option");n.text=t,n.value=t,e.appendChild(n),e.value=t}}o(),v();let h=n.value?Number(n.value.split("-")[0]):l.options.length?Number(l.options[0].value):a,c=n.value?Number(n.value.split("-")[1]):1;n.value&&n.value.split("-")[2];let f=new Date(h,c,0).getDate();i(f),n.value?p(n.value):(!l.value&&l.options.length&&(l.value=l.options[0].value),!t.value&&t.options.length&&(t.value=t.options[0].value),!e.value&&e.options.length&&(e.value=e.options[0].value),r()),n.addEventListener("change",()=>{p(n.value)}),l.addEventListener("change",()=>{let e=Number(l.value),n=Number(t.value);s(e,n),r()}),t.addEventListener("change",()=>{let e=Number(l.value),n=Number(t.value);s(e,n),r()}),e.addEventListener("change",r)});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
// Основная логика
document.addEventListener('DOMContentLoaded', () => {
const emailInput = document.getElementById('email');
@ -375,9 +384,12 @@ document.addEventListener('click', (e) => {
});
// @license-end
</script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("avatar"),t=document.getElementById("preview");e.addEventListener("change",async()=>{if(0===e.files.length)return;let n=e.files[0];t.src=URL.createObjectURL(n);let a=new FormData;a.append("avatar",n)})});
// @license-end
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -26,14 +26,16 @@
</section>
<script>
window.addEventListener('load', () => {
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
window.addEventListener('load', () => {
const savedEmail = localStorage.getItem('restoreEmail');
if (savedEmail) {
document.getElementById('email').value = savedEmail;
}
});
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('DOMContentLoaded', () => {
const emailInput = document.getElementById('email');
const restoreBtn = document.getElementById('restoreBtn');
const nextUrl = "{{next or '/profile'}}";
@ -70,7 +72,7 @@ document.addEventListener('DOMContentLoaded', () => {
if (hasError && firstErrorField) {
firstErrorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
showToast({message: 'Введите Email', status: 'error'})
showToast({ message: 'Введите Email', status: 'error' })
setTimeout(() => firstErrorField.focus(), 600);
return;
}
@ -102,20 +104,20 @@ document.addEventListener('DOMContentLoaded', () => {
// Optional: you can show server message if present
const errorText = await res.json();
// console.error('Server error:', errorText);
showToast({message: errorText.detail, status: 'error'})
showToast({ message: errorText.detail, status: 'error' })
}
} catch (err) {
// Network or other unexpected error — mark fields as error
const allFields = document.querySelectorAll('.floating-group');
allFields.forEach(g => g.classList.add('error'));
console.error('Request failed', err);
showToast({message: err, status: 'error'})
showToast({ message: err, status: 'error' })
}
});
// Убираем красную подсветку сразу при исправлении пользователем
document.querySelectorAll('.floating-group input').forEach(input => {
input.addEventListener('input', function() {
input.addEventListener('input', function () {
const group = this.closest('.floating-group');
if (group) {
group.classList.remove('error');
@ -124,7 +126,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Для файлов
if (input.type === 'file') {
input.addEventListener('change', function() {
input.addEventListener('change', function () {
const group = this.closest('.floating-group');
if (group && this.files.length > 0) {
group.classList.remove('error');
@ -133,10 +135,12 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
});
});
// @license-end
</script>
{{ footer.section() }}
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
fetch('/api/me')
.then(response => {
if (response.ok) {
@ -153,6 +157,7 @@ fetch('/api/me')
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
// @license-end
</script>
{% endblock %}

View File

@ -50,7 +50,8 @@
</div>
</section>
<script>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
// Password toggle functionality (same as login)
document.querySelectorAll('.password-toggle').forEach(toggle => {
const input = toggle.parentElement.querySelector('input');
@ -76,19 +77,19 @@
const confirmPass = document.getElementById('confirm_password').value;
if (newPass.length < 6) {
showToast({message: "Пароль должен быть не менее 6 символов", status: 'error'});
showToast({ message: "Пароль должен быть не менее 6 символов", status: 'error' });
return;
}
if (newPass !== confirmPass) {
showToast({message: "Пароли не совпадают", status: 'error'});
showToast({ message: "Пароли не совпадают", status: 'error' });
return;
}
try {
const res = await fetch('/api/reset-password', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: "{{ token }}",
new_password: newPass
@ -98,18 +99,19 @@
const data = await res.json();
if (res.ok) {
showToast({message: "Пароль успешно изменён!", status: 'success'});
showToast({ message: "Пароль успешно изменён!", status: 'success' });
setTimeout(() => {
window.location.href = `/login?next=${data.next}`;
}, 1500);
} else {
showToast({message: data.detail || "Ошибка при смене пароля", status: 'error'});
showToast({ message: data.detail || "Ошибка при смене пароля", status: 'error' });
}
} catch (err) {
showToast({message: "Ошибка соединения с сервером", status: 'error'});
showToast({ message: "Ошибка соединения с сервером", status: 'error' });
}
});
</script>
// @license-end
</script>
{{ footer.section() }}
{% endblock %}

View File

@ -65,6 +65,7 @@
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
(function(){
const input = document.getElementById('memberSearch');
const container = document.querySelectorAll('.c_lt3gk')[1];
@ -92,6 +93,7 @@
filter();
})();
// @license-end
</script>
{{ footer.section() }}
{% endblock %}

View File

@ -27,6 +27,7 @@
</div>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const userId = '{{find._id}}';
document.getElementById('switch-user').addEventListener('click', async () => {
@ -47,6 +48,7 @@ document.getElementById('switch-user').addEventListener('click', async () => {
showToast({message: 'Не удалось переключиться на пользователя.', status: 'error'});
}
});
// @license-end
</script>
{% endif %}
{% if 'admin' in user.get('roles', []) %}
@ -76,6 +78,7 @@ document.getElementById('switch-user').addEventListener('click', async () => {
</div>
</section>
<script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => {
const avatarInput = document.getElementById('avatar');
@ -90,6 +93,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
});
// @license-end
</script>
{{ footer.section() }}
{% endblock %}

183
vk.ipynb
View File

@ -53,7 +53,8 @@
" load_env('env_example')\n",
" except Exception:\n",
" pass"
]
],
"display_name": ".venv"
},
{
"cell_type": "markdown",
@ -85,7 +86,8 @@
"from fastapi import APIRouter, Depends, Request, Response\n",
"from fastapi.responses import RedirectResponse\n",
"from images import insert_logo\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "markdown",
@ -103,7 +105,8 @@
"outputs": [],
"source": [
"celery_app_vk = Celery('vk', broker=f'{redis_uri}1', backend=f'{redis_uri}1', task_track_started=True )\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -113,7 +116,8 @@
"outputs": [],
"source": [
"token_manager = TokenManager()\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -154,7 +158,8 @@
"\n",
" # Привязка соцсети к уже залогиненному пользователю\n",
" return RedirectResponse(url='/profile/social', status_code=status.HTTP_303_SEE_OTHER)"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -184,7 +189,8 @@
" images[_id] = (\"photo\", (name, background, \"image/jpeg\"))\n",
" \n",
" return images\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -193,6 +199,7 @@
"metadata": {},
"outputs": [],
"source": [
"# TODO: function call like send_tg from tg.py (without router)\n",
"@router.post(\"/send\")\n",
"async def send_vk(data:dict, request: Request):\n",
" auth = request.headers.get(\"Authorization\")\n",
@ -208,9 +215,9 @@
" \n",
" sign_avatar = data['sign_avatar']\n",
" db_name = item.get('db')\n",
" # if db_name == 'competitions':\n",
" # social = await social_collection.find_one({'service': True, 'type':'vk'})\n",
" # else:\n",
" if db_name in ['competitions', 'exhibition']:\n",
" social = await social_collection.find_one({'service': True, 'type':'vk'})\n",
" else:\n",
" social = doc\n",
" logging.info(doc)\n",
" logging.info(social)\n",
@ -218,10 +225,9 @@
" async with httpx.AsyncClient(http2=True, verify=ctx) as client:\n",
" attachments = []\n",
" for _id, img in images.items():\n",
" # doc_photo = await mongo_client['vk']['photos'].find_one({'id':f\"{_id}:{social.get('_id')}\"})\n",
" # if doc_photo:\n",
" # attachments.append(doc_photo['attachment'])\n",
" # continue\n",
" if (doc_photo:=await mongo_client['vk']['photos'].find_one({'id':f\"{_id}:{social.get('_id')}\"},{'attachment': 1})):\n",
" attachments.append(doc_photo['attachment'])\n",
" continue\n",
" payload = {\"v\": \"5.199\"}\n",
" if 'group' in social:\n",
" payload['group_id'] = social['id']\n",
@ -237,7 +243,7 @@
" attachment = ','.join([f'photo{i['owner_id']}_{i['id']}' for i in resp.json()['response']])\n",
" if not attachment:\n",
" return {'ok': False}\n",
" # await mongo_client['vk']['photos'].insert_one({'attachment':attachment,'id':f\"{_id}:{social.get('_id')}\"})\n",
" await mongo_client['vk']['photos'].insert_one({'attachment':attachment,'id':f\"{_id}:{social.get('_id')}\"})\n",
" attachments.append(attachment)\n",
" logging.info(attachments)\n",
" \n",
@ -257,7 +263,8 @@
" await mongo_client[db_name][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)\n",
" return {\"ok\": True}\n",
"\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -266,6 +273,7 @@
"metadata": {},
"outputs": [],
"source": [
"# TODO: function call like send_tg from tg.py (without router)\n",
"@router.post(\"/edit\")\n",
"async def edit_vk(data:dict, request: Request):\n",
" auth = request.headers.get(\"Authorization\")\n",
@ -333,7 +341,8 @@
" await mongo_client[db_name][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)\n",
" return {\"ok\": True}\n",
"\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -342,6 +351,7 @@
"metadata": {},
"outputs": [],
"source": [
"# TODO: function call like send_tg from tg.py (without router)\n",
"@router.post(\"/delete\")\n",
"async def wall_delete(data: dict, request: Request):\n",
" # ---- auth ----\n",
@ -386,7 +396,8 @@
" )\n",
"\n",
" return {\"ok\": True}\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -410,7 +421,7 @@
" try:\n",
" logging.info(f\"VK начало edit {item} {file_ids}\")\n",
" res = httpx.post(\n",
" 'http://127.0.0.1:30080/api/v1/social/vk/edit', \n",
" 'http://127.0.0.1:30081/api/v1/social/vk/edit', \n",
" headers={\"Authorization\": f\"bearer {ATOKEN}\"}, \n",
" json={'post_id': post_id, 'item': item, 'text': text, 'file_ids': file_ids, 'doc': social, 'sign_avatar': sign_avatar},\n",
" timeout=100\n",
@ -421,7 +432,8 @@
" \n",
" except Exception as exc:\n",
" raise self.retry(exc=exc)\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -445,7 +457,7 @@
" try:\n",
" logging.info(f\"VK удаления публикации {doc} {post_id}\")\n",
" res = httpx.post(\n",
" 'http://127.0.0.1:30080/api/v1/social/vk/delete',\n",
" 'http://127.0.0.1:30081/api/v1/social/vk/delete',\n",
" headers={\"Authorization\": f\"bearer {ATOKEN}\"},\n",
" json={'item': item, 'post_id': post_id, \"doc\": doc},\n",
" timeout=100\n",
@ -456,7 +468,8 @@
" \n",
" except Exception as exc:\n",
" raise self.retry(exc=exc)\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -480,7 +493,7 @@
" try:\n",
" logging.info(f\"VK начало публикации {item} {file_ids}\")\n",
" res = httpx.post(\n",
" 'http://127.0.0.1:30080/api/v1/social/vk/send',\n",
" 'http://127.0.0.1:30081/api/v1/social/vk/send',\n",
" headers={\"Authorization\": f\"bearer {ATOKEN}\"},\n",
" json={'item': item, 'text': text, 'file_ids': file_ids, 'doc': social, 'sign_avatar': sign_avatar},\n",
" timeout=100\n",
@ -491,7 +504,8 @@
" \n",
" except Exception as exc:\n",
" raise self.retry(exc=exc)\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "markdown",
@ -522,7 +536,8 @@
"# timeout=100\n",
"# ).json()\n",
"# await asyncio.sleep(1)\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -553,7 +568,8 @@
"\n",
" with open(f'{filename_without_extension}.py', 'w') as f:\n",
" f.write('\\n\\n'.join(app))\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -567,7 +583,8 @@
"outputs": [],
"source": [
"!celery -A vk.celery_app_vk worker --loglevel=info"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -581,7 +598,8 @@
"outputs": [],
"source": [
"!docker build -t registry.spiritofweb.ru/avtopost-api:latest ."
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -595,7 +613,8 @@
"outputs": [],
"source": [
"!sudo docker image push registry.spiritofweb.ru/avtopost-api:latest"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -609,7 +628,8 @@
"outputs": [],
"source": [
"!kubectl rollout restart deployments/vk-celery -n kultmost"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -623,7 +643,8 @@
"outputs": [],
"source": [
"!echo $REGISTRY_HOST"
]
],
"display_name": ".venv"
},
{
"cell_type": "markdown",
@ -641,7 +662,8 @@
"outputs": [],
"source": [
"stop"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -651,7 +673,8 @@
"outputs": [],
"source": [
"user_id = '681684390000000000000000'"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -662,7 +685,8 @@
"source": [
"await redis_client.delete(f\"access_token:{user_id}\")\n",
"await redis_client.delete(f\"tokens:{user_id}\")"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -686,7 +710,8 @@
" },\n",
" upsert=True,\n",
")\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -696,7 +721,8 @@
"outputs": [],
"source": [
"import token_manager as tm"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -707,7 +733,8 @@
"source": [
"from token_manager import TokenManager\n",
"token_manager = TokenManager()"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -719,7 +746,8 @@
"token_manager = TokenManager()\n",
"access_token = await token_manager.get_access_token('644fa8bb0000000000000000')\n",
"access_token"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -730,7 +758,8 @@
"source": [
"item ={'_id': '6a21559cca3ad8a73b652bcb', 'db': 'competitions', 'col': 'competitions'} \n",
"file_ids = ['681684390000000000000000', '6a21559cca3ad8a73b652bc6']"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -741,7 +770,8 @@
"source": [
"docs =[ doc async for doc in mongo_client['social']['social'].find({'user_id':'681684390000000000000000'})]\n",
"doc = docs[1]"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -751,7 +781,8 @@
"outputs": [],
"source": [
"competition = await competitions_col.find_one({'_id':ObjectId('6a21559cca3ad8a73b652bcb')})"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -761,7 +792,8 @@
"outputs": [],
"source": [
"images = await get_images(db_name, file_ids, sign_avatar)"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -803,7 +835,8 @@
" })\n",
" res = resp.json()\n",
" res.update({'doc': doc})"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -813,7 +846,8 @@
"outputs": [],
"source": [
"res"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -880,7 +914,8 @@
" update_spec = {\"$push\": {\"vk\": res}}\n",
" await mongo_client[db_name][item.get('col')].update_one({'_id': ObjectId(item.get('_id'))}, update_spec)\n",
" return {\"ok\": True}\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -919,7 +954,8 @@
" })\n",
" print(resp_wall.json())\n",
" \n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -929,7 +965,8 @@
"outputs": [],
"source": [
"await competitions_col.find_one({'_id': competition['_id']})"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -944,7 +981,8 @@
" \"$push\": {\"vk\": resp_wall.json()}\n",
" },\n",
" )"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -959,7 +997,8 @@
" \"v\": \"5.199\",\n",
" })\n",
"resp.json()['response'][0]"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -969,7 +1008,8 @@
"outputs": [],
"source": [
"[doc async for doc in mongo_client['social']['social'].find({'user_id':competition['user']['id']})]"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -987,7 +1027,8 @@
"})\n",
"\n",
"z.json()['response']"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -997,7 +1038,8 @@
"outputs": [],
"source": [
"#!pip freeze > requirements.txt"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1017,7 +1059,8 @@
" \"body\": \"group_id=191599084&user_id=392892935&role=moderator&is_contact=0&contact_position=&contact_phone=&contact_email=&permissions=&access_token=vk1.a.-kG07OUZgX5FrxtsQrU5QJh8HaP_suYXkqcg\",\n",
" \"method\": \"POST\",\n",
"});"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1035,7 +1078,8 @@
"VK_CLIENT_ID = 51852444\n",
"SCOPES = 'offline,wall,photos,groups'\n",
"redirect_uri = 'https://oauth.vk.ru/blank.html'"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1066,7 +1110,8 @@
"url = f'{AUTHORIZE_URL}?responsetype_item=code&client_id={VK_CLIENT_ID}&scope={SCOPES}&redirect_uri={redirect_uri}&state={state}&code_challenge={code_challenge}&code_challenge_method=S256'\n",
"\n",
"print(f\"\\n\\n{url}\\n\\n\")"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1078,7 +1123,8 @@
"\n",
"code='vk2.a.---'\n",
"device_id=''"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1089,7 +1135,8 @@
"source": [
"refresh_token = \"vk2.a.-----\"\n",
"device_id = \"-\"\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1108,7 +1155,8 @@
" 'state': os.urandom(32).hex(),\n",
"})\n",
"z.json()"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1119,7 +1167,8 @@
"source": [
"CLIENT_ID = 51852444\n",
"OAUTH_URL = 'https://id.vk.com/oauth2/auth'"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1140,7 +1189,8 @@
" 'redirect_uri': 'https://oauth.vk.com/blank.html'\n",
"})\n",
"r.json()\n"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1161,7 +1211,8 @@
"\n",
"z=T()\n",
"z.json()"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1172,7 +1223,8 @@
"source": [
"refresh_token=r.json()['refresh_token']\n",
"refresh_token"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1191,7 +1243,8 @@
" 'state': os.urandom(32).hex(),\n",
"})\n",
"z.json()"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1201,7 +1254,8 @@
"outputs": [],
"source": [
"print(f'\\n\\n{z.json()['refresh_token']}\\n\\n')"
]
],
"display_name": ".venv"
},
{
"cell_type": "code",
@ -1211,7 +1265,8 @@
"outputs": [],
"source": [
"users.search"
]
],
"display_name": ".venv"
},
{
"cell_type": "markdown",

26
vk.py
View File

@ -123,6 +123,7 @@ async def get_images(db_name: str, file_ids: list, sign_avatar: bool = True):
return images
# TODO: function call like send_tg from tg.py (without router)
@router.post("/send")
async def send_vk(data:dict, request: Request):
auth = request.headers.get("Authorization")
@ -138,9 +139,9 @@ async def send_vk(data:dict, request: Request):
sign_avatar = data['sign_avatar']
db_name = item.get('db')
# if db_name == 'competitions':
# social = await social_collection.find_one({'service': True, 'type':'vk'})
# else:
if db_name in ['competitions', 'exhibition']:
social = await social_collection.find_one({'service': True, 'type':'vk'})
else:
social = doc
logging.info(doc)
logging.info(social)
@ -148,10 +149,9 @@ async def send_vk(data:dict, request: Request):
async with httpx.AsyncClient(http2=True, verify=ctx) as client:
attachments = []
for _id, img in images.items():
# doc_photo = await mongo_client['vk']['photos'].find_one({'id':f"{_id}:{social.get('_id')}"})
# if doc_photo:
# attachments.append(doc_photo['attachment'])
# continue
if (doc_photo:=await mongo_client['vk']['photos'].find_one({'id':f"{_id}:{social.get('_id')}"},{'attachment': 1})):
attachments.append(doc_photo['attachment'])
continue
payload = {"v": "5.199"}
if 'group' in social:
payload['group_id'] = social['id']
@ -167,7 +167,7 @@ async def send_vk(data:dict, request: Request):
attachment = ','.join([f'photo{i['owner_id']}_{i['id']}' for i in resp.json()['response']])
if not attachment:
return {'ok': False}
# await mongo_client['vk']['photos'].insert_one({'attachment':attachment,'id':f"{_id}:{social.get('_id')}"})
await mongo_client['vk']['photos'].insert_one({'attachment':attachment,'id':f"{_id}:{social.get('_id')}"})
attachments.append(attachment)
logging.info(attachments)
@ -188,7 +188,7 @@ async def send_vk(data:dict, request: Request):
return {"ok": True}
# TODO: function call like send_tg from tg.py (without router)
@router.post("/edit")
async def edit_vk(data:dict, request: Request):
auth = request.headers.get("Authorization")
@ -257,7 +257,7 @@ async def edit_vk(data:dict, request: Request):
return {"ok": True}
# TODO: function call like send_tg from tg.py (without router)
@router.post("/delete")
async def wall_delete(data: dict, request: Request):
# ---- auth ----
@ -319,7 +319,7 @@ def edit(self, post_id: int, item: dict, text: str, file_ids: list, social: dict
try:
logging.info(f"VK начало edit {item} {file_ids}")
res = httpx.post(
'http://127.0.0.1:30080/api/v1/social/vk/edit',
'http://127.0.0.1:30081/api/v1/social/vk/edit',
headers={"Authorization": f"bearer {ATOKEN}"},
json={'post_id': post_id, 'item': item, 'text': text, 'file_ids': file_ids, 'doc': social, 'sign_avatar': sign_avatar},
timeout=100
@ -347,7 +347,7 @@ def delete_wall(self, post_id: int, doc: dict, item: dict|None = None):
try:
logging.info(f"VK удаления публикации {doc} {post_id}")
res = httpx.post(
'http://127.0.0.1:30080/api/v1/social/vk/delete',
'http://127.0.0.1:30081/api/v1/social/vk/delete',
headers={"Authorization": f"bearer {ATOKEN}"},
json={'item': item, 'post_id': post_id, "doc": doc},
timeout=100
@ -375,7 +375,7 @@ def notify(self, item: dict, text: str, file_ids: list, social: dict, sign_avata
try:
logging.info(f"VK начало публикации {item} {file_ids}")
res = httpx.post(
'http://127.0.0.1:30080/api/v1/social/vk/send',
'http://127.0.0.1:30081/api/v1/social/vk/send',
headers={"Authorization": f"bearer {ATOKEN}"},
json={'item': item, 'text': text, 'file_ids': file_ids, 'doc': social, 'sign_avatar': sign_avatar},
timeout=100