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

View File

@ -80,7 +80,6 @@
"import uuid\n", "import uuid\n",
"import bcrypt\n", "import bcrypt\n",
"from typing import List, Any, Dict, Optional, Union\n", "from typing import List, Any, Dict, Optional, Union\n",
"from datetime import timedelta\n",
"from urllib.parse import urlencode\n", "from urllib.parse import urlencode\n",
"from bson import ObjectId\n", "from bson import ObjectId\n",
"from PIL import Image\n", "from PIL import Image\n",
@ -473,6 +472,24 @@
], ],
"display_name": ".venv" "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", "cell_type": "code",
"execution_count": null, "execution_count": null,

11
main.py
View File

@ -19,7 +19,6 @@ from starlette.middleware.gzip import GZipMiddleware
import uuid import uuid
import bcrypt import bcrypt
from typing import List, Any, Dict, Optional, Union from typing import List, Any, Dict, Optional, Union
from datetime import timedelta
from urllib.parse import urlencode from urllib.parse import urlencode
from bson import ObjectId from bson import ObjectId
from PIL import Image 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} 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") @app.get("/cron/day")
async def cron_every_day(token: str = Security(get_token)): async def cron_every_day(token: str = Security(get_token)):
res = await competitions_col.update_many( 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 }); 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 MIN_VALID_SIZE = 500;
const ASSETS_TO_CACHE = [ const ASSETS_TO_CACHE = [
@ -19,6 +19,8 @@ const ASSETS_TO_CACHE = [
'/static/event3.jpg', '/static/event3.jpg',
'/static/event4.jpg', '/static/event4.jpg',
'/static/event5.jpg', '/static/event5.jpg',
'/static/script/share.js',
'/static/script/select.js',
'/static/event6.jpg', '/static/event6.jpg',
'/static/event7.webp', '/static/event7.webp',
'/partner', '/partner',

View File

@ -97,8 +97,10 @@ picture{cursor: zoom-in;}
</head> </head>
<body {% if request.cookies.zoom%}style="zoom: {{ request.cookies.zoom }}%"{%endif%} onload="adjustBodyStyle()"> <body {% if request.cookies.zoom%}style="zoom: {{ request.cookies.zoom }}%"{%endif%} onload="adjustBodyStyle()">
<script> <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`;} 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 }); 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> </script>
{% block content %}{% endblock %} {% block content %}{% endblock %}
<div id="modal" class="modal" style="display: none;cursor: zoom-out;"> <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> </a>
</div> </div>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('picture').forEach(picture => { document.querySelectorAll('picture').forEach(picture => {
picture.addEventListener('click', function() { 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';} function closeModalPic() {const modalImg = document.getElementById('modalImg');modalImg.src = '';document.getElementById('modal').style.display = 'none';}
// @license-end
</script> </script>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
{% raw %} {% 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}} 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 %} {% endraw %}
{% if notify %} {% if notify %}
showToast({message: '{{notify.text}}', status: '{{notify.status}}', duration:{{notify.duration}}}); showToast({message: '{{notify.text}}', status: '{{notify.status}}', duration:{{notify.duration}}});
{% endif %} {% endif %}
// @license-end
</script> </script>
<div id="shareModal" <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;"> 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>
</div> </div>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function imageWithTextBlob(imageUrl, text, options = {}) { async function imageWithTextBlob(imageUrl, text, options = {}) {
const response = await fetch(imageUrl, { const response = await fetch(imageUrl, {
mode: 'cors', mode: 'cors',
credentials: 'same-origin', credentials: 'same-origin',
}); });
if (!response.ok) throw new Error(`HTTP ${response.status}`); if (!response.ok) throw new Error(`HTTP ${response.status}`);
const imageBlob = await response.blob(); const imageBlob = await response.blob();
const imageBitmap = await createImageBitmap(imageBlob); const imageBitmap = await createImageBitmap(imageBlob);
@ -204,7 +209,7 @@ async function imageWithTextBlob(imageUrl, text, options = {}) {
// Draw image // Draw image
ctx.drawImage(imageBitmap, 0, 0, width, height); 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 (!text?.trim()) {
if (canvas.convertToBlob) { if (canvas.convertToBlob) {
return canvas.convertToBlob({ return canvas.convertToBlob({
@ -218,7 +223,7 @@ async function imageWithTextBlob(imageUrl, text, options = {}) {
} }
// === Text rendering === // === Text rendering ===
const basePadding = 6; const basePadding = options.basePadding ?? 8; // internal left/right/top padding
const fontSize = options.fontSize ?? Math.round(width * 0.05); const fontSize = options.fontSize ?? Math.round(width * 0.05);
ctx.font = `${fontSize}px ${options.fontFamily ?? 'sans-serif'}`; ctx.font = `${fontSize}px ${options.fontFamily ?? 'sans-serif'}`;
ctx.textBaseline = 'bottom'; ctx.textBaseline = 'bottom';
@ -246,11 +251,17 @@ async function imageWithTextBlob(imageUrl, text, options = {}) {
if (line) lines.push(line); if (line) lines.push(line);
const lineHeight = fontSize + (options.lineGap ?? 6); 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 maxLineWidth = Math.max(...lines.map((l) => ctx.measureText(l).width));
const boxWidth = maxLineWidth + basePadding * 2; 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 boxX = (width - boxWidth) / 2;
const boxY = height - bottomPadding - boxHeight; const boxY = height - bottomPadding - boxHeight;
const radius = options.radius ?? 8; const radius = options.radius ?? 8;
@ -266,6 +277,7 @@ async function imageWithTextBlob(imageUrl, text, options = {}) {
// Text // Text
ctx.fillStyle = textColor; ctx.fillStyle = textColor;
ctx.shadowColor = 'transparent'; ctx.shadowColor = 'transparent';
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const lineY = boxY + basePadding + (i + 1) * lineHeight - (lineHeight - fontSize) / 2; const lineY = boxY + basePadding + (i + 1) * lineHeight - (lineHeight - fontSize) / 2;
const lineX = width / 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) canvas.toBlob((b) => resolve(b), options.type ?? 'image/png', options.quality ?? 0.92)
); );
} }
function roundRect(ctx, x, y, w, h, r, fill, stroke) { function roundRect(ctx, x, y, w, h, r, fill, stroke) {
if (typeof r === 'number') r = { tl: r, tr: r, br: r, bl: r }; if (typeof r === 'number') r = { tl: r, tr: r, br: r, bl: r };
ctx.beginPath(); ctx.beginPath();
@ -306,8 +317,6 @@ function roundRect(ctx, x, y, w, h, r, fill, stroke) {
if (fill) ctx.fill(); if (fill) ctx.fill();
if (stroke) ctx.stroke(); if (stroke) ctx.stroke();
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const shareModal = document.getElementById('shareModal'); const shareModal = document.getElementById('shareModal');
const closeshareModal = document.getElementById('closeModal'); const closeshareModal = document.getElementById('closeModal');
@ -340,11 +349,9 @@ shareBtns.forEach(btn => {
const text = btn.dataset.text || document.title; const text = btn.dataset.text || document.title;
const image = btn.dataset.image; const image = btn.dataset.image;
const imagetext = btn.dataset.imagetext || ''; const imagetext = btn.dataset.imagetext || '';
let blob = null; let blob = null;
let file = null; let file = null;
let objectUrl = null; let objectUrl = null;
try { try {
if (image) { if (image) {
const imageUrl = window.location.origin + 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}`); console.log(`Generated blob for "${imagetext}" | size: ${blob.size} | url: ${imageUrl}`);
const filename = `shared-image-${Date.now()}.png`; const filename = `shared-image-${Date.now()}.png`;
file = new File([blob], filename, { type: blob.type }); file = new File([blob], filename, { type: blob.type });
// Создаём object URL только если хотим (необязательно для navigator.share) // Создаём object URL только если хотим (необязательно для navigator.share)
objectUrl = URL.createObjectURL(file); objectUrl = URL.createObjectURL(file);
if (navigator.canShare && navigator.canShare({ files: [file] })) { if (navigator.canShare && navigator.canShare({ files: [file] })) {
await navigator.share({ await navigator.share({
title: text, title: text,
@ -386,15 +391,12 @@ shareBtns.forEach(btn => {
objectUrl = null; objectUrl = null;
} }
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
// Удаляем ссылки, чтобы GC мог собрать объекты // Удаляем ссылки, чтобы GC мог собрать объекты
blob = null; blob = null;
file = null; file = null;
} }
}); });
}); });
function openshareModal(link, text) { function openshareModal(link, text) {
vk.href = buildShareUrl('vk', link, text); vk.href = buildShareUrl('vk', link, text);
tg.href = buildShareUrl('tg', link, text); tg.href = buildShareUrl('tg', link, text);
@ -406,15 +408,12 @@ shareBtns.forEach(btn => {
copyBtn.dataset.link = link; copyBtn.dataset.link = link;
shareModal.style.display = 'flex' shareModal.style.display = 'flex'
} }
closeshareModal.addEventListener('click', () => { closeshareModal.addEventListener('click', () => {
shareModal.style.display = 'none'; shareModal.style.display = 'none';
}); });
shareModal.addEventListener('click', (e) => { shareModal.addEventListener('click', (e) => {
if (e.target === shareModal) shareModal.style.display = 'none'; if (e.target === shareModal) shareModal.style.display = 'none';
}); });
async function copyToClipboard(text) { async function copyToClipboard(text) {
try { try {
if (navigator.clipboard && navigator.clipboard.writeText) { if (navigator.clipboard && navigator.clipboard.writeText) {
@ -434,151 +433,20 @@ shareBtns.forEach(btn => {
showToast({message: 'Не удалось скопировать ссылку', status: 'error'}); showToast({message: 'Не удалось скопировать ссылку', status: 'error'});
} }
} }
function showCopyMessage() { function showCopyMessage() {
copyMsg.style.display = 'inline'; copyMsg.style.display = 'inline';
setTimeout(() => copyMsg.style.display = 'none', 2000); setTimeout(() => copyMsg.style.display = 'none', 2000);
} }
copyBtn.addEventListener('click', () => { copyBtn.addEventListener('click', () => {
const link = copyBtn.dataset.link || window.location.href; const link = copyBtn.dataset.link || window.location.href;
copyToClipboard(link); copyToClipboard(link);
}); });
}); });
// @license-end
</script> </script>
<script> <script>
(async () => { // @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const parts_cookie = `; ${document.cookie}`.split(`; token=`); (function() {
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() {
'use strict'; 'use strict';
const root = document.getElementById('dd-modal-root') || document.body; const root = document.getElementById('dd-modal-root') || document.body;
@ -709,21 +577,11 @@ shareBtns.forEach(btn => {
img.alt = 'Подсказка'; img.alt = 'Подсказка';
div.appendChild(img); div.appendChild(img);
} }
// куда-то добавляем div
span.appendChild(div); span.appendChild(div);
wrapper.append(input, span); wrapper.append(input, span);
return wrapper; return wrapper;
} }
// Кастомные значения
if (allowCustom) { if (allowCustom) {
customContainer.innerHTML = ` customContainer.innerHTML = `
<div style="display:flex;gap:8px;align-items:center;flex-direction:column;"> <div style="display:flex;gap:8px;align-items:center;flex-direction:column;">
@ -858,6 +716,98 @@ shareBtns.forEach(btn => {
}, true); }, 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> </script>
<div id="dd-modal-root"></div> <div id="dd-modal-root"></div>
@ -868,6 +818,7 @@ shareBtns.forEach(btn => {
</div> </div>
</div> </div>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const modal = document.getElementById('modalToolTip'); const modal = document.getElementById('modalToolTip');
const modalText = document.getElementById('modalToolTipText'); const modalText = document.getElementById('modalToolTipText');
@ -897,6 +848,7 @@ shareBtns.forEach(btn => {
} }
}); });
}); });
// @license-end
</script> </script>
</body> </body>
</html> </html>

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@
{% import "/components/header.html" as header %} {% import "/components/header.html" as header %}
{% import "/components/footer.html" as footer %} {% import "/components/footer.html" as footer %}
{% import "components/share.html" as share %} {% import "components/share.html" as share %}
{% import "/components/contacts.html" as contacts %}
{% block title %}{{exhibition.title}} | Картины{% endblock %} {% block title %}{{exhibition.title}} | Картины{% endblock %}
@ -42,6 +43,14 @@
</div> </div>
<header class="c_77wsp b_qx462"> <header class="c_77wsp b_qx462">
<h2 class="c_aiv78 b_6jj0l">{{exhibition.title}}</h2> <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') %} {% 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;"> <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> <span class="b_j2ln2">Релиз выставки</span>
@ -118,6 +127,7 @@
</div> </div>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function saledPaintings(postId) { function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, { fetch(`/api/post/${postId}/sale`, {
method: 'PUT' method: 'PUT'
@ -148,8 +158,10 @@
}); });
}); });
} }
// @license-end
</script> </script>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
(function(){ (function(){
const input = document.getElementById('memberSearch'); const input = document.getElementById('memberSearch');
const container = document.querySelector('.c_cnedr'); const container = document.querySelector('.c_cnedr');
@ -175,6 +187,7 @@
filter(); filter();
})(); })();
// @license-end
</script> </script>
@ -195,6 +208,7 @@
<meta name="description" content="{{exhibition.short_desc}}"> <meta name="description" content="{{exhibition.short_desc}}">
<meta property="og:site_name" content="Конкурсы | KULTMOST.АВТОПОСТ"> <meta property="og:site_name" content="Конкурсы | KULTMOST.АВТОПОСТ">
{{ header.style() }} {{ header.style() }}
{{ contacts.style() }}
<style> <style>
.b_0jst1 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; } .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; } .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" %} {% extends "base.html" %}
{% import "/components/header.html" as header %} {% import "/components/header.html" as header %}
{% import "/components/footer.html" as footer %} {% import "/components/footer.html" as footer %}
{% import "/components/contacts.html" as contacts %}
{% import "components/share.html" as share %} {% import "components/share.html" as share %}
{% block title %}Выставки{% endblock %} {% block title %}Выставки{% endblock %}
{% block content %} {% block content %}
{{ header.section(user) }} {{ header.section(user) }}
<section class="c_cr9xg"> <section class="c_cr9xg">
@ -16,71 +16,70 @@
</div> </div>
<div class="c_dbu4q"> <div class="c_dbu4q">
{% for exhibition in exhibitions %} {% for exhibition in exhibitions %}
{% set link="/exhibition/{}".format(exhibition._id) %} {% set link="/exhibition/{}".format(exhibition._id) %}
<article class="c_9fgdh b_ax8vz" id="l{{exhibition._id}}"> <article class="c_9fgdh b_ax8vz" id="l{{exhibition._id}}">
{% if link %} {% if link %}<a href="{{link}}">{% endif %}
<a href="{{link}}"> <div class="b_ornr9">
{% endif %} <picture>
<div class="b_ornr9"> <img crossorigin="anonymous" fetchpriority="high" loading="eager" alt="{{ exhibition.title }}"
<picture> class="b_ornr9" src="/b/exhibition/{{ exhibition.poster_file_id }}"
<img crossorigin="anonymous" fetchpriority="high" loading="eager" alt="{{ exhibition.title }}" class="b_ornr9" alt="Выставка {{exhibition.title }}">
src="/b/exhibition/{{ exhibition.poster_file_id }}" alt="Выставка {{exhibition.title }}"> </picture>
</picture> </div>
</div> {% if link %}</a>{% endif %}
{% if link %} <div class="c_z3dpx b_73p10">
</a> {% if link %}<a href="{{link}}" class="b_73p10">{% endif %}
{% endif %} <div class="{% if now > exhibition.date_finish %}b_vabj9_2{%else%}b_vabj9{% endif %}">
<div class="c_z3dpx b_73p10"> {% if now > exhibition.date_finish %}
{% if link %} <span class="c_95csi b_0jst1_2">Закончилась</span>
<a href="{{link}}" class="b_73p10"> {% elif now > exhibition.datetime_start %}
{% endif %} <span class="c_95csi b_0jst1">Началась</span>
<div class="{% if now > exhibition.date_finish %}b_vabj9_2{%else%}b_vabj9{% endif %}"> {% else %}
{% if now > exhibition.date_finish %} <span class="c_95csi b_0jst1">Формируется</span>
<span class="c_95csi b_0jst1_2">Закончилась</span> {% endif %}
{% elif now > exhibition.datetime_start %} </div>
<span class="c_95csi b_0jst1">Началась</span> <header class="c_m876y b_9xtz9">
{% else %} <h3 class="c_hysjg b_8f3uh">{{ exhibition.title }}</h3>
<span class="c_95csi b_0jst1">Формируется</span> </header>
{% endif %} <p class="c_2fr39 b_tfnz2">{{ exhibition.topic }}</p>
</div> {% if link %}</a>{% endif %}
<header class="c_m876y b_9xtz9"> {% set creator = exhibition.get('user') %}
<h3 class="c_hysjg b_8f3uh">{{ exhibition.title }}</h3> {% if creator %}
</header> <div class="c_dbbe4 b_otttb">
<p class="c_2fr39 b_tfnz2">{{ exhibition.topic }}</p> <img class="c_pm80w b_a68fq" src="/a/{{creator._id}}" alt="{{ creator.username | e }}"
{% set user = exhibition.get('user') %} crossorigin="anonymous" fetchpriority="high" loading="eager">
<div class="c_dbbe4 b_otttb"> <div class="c_4402p b_ntazj">
{% if user %} <p class="c_kvah4 b_ghosy">{{ creator.username | e }}</p>
<img class="c_pm80w b_a68fq" src="/a/{{user._id}}" alt="{{ user.username | e }}" crossorigin="anonymous" fetchpriority="high" loading="eager"> </div>
<div class="c_4402p b_ntazj"> </div>
<p class="c_kvah4 b_ghosy">{{ user.username | e }}</p> {{ contacts.details(creator) }}
{% endif %}
{% 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">
<span class="c_som9s">Релиз выставки</span>
</a>
{%endif%}
<a class="c_aq9et" href="{{link}}" rel="noopener noreferrer">
<span class="c_som9s">Список картин</span>
</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>
</div>
<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), "Приглашаем на выставку") }}
</span>
</div>
<p class="c_drpqj b_mp1wb">{{ (exhibition._id.generation_time.strftime('%d.%m.%Y')) if exhibition._id else ''}}</p>
</div> </div>
{% endif %} </article>
</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">
<span class="c_som9s">Релиз выставки</span>
</a>
{%endif%}
<a class="c_aq9et" href="{{link}}" rel="noopener noreferrer">
<span class="c_som9s">Список картин</span>
</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>
</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), "Приглашаем на выставку") }}
</span>
</div>
<p class="c_drpqj b_mp1wb">{{ (exhibition._id.generation_time.strftime('%d.%m.%Y')) if exhibition._id else ''}}</p>
</div>
</article>
{% endfor %} {% endfor %}
</div> </div>
</section> </section>
@ -89,18 +88,10 @@
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
<style>/* Base (common) styles */ {{ contacts.style() }}
.c_aq9et { <style>
cursor: pointer; .c_aq9et {cursor: pointer;transition: transform 0.1s linear, box‑shadow 0.1s linear;box-shadow: 0 0 0 rgba(0, 0, 0, 0);}
transition: transform 0.1s linear, box‑shadow 0.1s linear; .c_aq9et:active {transform: scale(0.9);box-shadow: 5px 5px 5px rgba(95, 23, 152, 0.32);}
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%;} article>a{width: 100%;}
picture>img{ width: 100%; object-fit: scale-down; } picture>img{ width: 100%; object-fit: scale-down; }
.fl{display: flex;justify-content: space-between;width: 100%;} .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;} .b_ornr9{height: auto;}
} }
</style> </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; } .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) { @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_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_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_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; } .c_cr9xg { align-items: center; background: #FFF; display: flex; flex-direction: column; gap: 30px; }
} }
/* Tablet */
@media (min-width: 700px) and (max-width: 900px) { @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_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_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_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; } .c_cr9xg { align-items: center; background: #FFF; display: flex; flex-direction: column; gap: 30px; padding-left: 50px; }
} }
/* Desktop */
@media (min-width: 901px) { @media (min-width: 901px) {
.c_dbu4q { align-content: flex-start; align-items: flex-start; display: flex; flex-wrap: wrap; gap: 20px; width: 100%; } .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; } .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> </form>
{% if user.send_address %} {% if user.send_address %}
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.getElementById('address').value = '{{ user.send_address[-1] }}' document.getElementById('address').value = '{{ user.send_address[-1] }}'
// @license-end
</script> </script>
{% endif %} {% endif %}
{% if user.places %} {% if user.places %}
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.getElementById('place').value = '{{ user.places[-1] }}' document.getElementById('place').value = '{{ user.places[-1] }}'
// @license-end
</script> </script>
{% endif %} {% endif %}
<script defer> <script defer>
@ -363,6 +367,7 @@ function bindLinkValidation({
linkErrorSelector: '#link-error-1' linkErrorSelector: '#link-error-1'
}); });
// @license-end
</script> </script>
<script defer> <script defer>
@ -384,6 +389,7 @@ function bindLinkValidation({
} }
setTomorrowAtNoon(); setTomorrowAtNoon();
// @license-end
</script> </script>
<script defer> <script defer>
@ -551,6 +557,7 @@ function bindLinkValidation({
} }
}); });
// @license-end
</script> </script>

View File

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

View File

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

View File

@ -60,19 +60,19 @@
</div> </div>
</form> </form>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function sendToNextPage(e) { function sendToNextPage(e) {
e.preventDefault(); e.preventDefault();
const email = document.getElementById('email').value.trim(); const email = document.getElementById('email').value.trim();
if (email) { if (email) {
localStorage.setItem('restoreEmail', email); localStorage.setItem('restoreEmail', email);
} }
window.location.href = "/restore{% if next %}?next={{next}}{% endif %}"; window.location.href = "/restore{% if next %}?next={{next}}{% endif %}";
} }
// @license-end
</script> </script>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const emailInput = document.getElementById('email'); const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password'); const passwordInput = document.getElementById('password');
@ -210,9 +210,11 @@ function sendToNextPage(e) {
}); });
} }
}); });
// @license-end
</script> </script>
{{ footer.section() }} {{ footer.section() }}
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
fetch('/api/me') fetch('/api/me')
.then(response => { .then(response => {
if (response.ok) { if (response.ok) {
@ -229,6 +231,7 @@ fetch('/api/me')
.catch(error => { .catch(error => {
console.error('There was a problem with the fetch operation:', error); console.error('There was a problem with the fetch operation:', error);
}); });
// @license-end
</script> </script>
{% endblock %} {% 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> <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> </div>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.getElementById('openModalButtonVideo').addEventListener('click', function() { document.getElementById('openModalButtonVideo').addEventListener('click', function() {
const modalVideoFrame = document.getElementById('modalVideoFrame'); const modalVideoFrame = document.getElementById('modalVideoFrame');
const modal = document.getElementById('modalVideo'); const modal = document.getElementById('modalVideo');
modalVideoFrame.src = 'https://vk.com/video_ext.php?oid=-213856894&id=456239020&hd=2'; modalVideoFrame.src = 'https://vk.com/video_ext.php?oid=-213856894&id=456239020&hd=2';
modal.style.display = 'flex'; modal.style.display = 'flex';
}); });
}); });
function videocloseModalButton() { function videocloseModalButton() {
document.getElementById('modalVideo').style.display = 'none'; document.getElementById('modalVideo').style.display = 'none';
} }
// @license-end
</script> </script>
{%- endmacro %} {%- endmacro %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@
{% import "components/profile_card.html" as profile_card %} {% import "components/profile_card.html" as profile_card %}
{% import "components/profile_nav.html" as profile_nav %} {% import "components/profile_nav.html" as profile_nav %}
{% import "competition/list.html" as competition %} {% import "competition/list.html" as competition %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Где картины{% endblock %} {% block title %}Где картины{% endblock %}
@ -25,7 +26,8 @@
</figcaption> </figcaption>
</figure> </figure>
{% for doc in data %} {% 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}}"> <figcaption class="c_3g8gh b_pvw5a" data-id="{{doc._id}}">
{% if doc.status == 'Ожидает отправки' %} {% if doc.status == 'Ожидает отправки' %}
<div class="b_thlt1"> <div class="b_thlt1">
@ -44,205 +46,74 @@
<span class="b_5dr47_2">{{doc.status}}</span> <span class="b_5dr47_2">{{doc.status}}</span>
</div> </div>
{% endif %} {% endif %}
{% if doc.type == 'exhibition' and doc.get('exhibition') or doc.status == 'Продано' %} {% if doc.type == 'exhibition' and doc.get('exhibition') or doc.status == 'Продано' %}
<p class="b_6spaf">{{doc.exhibition.title}}</p> <p class="b_6spaf">{{doc.exhibition.title}}</p>
<p>Место проведения: {{ doc.exhibition.get('place') }}</p> <p>Место проведения: {{ doc.exhibition.get('place') }}</p>
<div class="b_xz6em"> <div class="b_xz6em">
<img src="/a/{{doc.receiver._id}}" class="b_ob59y"> <img src="/a/{{doc.receiver._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz"> <div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Организатор</span> <span class="b_qla2q">Организатор</span>
<span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span> <span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span>
</div> </div>
</div> </div>
{% if doc.get('user') and user._id == doc.user._id%} {% if doc.get('user') and user._id == doc.user._id%}
<details style="margin-bottom: 1.25rem;"> {{ contacts.details(doc.receiver) }}
<summary>Показать контакты</summary> {% endif %}
<div class="menua"> {% if doc.get('user') %}
<a class="icona" href="/artists/{{doc.receiver._id}}"> <div class="b_xz6em">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798"> <img src="/a/{{doc.user._id}}" class="b_ob59y">
<path <div class="c_9hjdk b_ffruz">
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" /> <span class="b_qla2q">Художник</span>
</svg> <span class="c_zm0m4 b_09est">{{doc.user.username}}</span>
<span>Профиль</span> </div>
</a> </div>
<a class="icona" href="tel:{{doc.receiver.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px" {% if user._id != doc.user._id%}
viewBox="0 -960 960 960" width="24px" fill="#5F1798"> {{ contacts.details(doc.user) }}
<path {% endif %}
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" /> {% endif %}
</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 %}
{% else %} {% else %}
{% if doc.address %} {% if doc.address %}
<p class="b_6spaf">Адрес: {{doc.address}}</p> <p class="b_6spaf">Адрес: {{doc.address}}</p>
{% endif %} {% endif %}
{% if doc.back_address %} {% if doc.back_address %}
<p class="b_6spaf">Адрес для возврата: {{doc.back_address}}</p> <p class="b_6spaf">Адрес для возврата: {{doc.back_address}}</p>
{% endif %} {% endif %}
{% if doc.delivery_type %} {% if doc.delivery_type %}
<p class="b_6spaf">Способ доставки: {{doc.delivery_type}}</p> <p class="b_6spaf">Способ доставки: {{doc.delivery_type}}</p>
{% endif %} {% endif %}
{% if doc.track %} {% if doc.track %}
<p class="b_6spaf">Трек-номер: {{doc.track}}</p> <p class="b_6spaf">Трек-номер: {{doc.track}}</p>
{% endif %} {% endif %}
{% if doc.sender %} {% if doc.sender %}
<div class="b_xz6em"> <div class="b_xz6em">
<img src="/a/{{doc.sender._id}}" class="b_ob59y"> <img src="/a/{{doc.sender._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz"> <div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Отправитель</span> <span class="b_qla2q">Отправитель</span>
<span class="c_zm0m4 b_09est">{{doc.sender.username}}</span> <span class="c_zm0m4 b_09est">{{doc.sender.username}}</span>
</div>
</div> </div>
{% if user._id != doc.sender._id%} </div>
<details style="margin-bottom: 1.25rem;"> {% if user._id != doc.sender._id%}
<summary>Показать контакты</summary> {{ contacts.details(doc.sender) }}
<div class="menua"> {% endif %}
<a class="icona" href="/artists/{{doc.sender._id}}"> {% endif %}
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798"> {% if doc.receiver %}
<path <div class="b_xz6em">
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" /> <img src="/a/{{doc.receiver._id}}" class="b_ob59y">
</svg> <div class="c_9hjdk b_ffruz">
<span>Профиль</span> <span class="b_qla2q">Получатель</span>
</a> <span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span>
<a class="icona" href="tel:{{doc.sender.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px" <span class="b_qla2q">+7 *** *** {{ doc.receiver.phone[-4:] }}</span>
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 %}
{% endif %}
{% if doc.receiver %}
<div class="b_xz6em">
<img src="/a/{{doc.receiver._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Получатель</span>
<span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span>
<span class="b_qla2q">+7 *** *** {{ doc.receiver.phone[-4:] }}</span>
</div>
</div> </div>
{% if user._id != doc.receiver._id%} </div>
<details style="margin-bottom: 1.25rem;"> {% if user._id != doc.receiver._id%}
<summary>Показать контакты</summary> {{ contacts.details(doc.receiver) }}
<div class="menua"> {% endif %}
<a class="icona" href="/artists/{{doc.receiver._id}}"> {% endif %}
<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 %}
{% endif %}
{% endif %} {% endif %}
<p class="b_6spaf"> <p class="b_6spaf">
{{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}} {{ (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> </p>
</figcaption> </figcaption>
</figure> </figure>
@ -251,7 +122,6 @@
</div> </div>
</div> </div>
</section> </section>
{{ footer.section(user) }} {{ footer.section(user) }}
{% endblock %} {% endblock %}
@ -259,6 +129,7 @@
{{ header.style() }} {{ header.style() }}
{{ competition.style() }} {{ competition.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ contacts.style() }}
<style>/* Base (common) styles */ <style>/* Base (common) styles */
.c_aihm6{width: 100%;} .c_aihm6{width: 100%;}
.mw{margin: 0 20px; .mw{margin: 0 20px;
@ -394,15 +265,6 @@ picture>img{ width: 100%; object-fit: scale-down; }
} }
</style> </style>
<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 { .c_hysjg {
color: #0ED646; color: #0ED646;
font-family: Inter; font-family: Inter;

View File

@ -4,11 +4,13 @@
{% import "components/profile_card.html" as profile_card %} {% import "components/profile_card.html" as profile_card %}
{% import "components/profile_nav.html" as profile_nav %} {% import "components/profile_nav.html" as profile_nav %}
{% import "competition/list.html" as competition %} {% import "competition/list.html" as competition %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Где картины{% endblock %} {% block title %}Где картины{% endblock %}
{% macro render_doc(doc) %} {% 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}}"> <img class="b_1fvkt" src="/b/posts/{{doc.img}}">
<figcaption class="c_3g8gh b_pvw5a" data-id="{{doc._id}}"> <figcaption class="c_3g8gh b_pvw5a" data-id="{{doc._id}}">
{% if doc.status == 'Ожидает отправки' %} {% if doc.status == 'Ожидает отправки' %}
@ -33,235 +35,97 @@
<h2 class="c_9hvtd b_f5fb2">{{doc.title}}</h2> <h2 class="c_9hvtd b_f5fb2">{{doc.title}}</h2>
{% if doc.status == 'На выставке' %} {% if doc.status == 'На выставке' %}
<label class="offer-label"> <label class="offer-label">
<input type="checkbox" <input type="checkbox" onchange="saledPaintings('{{doc.post_id}}')" class="offer-checkbox" {% if
onchange="saledPaintings('{{doc.post_id}}')" doc.status=='Продано' %}checked{%endif%}>
class="offer-checkbox"
{% if doc.status == 'Продано' %}checked{%endif%}>
<span>Продано</span> <span>Продано</span>
</label> </label>
{% endif %} {% endif %}
{% if doc.type == 'exhibition' and doc.get('exhibition') or doc.status == 'Продано' %} {% if doc.type == 'exhibition' and doc.get('exhibition') or doc.status == 'Продано' %}
<p class="b_6spaf">{{doc.get('exhibition').title}}</p> <p class="b_6spaf">{{doc.get('exhibition').title}}</p>
<div class="b_xz6em"> <div class="b_xz6em">
<img src="/a/{{doc.receiver._id}}" class="b_ob59y"> <img src="/a/{{doc.receiver._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz"> <div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Организатор</span> <span class="b_qla2q">Организатор</span>
<span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span> <span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span>
</div>
</div> </div>
{% if doc.get('user') and user._id == doc.user._id%} </div>
<details style="margin-bottom: 1.25rem;"> {% if doc.get('user') and user._id == doc.user._id%}
<summary>Показать контакты</summary> {{ contacts.details(doc.receiver) }}
<div class="menua"> {% endif %}
<a class="icona" href="/artists/{{doc.receiver._id}}"> {% if doc.get('user') %}
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798"> <div class="b_xz6em">
<path <img src="/a/{{doc.user._id}}" class="b_ob59y">
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" /> <div class="c_9hjdk b_ffruz">
</svg> <span class="b_qla2q">Художник</span>
<span>Профиль</span> <span class="c_zm0m4 b_09est">{{doc.user.username}}</span>
</a> </div>
<a class="icona" href="tel:{{doc.receiver.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px" </div>
viewBox="0 -960 960 960" width="24px" fill="#5F1798"> {% if user._id != doc.user._id%}
<path {{ contacts.details(doc.user) }}
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" /> {% endif %}
</svg> {% endif %}
<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 %}
{% else %} {% else %}
{% if doc.address %} {% if doc.address %}
<p class="b_6spaf">Адрес: {{doc.address}}</p> <p class="b_6spaf">Адрес: {{doc.address}}</p>
{% endif %} {% endif %}
{% if doc.back_address %} {% if doc.back_address %}
<p class="b_6spaf">Адрес для возврата: {{doc.back_address}}</p> <p class="b_6spaf">Адрес для возврата: {{doc.back_address}}</p>
{% endif %} {% endif %}
{% if doc.delivery_type %} {% if doc.delivery_type %}
<p class="b_6spaf">Способ доставки: {{doc.delivery_type}}</p> <p class="b_6spaf">Способ доставки: {{doc.delivery_type}}</p>
{% endif %} {% endif %}
{% if doc.track %} {% if doc.track %}
<p class="b_6spaf">Трэк-номер: {{doc.track}}</p> <p class="b_6spaf">Трэк-номер: {{doc.track}}</p>
{% endif %} {% endif %}
{% if doc.sender %} {% if doc.sender %}
<div class="b_xz6em"> <div class="b_xz6em">
<img src="/a/{{doc.sender._id}}" class="b_ob59y"> <img src="/a/{{doc.sender._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz"> <div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Отправитель</span> <span class="b_qla2q">Отправитель</span>
<span class="c_zm0m4 b_09est">{{doc.sender.username}}</span> <span class="c_zm0m4 b_09est">{{doc.sender.username}}</span>
</div>
</div> </div>
{% if user._id != doc.sender._id%} </div>
<details style="margin-bottom: 1.25rem;"> {% if user._id != doc.sender._id%}
<summary>Показать контакты</summary> {{ contacts.details(doc.sender) }}
<div class="menua"> {% endif %}
<a class="icona" href="/artists/{{doc.sender._id}}"> {% endif %}
<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 doc.receiver %}
{% if not doc.sender.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}> <div class="b_xz6em">
<img src="/static/icons/vk.svg" height="40"> <img src="/a/{{doc.receiver._id}}" class="b_ob59y">
</a> <div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Получатель</span>
<a class="icona" href="https://telegram.me/{{doc.sender.phone.split(',')[-1].strip()}}"> <span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span>
<img src="/static/icons/tg.svg" height="40"> <span class="b_qla2q">+7 *** *** {{ doc.receiver.phone[-4:] }}</span>
<!-- <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 %}
{% endif %}
{% if doc.receiver %}
<div class="b_xz6em">
<img src="/a/{{doc.receiver._id}}" class="b_ob59y">
<div class="c_9hjdk b_ffruz">
<span class="b_qla2q">Получатель</span>
<span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span>
<span class="b_qla2q">+7 *** *** {{ doc.receiver.phone[-4:] }}</span>
</div>
</div> </div>
{% if user._id != doc.receiver._id%} </div>
<details style="margin-bottom: 1.25rem;"> {% if user._id != doc.receiver._id%}
<summary>Показать контакты</summary> {{ contacts.details(doc.receiver) }}
<div class="menua"> {% endif %}
<a class="icona" href="/artists/{{doc.receiver._id}}"> {% endif %}
<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%}" {# Checkbox instead of per-item button #}
{% if not doc.receiver.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}> {% if doc.status in ['Ожидает отправки', 'Возврат художнику'] and user._id == doc.sender._id %}
<img src="/static/icons/vk.svg" height="40"> <label class="offer-label delivery-checkbox-label">
</a> <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 %}
<a class="icona" href="https://telegram.me/{{doc.receiver.phone.split(',')[-1].strip()}}"> {% if doc.status == 'Ожидает получения' and user._id == doc.receiver._id %}
<img src="/static/icons/tg.svg" height="40"> <label class="offer-label delivery-checkbox-label">
<!-- <span>Написать в ТГ</span> --> <input type="checkbox" class="offer-checkbox delivery-checkbox accept-checkbox" data-id="{{doc._id}}">
</a> <span>Выбрать для получения</span>
<a class="icona" href="https://max.ru/{{doc.receiver.phone.split(',')[-1].strip()}}"> </label>
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;"> {% endif %}
<!-- <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 %}
{% 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}}">
<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}}">
<span>Выбрать для получения</span>
</label>
{% endif %}
{% endif %} {% endif %}
<p class="b_6spaf"> <p class="b_6spaf">
{{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}} {{ (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> </p>
</figcaption> </figcaption>
</figure> </figure>
@ -362,6 +226,7 @@
</button> </button>
</div> </div>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function saledPaintings(postId) { function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, { fetch(`/api/post/${postId}/sale`, {
method: 'PUT' method: 'PUT'
@ -392,8 +257,10 @@
}); });
}); });
} }
// @license-end
</script> </script>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function selectAll(elem, send=true){ function selectAll(elem, send=true){
if (send){ if (send){
if (elem.checked){ if (elem.checked){
@ -420,8 +287,10 @@
updateStickyButtons(); updateStickyButtons();
} }
// @license-end
</script> </script>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
// Helpers: collect selected IDs // Helpers: collect selected IDs
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
@ -668,6 +537,7 @@
} }
}); });
}); });
// @license-end
</script> </script>
<script defer> <script defer>
function toggleTrackField() { function toggleTrackField() {
@ -717,8 +587,10 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
document.getElementById('exhibition_id').value = exhibition_id; document.getElementById('exhibition_id').value = exhibition_id;
} }
filterFigure(exhibition_id); filterFigure(exhibition_id);
// @license-end
</script> </script>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const deliveryType = document.getElementById('delivery_type'); const deliveryType = document.getElementById('delivery_type');
const track = document.getElementById('track'); const track = document.getElementById('track');
@ -734,6 +606,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
deliveryType.addEventListener('change', toggleTrack); deliveryType.addEventListener('change', toggleTrack);
toggleTrack(); toggleTrack();
// @license-end
</script> </script>
</div> </div>
@ -748,17 +621,9 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
{{ header.style() }} {{ header.style() }}
{{ competition.style() }} {{ competition.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ contacts.style() }}
<style> <style>
/* Base (common) styles */ /* 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%;} .c_aihm6{width: 100%;}
.mw{margin: 0 20px; .mw{margin: 0 20px;
width: 100%;} width: 100%;}

View File

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

View File

@ -217,6 +217,7 @@
</div> </div>
</section> </section>
<script> <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('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('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 %}` 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('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 %}` document.getElementById('biography').value = `{% if user.get('biography') is not none %}{{user.get('biography','') }}{% endif %}`
// @license-end
</script> </script>
<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)}); 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>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const createBtn = document.getElementById('createBtn'); const createBtn = document.getElementById('createBtn');
@ -328,6 +333,7 @@ window.location.reload();
}); });
// @license-end
</script> </script>
{{ footer.section(user) }} {{ footer.section(user) }}
{% endblock %} {% endblock %}

View File

@ -3,6 +3,7 @@
{% import "/components/footer.html" as footer %} {% import "/components/footer.html" as footer %}
{% import "components/profile_card.html" as profile_card %} {% import "components/profile_card.html" as profile_card %}
{% import "components/profile_nav.html" as profile_nav %} {% import "components/profile_nav.html" as profile_nav %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Приглашения{% endblock %} {% block title %}Приглашения{% endblock %}
@ -199,6 +200,7 @@
</button> </button>
</div> </div>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function selecteAll(elem){ function selecteAll(elem){
if (elem.checked){ if (elem.checked){
document.querySelectorAll('.yes').forEach(el=>{ document.querySelectorAll('.yes').forEach(el=>{
@ -215,6 +217,7 @@
updateSelectedCount() updateSelectedCount()
} }
// @license-end
</script> </script>
{%endif%} {%endif%}
{% if doc.get('type_')=='paint' %} {% if doc.get('type_')=='paint' %}
@ -253,6 +256,7 @@
{# Sticky bottom action bar #} {# Sticky bottom action bar #}
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function toggleTrackField() { function toggleTrackField() {
const deliveryType = document.getElementById('delivery_type'); const deliveryType = document.getElementById('delivery_type');
const trackGroup = document.getElementById('track-group'); const trackGroup = document.getElementById('track-group');
@ -275,8 +279,10 @@ document.addEventListener('DOMContentLoaded', toggleTrackField);
// Run every time the select changes // Run every time the select changes
document.getElementById('delivery_type')?.addEventListener('change', toggleTrackField); document.getElementById('delivery_type')?.addEventListener('change', toggleTrackField);
// @license-end
</script> </script>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const query = window.location.href.split('?')[1] || ''; const query = window.location.href.split('?')[1] || '';
let exhibition_id = null; let exhibition_id = null;
if (query) { if (query) {
@ -302,8 +308,10 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
document.getElementById('exhibition_id').value = exhibition_id; document.getElementById('exhibition_id').value = exhibition_id;
} }
filterFigure(exhibition_id); filterFigure(exhibition_id);
// @license-end
</script> </script>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const deliveryType = document.getElementById('delivery_type'); const deliveryType = document.getElementById('delivery_type');
const track = document.getElementById('track'); const track = document.getElementById('track');
@ -319,8 +327,10 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
deliveryType.addEventListener('change', toggleTrack); deliveryType.addEventListener('change', toggleTrack);
toggleTrack(); toggleTrack();
// @license-end
</script> </script>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
// Helpers: collect selected IDs // Helpers: collect selected IDs
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
@ -561,6 +571,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
} }
}); });
}); });
// @license-end
</script> </script>
{% endif %} {% endif %}
</div> </div>
@ -568,6 +579,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
</div> </div>
</section> </section>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const LS_KEY = 'selected_posts_ids_{{doc._id}}'; const LS_KEY = 'selected_posts_ids_{{doc._id}}';
function safeParseArray(raw) { function safeParseArray(raw) {
@ -728,6 +740,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
setAllCheckboxesFromStorage(); setAllCheckboxesFromStorage();
}); });
// @license-end
</script> </script>
{{ footer.section(user) }} {{ footer.section(user) }}
{% endblock %} {% endblock %}
@ -742,45 +755,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
</div> </div>
{% endif %} {% endif %}
</div> </div>
<details style="margin-bottom: 1.25rem;"> {{ contacts.details(user) }}
<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>
{% endmacro %} {% endmacro %}
{% macro render_links(doc) %} {% macro render_links(doc) %}
{% if doc.get('exhibition')%} {% if doc.get('exhibition')%}
@ -793,6 +768,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ contacts.style() }}
<style> <style>
/* Base (common) styles */ /* Base (common) styles */
.c_aihm6{width: 100%;} .c_aihm6{width: 100%;}
@ -929,15 +905,6 @@ picture>img{ width: 100%; object-fit: scale-down; }
} }
</style> </style>
<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 { .c_hysjg {
color: #0ED646; color: #0ED646;
font-family: Inter; font-family: Inter;

View File

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

View File

@ -3,11 +3,11 @@
{% import "/components/footer.html" as footer %} {% import "/components/footer.html" as footer %}
{% import "components/profile_card.html" as profile_card %} {% import "components/profile_card.html" as profile_card %}
{% import "components/profile_nav.html" as profile_nav %} {% import "components/profile_nav.html" as profile_nav %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Приглашения{% endblock %} {% block title %}Приглашения{% endblock %}
{% block content %} {% block content %}
{{ header.section(user) }} {{ header.section(user) }}
<section class="c_dg3k4"> <section class="c_dg3k4">
@ -76,6 +76,7 @@
</div> </div>
</section> </section>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function accept_inv(inv_id) { async function accept_inv(inv_id) {
try { try {
const res = await fetch(`/api/invitation/accept/${inv_id}`, { const res = await fetch(`/api/invitation/accept/${inv_id}`, {
@ -95,63 +96,27 @@ async function accept_inv(inv_id) {
showToast({ message: 'Ошибка сети/сервера', status: 'error' }); showToast({ message: 'Ошибка сети/сервера', status: 'error' });
} }
} }
// @license-end
</script> </script>
{{ footer.section(user) }} {{ footer.section(user) }}
{% endblock %} {% endblock %}
{% macro render_user_contacts(user) %} {% macro render_user_contacts(user) %}
{% if user %}
<div class="c_dbbe4 b_otttb"> <div class="c_dbbe4 b_otttb">
{% if user %}
<img class="c_pm80w b_a68fq" src="/a/{{user._id}}" alt="{{ user.username | e }}" crossorigin="anonymous" <img class="c_pm80w b_a68fq" src="/a/{{user._id}}" alt="{{ user.username | e }}" crossorigin="anonymous"
fetchpriority="high" loading="eager"> fetchpriority="high" loading="eager">
<div class="c_4402p b_ntazj"> <div class="c_4402p b_ntazj">
<p class="c_kvah4 b_ghosy">{{ user.username | e }}</p> <p class="c_kvah4 b_ghosy">{{ user.username | e }}</p>
</div> </div>
{% endif %}
</div> </div>
<details style="margin-bottom: 1.25rem;"> {{ contacts.details(user) }}
<summary>Показать контакты</summary> {% endif %}
<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>
{% endmacro %} {% endmacro %}
{% block head %} {% block head %}
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ contacts.style() }}
<style> <style>
/* Base (common) styles */ /* Base (common) styles */
.c_aq9et { .c_aq9et {

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@
{% import "components/profile_nav.html" as profile_nav %} {% import "components/profile_nav.html" as profile_nav %}
{% import "competition/list.html" as competition %} {% import "competition/list.html" as competition %}
{% import "components/label.html" as label %} {% import "components/label.html" as label %}
{% import "/components/contacts.html" as contacts %}
{% block title %}Отобранные картины | Выставка {{exhibition.title}}{% endblock %} {% block title %}Отобранные картины | Выставка {{exhibition.title}}{% endblock %}
@ -101,55 +102,7 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<details style="margin-bottom: 1.25rem;"> {{ contacts.details(post.user) }}
<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>
{% if post.status=='В каталоге' %} {% if post.status=='В каталоге' %}
<button onclick="selectPost(this, '{{post._id}}')" class="c_ctfod b_289i1"> <button onclick="selectPost(this, '{{post._id}}')" class="c_ctfod b_289i1">
<span class="c_d2l0b b_al8e7">Убрать с выставки</span> <span class="c_d2l0b b_al8e7">Убрать с выставки</span>
@ -174,6 +127,7 @@
</div> </div>
</section> </section>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function saledPaintings(postId) { function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, { fetch(`/api/post/${postId}/sale`, {
method: 'PUT' method: 'PUT'
@ -204,9 +158,11 @@
}); });
}); });
} }
// @license-end
</script> </script>
{# {#
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function btnAccept(btn, itemId){ async function btnAccept(btn, itemId){
const figcaption = btn.parentElement; const figcaption = btn.parentElement;
const statusDiv = figcaption.querySelectorAll('div')[0]; const statusDiv = figcaption.querySelectorAll('div')[0];
@ -245,9 +201,11 @@
showToast({ message: 'Не удалось подключиться к серверу', duration: 2500 }); showToast({ message: 'Не удалось подключиться к серверу', duration: 2500 });
} }
}; };
// @license-end
</script> </script>
#} #}
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function sendInvitations(exhibition_id) { async function sendInvitations(exhibition_id) {
const data = new FormData(); const data = new FormData();
data.set('exhibition_id', exhibition_id); data.set('exhibition_id', exhibition_id);
@ -296,6 +254,7 @@
select.checked = false select.checked = false
} }
} }
// @license-end
</script> </script>
{{ footer.section(user) }} {{ footer.section(user) }}
{% endblock %} {% endblock %}
@ -304,6 +263,7 @@
{{ header.style() }} {{ header.style() }}
{{ profile_nav.style() }} {{ profile_nav.style() }}
{{ competition.style() }} {{ competition.style() }}
{{ contacts.style() }}
<style> <style>
.menua{width: 100%; .menua{width: 100%;
display: flex; display: flex;

View File

@ -41,14 +41,14 @@
<h3 class="c_hysjg b_8f3uh" id="preview_title">{{i.title}}</h3> <h3 class="c_hysjg b_8f3uh" id="preview_title">{{i.title}}</h3>
</header> </header>
<p class="c_2fr39 b_tfnz2" id="preview_topic">{{i.topic}}</p> <p class="c_2fr39 b_tfnz2" id="preview_topic">{{i.topic}}</p>
{% if i.user %}
<div class="c_dbbe4 b_otttb"> <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 }}"> <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"> <div class="c_4402p b_ntazj">
<p class="c_kvah4 b_ghosy">{{ i.user.username }}</p> <p class="c_kvah4 b_ghosy">{{ i.user.username }}</p>
</div> </div>
{% endif %}
</div> </div>
{% endif %}
<p class="c_2fr39 b_tfnz2">Подробнее</p> <p class="c_2fr39 b_tfnz2">Подробнее</p>
</a> </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> <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> </div>
</section> </section>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
async function deleteExhibition(btn, _id) { async function deleteExhibition(btn, _id) {
const article = btn.closest('article'); const article = btn.closest('article');
try { try {
@ -75,6 +76,7 @@ async function deleteExhibition(btn, _id) {
console.error('Delete failed:', err); console.error('Delete failed:', err);
} }
} }
// @license-end
</script> </script>
{{ footer.section(user) }} {{ footer.section(user) }}
{% endblock %} {% endblock %}

View File

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

View File

@ -137,9 +137,12 @@
</div> </div>
</form> </form>
<script> <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))})}); 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>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
function showOfferModal(offerHtml = '<p>Текст оферты...</p>') { function showOfferModal(offerHtml = '<p>Текст оферты...</p>') {
return new Promise((resolve) => { return new Promise((resolve) => {
const backdrop = document.createElement('div'); const backdrop = document.createElement('div');
@ -209,14 +212,20 @@ document.querySelectorAll('#openOffer').forEach(el => {
}); });
}); });
}); });
// @license-end
</script> </script>
<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}))})}); 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>
<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)}); 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>
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
// Основная логика // Основная логика
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const emailInput = document.getElementById('email'); const emailInput = document.getElementById('email');
@ -375,9 +384,12 @@ document.addEventListener('click', (e) => {
}); });
// @license-end
</script> </script>
<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)})}); 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> </script>
{{ footer.section(user) }} {{ footer.section(user) }}
{% endblock %} {% endblock %}

View File

@ -26,117 +26,121 @@
</section> </section>
<script> <script>
window.addEventListener('load', () => { // @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
const savedEmail = localStorage.getItem('restoreEmail'); // @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
window.addEventListener('load', () => {
if (savedEmail) { const savedEmail = localStorage.getItem('restoreEmail');
document.getElementById('email').value = savedEmail;
}
});
document.addEventListener('DOMContentLoaded', () => {
const emailInput = document.getElementById('email');
const restoreBtn = document.getElementById('restoreBtn');
const nextUrl = "{{next or '/profile'}}";
restoreBtn.addEventListener('click', async (e) => {
e.preventDefault();
const requiredInputs = document.querySelectorAll('input[required]'); if (savedEmail) {
const requiredTextareas = document.querySelectorAll('textarea[required]'); document.getElementById('email').value = savedEmail;
const requiredFields = [...requiredInputs, ...requiredTextareas];
let firstErrorField = null;
let hasError = false;
requiredFields.forEach(field => {
const group = field.closest('.floating-group');
if (!group) return; // skip if no group wrapper
let isEmpty = false;
if (field.type === 'file') {
isEmpty = !field.files || field.files.length === 0;
} else {
isEmpty = !field.value || !field.value.trim();
}
if (isEmpty) {
group.classList.add('error');
hasError = true;
if (!firstErrorField) firstErrorField = field;
} else {
group.classList.remove('error');
}
});
if (hasError && firstErrorField) {
firstErrorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
showToast({message: 'Введите Email', status: 'error'})
setTimeout(() => firstErrorField.focus(), 600);
return;
}
// No client-side validation errors — send request
try {
const payload = {
identifier: emailInput ? emailInput.value : '',
next: nextUrl
};
const res = await fetch('/api/restore', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (res.ok) {
// success (HTTP 2xx). Redirect to /form
const params = new URLSearchParams(window.location.search);
const next = params.get('next') || '/login';
window.location.href = next;
} else {
// non-2xx — mark all input groups as error
const allFields = document.querySelectorAll('.floating-group');
allFields.forEach(g => g.classList.add('error'));
// Optional: you can show server message if present
const errorText = await res.json();
// console.error('Server error:', errorText);
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'})
} }
}); });
document.addEventListener('DOMContentLoaded', () => {
const emailInput = document.getElementById('email');
const restoreBtn = document.getElementById('restoreBtn');
const nextUrl = "{{next or '/profile'}}";
restoreBtn.addEventListener('click', async (e) => {
e.preventDefault();
const requiredInputs = document.querySelectorAll('input[required]');
const requiredTextareas = document.querySelectorAll('textarea[required]');
const requiredFields = [...requiredInputs, ...requiredTextareas];
let firstErrorField = null;
let hasError = false;
requiredFields.forEach(field => {
const group = field.closest('.floating-group');
if (!group) return; // skip if no group wrapper
let isEmpty = false;
if (field.type === 'file') {
isEmpty = !field.files || field.files.length === 0;
} else {
isEmpty = !field.value || !field.value.trim();
}
if (isEmpty) {
group.classList.add('error');
hasError = true;
if (!firstErrorField) firstErrorField = field;
} else {
group.classList.remove('error');
}
});
if (hasError && firstErrorField) {
firstErrorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
showToast({ message: 'Введите Email', status: 'error' })
setTimeout(() => firstErrorField.focus(), 600);
return;
}
// No client-side validation errors — send request
try {
const payload = {
identifier: emailInput ? emailInput.value : '',
next: nextUrl
};
const res = await fetch('/api/restore', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (res.ok) {
// success (HTTP 2xx). Redirect to /form
const params = new URLSearchParams(window.location.search);
const next = params.get('next') || '/login';
window.location.href = next;
} else {
// non-2xx — mark all input groups as error
const allFields = document.querySelectorAll('.floating-group');
allFields.forEach(g => g.classList.add('error'));
// Optional: you can show server message if present
const errorText = await res.json();
// console.error('Server error:', errorText);
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' })
}
});
// Убираем красную подсветку сразу при исправлении пользователем // Убираем красную подсветку сразу при исправлении пользователем
document.querySelectorAll('.floating-group input').forEach(input => { document.querySelectorAll('.floating-group input').forEach(input => {
input.addEventListener('input', function() { input.addEventListener('input', function () {
const group = this.closest('.floating-group'); const group = this.closest('.floating-group');
if (group) { if (group) {
group.classList.remove('error'); group.classList.remove('error');
}
});
// Для файлов
if (input.type === 'file') {
input.addEventListener('change', function() {
const group = this.closest('.floating-group');
if (group && this.files.length > 0) {
group.classList.remove('error');
}
});
} }
});
// Для файлов
if (input.type === 'file') {
input.addEventListener('change', function () {
const group = this.closest('.floating-group');
if (group && this.files.length > 0) {
group.classList.remove('error');
}
});
}
}); });
}); });
// @license-end
</script> </script>
{{ footer.section() }} {{ footer.section() }}
<script> <script>
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
fetch('/api/me') fetch('/api/me')
.then(response => { .then(response => {
if (response.ok) { if (response.ok) {
@ -153,6 +157,7 @@ fetch('/api/me')
.catch(error => { .catch(error => {
console.error('There was a problem with the fetch operation:', error); console.error('There was a problem with the fetch operation:', error);
}); });
// @license-end
</script> </script>
{% endblock %} {% endblock %}

View File

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

View File

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

View File

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

185
vk.ipynb
View File

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

28
vk.py
View File

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