alterações gerais
This commit is contained in:
5
diarios_oficiais_alems/static/css/bootstrap-custom.min.css
vendored
Normal file
5
diarios_oficiais_alems/static/css/bootstrap-custom.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2106
diarios_oficiais_alems/static/css/bootstrap-icons.css
vendored
Normal file
2106
diarios_oficiais_alems/static/css/bootstrap-icons.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
diarios_oficiais_alems/static/css/fonts/bootstrap-icons.woff
Normal file
BIN
diarios_oficiais_alems/static/css/fonts/bootstrap-icons.woff
Normal file
Binary file not shown.
BIN
diarios_oficiais_alems/static/css/fonts/bootstrap-icons.woff2
Normal file
BIN
diarios_oficiais_alems/static/css/fonts/bootstrap-icons.woff2
Normal file
Binary file not shown.
76
diarios_oficiais_alems/static/css/style.css
Normal file
76
diarios_oficiais_alems/static/css/style.css
Normal file
@ -0,0 +1,76 @@
|
||||
.bg-light-gradient {
|
||||
background: linear-gradient(to right, #f8f9fa, #e9ecef);
|
||||
}
|
||||
.search-card {
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
||||
border: none;
|
||||
}
|
||||
.result-card {
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.result-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.page-item.active .page-link {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
.date-picker {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
padding: 0.375rem 0.75rem;
|
||||
}
|
||||
.best-match {
|
||||
border-left: 4px solid #0d6efd;
|
||||
background-color: rgba(13, 110, 253, 0.05);
|
||||
}
|
||||
.page-content {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.accordion-button:not(.collapsed) {
|
||||
background-color: rgba(13, 110, 253, 0.1);
|
||||
color: #0d6efd;
|
||||
}
|
||||
.accordion-button:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
mark {
|
||||
background-color: #fff3cd;
|
||||
padding: 0.1em 0.2em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.page-badge {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.match-score {
|
||||
color: #0d6efd;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.btn-sort {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.btn-sort.active {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
}
|
||||
.search-options {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.search-options {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 97 KiB |
BIN
diarios_oficiais_alems/static/images/logo.jpg
Normal file
BIN
diarios_oficiais_alems/static/images/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
diarios_oficiais_alems/static/images/risco.jpg
Normal file
BIN
diarios_oficiais_alems/static/images/risco.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
5
diarios_oficiais_alems/static/js/alpine.min.js
vendored
Normal file
5
diarios_oficiais_alems/static/js/alpine.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
diarios_oficiais_alems/static/js/bootstrap.bundle.min.js
vendored
Normal file
7
diarios_oficiais_alems/static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
diarios_oficiais_alems/static/js/config.js
Normal file
1
diarios_oficiais_alems/static/js/config.js
Normal file
@ -0,0 +1 @@
|
||||
const API_BASE_URL = "http://109.199.98.226";
|
||||
242
diarios_oficiais_alems/static/js/script.js
Normal file
242
diarios_oficiais_alems/static/js/script.js
Normal file
@ -0,0 +1,242 @@
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('searchApp', () => ({
|
||||
searchParams: {
|
||||
q: '',
|
||||
numero_diario: '',
|
||||
data_inicio: '',
|
||||
data_fim: '',
|
||||
modo_busca: 'exata',
|
||||
ordenar_por: 'data_asc',
|
||||
page: 1,
|
||||
page_size: 10
|
||||
},
|
||||
searchResults: null,
|
||||
isLoading: false,
|
||||
hasSearched: false,
|
||||
error: null,
|
||||
showAdvanced: false,
|
||||
expandedContents: {},
|
||||
suggestion: null,
|
||||
ultimoTermoBuscado: '',
|
||||
|
||||
get shouldShowSuggestion() {
|
||||
if (!this.suggestion || !this.searchParams.q) return false;
|
||||
|
||||
// Função para remover acentos e converter para minúsculas
|
||||
const normalize = (text) => {
|
||||
return text.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace("/", " ")
|
||||
.replace(/[^a-z0-9\s]/g, " ") // Substitui todos os outros símbolos por espaço
|
||||
.replace(/[\u0300-\u036f]/g, '');
|
||||
};
|
||||
|
||||
const normalizedQuery = normalize(this.searchParams.q);
|
||||
const normalizedSuggestion = normalize(this.suggestion);
|
||||
|
||||
// Só mostra a sugestão se for diferente do termo buscado (ignorando acentos e caixa)
|
||||
return normalizedQuery !== normalizedSuggestion;
|
||||
},
|
||||
|
||||
// Usa a sugestão como novo termo de busca
|
||||
usesuggestion() {
|
||||
this.searchParams.q = this.suggestion;
|
||||
this.searchParams.page = 1;
|
||||
this.performSearch();
|
||||
},
|
||||
|
||||
// Verifica se um diário tem uma melhor correspondência
|
||||
hasBestMatch(diario) {
|
||||
return diario.paginas && diario.paginas.length > 0;
|
||||
},
|
||||
|
||||
// Muda a ordenação e faz uma nova busca
|
||||
changeOrder(order) {
|
||||
if (this.searchParams.ordenar_por !== order) {
|
||||
this.searchParams.ordenar_por = order;
|
||||
this.searchParams.page = 1; // Volta para a primeira página
|
||||
this.performSearch();
|
||||
}
|
||||
},
|
||||
|
||||
// Obter sugestão da API
|
||||
async getSuggestion(query) {
|
||||
if (!query) return null;
|
||||
|
||||
try {
|
||||
const url = new URL('http://192.168.235.234/api/v1/diarios/sugestao');
|
||||
url.searchParams.append('q', query);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return data.sugestao;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar sugestão:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
get totalPages() {
|
||||
if (!this.searchResults) return 0;
|
||||
return Math.ceil(this.searchResults.total / this.searchResults.por_pagina);
|
||||
},
|
||||
|
||||
get paginationArray() {
|
||||
const pages = [];
|
||||
const currentPage = this.searchParams.page;
|
||||
const totalPages = this.totalPages;
|
||||
|
||||
// Função auxiliar para adicionar páginas
|
||||
const addPage = (page) => {
|
||||
if (page >= 1 && page <= totalPages && !pages.includes(page)) {
|
||||
pages.push(page);
|
||||
}
|
||||
};
|
||||
|
||||
// Sempre mostrar primeira página, página atual, última página
|
||||
// e 1-2 páginas adjacentes à página atual
|
||||
addPage(1);
|
||||
addPage(currentPage - 2);
|
||||
addPage(currentPage - 1);
|
||||
addPage(currentPage);
|
||||
addPage(currentPage + 1);
|
||||
addPage(currentPage + 2);
|
||||
addPage(totalPages);
|
||||
|
||||
// Ordenar e adicionar separadores
|
||||
const result = pages.sort((a, b) => a - b);
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// Retorna a melhor página (maior score) de um diário
|
||||
getBestPage(paginas) {
|
||||
if (!paginas || paginas.length === 0) return null;
|
||||
|
||||
// Ordena as páginas por score (se disponível) ou pelo número da página se não houver score
|
||||
const sortedPages = [...paginas].sort((a, b) => {
|
||||
if (a.score === undefined || b.score === undefined) return 0;
|
||||
if (a.score === undefined) return 1;
|
||||
if (b.score === undefined) return -1;
|
||||
return b.score - a.score;
|
||||
});
|
||||
|
||||
return sortedPages[0];
|
||||
},
|
||||
|
||||
// Retorna todas as páginas exceto a melhor
|
||||
getOtherPages(paginas) {
|
||||
if (!paginas || paginas.length <= 1) return [];
|
||||
|
||||
const bestPage = this.getBestPage(paginas);
|
||||
if (!bestPage) return paginas;
|
||||
|
||||
return paginas.filter(p => p.numero !== bestPage.numero)
|
||||
.sort((a, b) => a.numero - b.numero); // Ordena por número da página
|
||||
},
|
||||
|
||||
// Controla a exibição do conteúdo completo de uma página
|
||||
toggleFullContent(diarioIndex, paginaNumero) {
|
||||
const key = `${diarioIndex}-${paginaNumero}`;
|
||||
this.expandedContents[key] = !this.expandedContents[key];
|
||||
},
|
||||
|
||||
// Verifica se um conteúdo está expandido
|
||||
isFullContentVisible(diarioIndex, paginaNumero) {
|
||||
const key = `${diarioIndex}-${paginaNumero}`;
|
||||
return this.expandedContents[key] === true;
|
||||
},
|
||||
|
||||
async performSearch() {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
this.hasSearched = true;
|
||||
this.expandedContents = {}; // Resetar estados expandidos
|
||||
this.suggestion = null; // Resetar a sugestão
|
||||
|
||||
try {
|
||||
let suggestionPromise = null;
|
||||
if (this.searchParams.q) {
|
||||
suggestionPromise = this.getSuggestion(this.searchParams.q);
|
||||
}
|
||||
if (this.searchParams.q !== this.ultimoTermoBuscado) {
|
||||
this.searchParams.page = 1;
|
||||
}
|
||||
this.ultimoTermoBuscado = this.searchParams.q;
|
||||
// Usando agora o endpoint busca
|
||||
const url = new URL('http://192.168.235.234/api/v1/diarios/busca');
|
||||
|
||||
// Adicionar parâmetros à URL
|
||||
Object.entries(this.searchParams).forEach(([key, value]) => {
|
||||
if (value !== '' && value !== null) {
|
||||
url.searchParams.append(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => null);
|
||||
throw new Error(errorData?.message || `Erro HTTP: ${response.status}`);
|
||||
}
|
||||
|
||||
this.searchResults = await response.json();
|
||||
|
||||
// Processar os resultados para garantir que as páginas tenham score
|
||||
if (this.searchResults && this.searchResults.resultados) {
|
||||
this.searchResults.resultados.forEach(diario => {
|
||||
if (diario.paginas) {
|
||||
// Atribuir scores padrão se não existirem
|
||||
diario.paginas.forEach((pagina, index) => {
|
||||
if (pagina.score === undefined || pagina.score === null) {
|
||||
pagina.score = diario.paginas.length - index; // Score inversamente proporcional ao índice
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (suggestionPromise) {
|
||||
this.suggestion = await suggestionPromise;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro na busca:', error);
|
||||
this.error = `Erro ao buscar diários: ${error.message}`;
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
formatDate(dateString) {
|
||||
const options = { day: '2-digit', month: '2-digit', year: 'numeric' };
|
||||
return new Date(dateString + 'T00:00:00').toLocaleDateString('pt-BR', options);
|
||||
},
|
||||
goToPage(page) {
|
||||
if (page < 1 || page > this.totalPages) return;
|
||||
this.searchParams.page = page;
|
||||
this.performSearch();
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
},
|
||||
|
||||
resetSearch() {
|
||||
this.searchParams = {
|
||||
q: '',
|
||||
numero_diario: '',
|
||||
data_inicio: '',
|
||||
data_fim: '',
|
||||
modo_busca: 'exata',
|
||||
ordenar_por: 'relevancia',
|
||||
page: 1,
|
||||
page_size: 10
|
||||
};
|
||||
this.searchResults = null;
|
||||
this.hasSearched = false;
|
||||
this.error = null;
|
||||
this.expandedContents = {};
|
||||
}
|
||||
}));
|
||||
});
|
||||
@ -90,17 +90,6 @@
|
||||
{# URL provided by django-allauth/account/urls.py #}
|
||||
<a class="nav-link" href="{% url 'account_logout' %}">{% translate "Sign Out" %}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{% if ACCOUNT_ALLOW_REGISTRATION %}
|
||||
<li class="nav-item">
|
||||
{# URL provided by django-allauth/account/urls.py #}
|
||||
<a id="sign-up-link" class="nav-link" href="{% url 'account_signup' %}">{% translate "Sign Up" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
{# URL provided by django-allauth/account/urls.py #}
|
||||
<a id="log-in-link" class="nav-link" href="{% url 'account_login' %}">{% translate "Sign In" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -1,25 +1,40 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sistema de Busca de Diários Oficiais</title>
|
||||
<title>Diários Oficiais</title>
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="{% static 'css/bootstrap-custom.min.css' %}" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="{% static 'css/bootstrap-icons.css' %}">
|
||||
<!-- Alpine.js -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.1/dist/cdn.min.js"></script>
|
||||
<script defer src="{% static 'js/alpine.min.js' %}"></script>
|
||||
<!-- Estilos customizados -->
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
|
||||
<link rel="icon" href="{% static 'images/favicon.ico' %}" type="image/x-icon">
|
||||
</head>
|
||||
<body class="bg-light-gradient">
|
||||
<div class="container py-5" x-data="searchApp">
|
||||
<div class="container py-5" x-data="searchApp"> <!-- Container do Logo e Imagem Direita -->
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-12 col-lg-10">
|
||||
<div class="d-flex justify-content-between align-items-center mb-5">
|
||||
<!-- Logo à esquerda -->
|
||||
<div class="logo-container">
|
||||
<img src="{% static 'images/logo.jpg' %}" alt="Logo" class="img-fluid">
|
||||
</div>
|
||||
<!-- Imagem à direita -->
|
||||
<div class="risco-container">
|
||||
<img src="{% static 'images/risco.jpg' %}" alt="Risco" class="img-fluid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-12 col-lg-10">
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="display-5 fw-bold text-primary mb-3">
|
||||
<i class="bi bi-search me-2"></i>Sistema de Busca de Diários Oficiais
|
||||
<i class="bi bi-search me-2"></i>Diários Oficiais
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@ -114,7 +129,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Adicionando a seção de "Você quis dizer" após iniciar a busca -->
|
||||
<!-- Seção Você quis dizer -->
|
||||
<template x-if="!isLoading && !error && searchResults && suggestion && shouldShowSuggestion">
|
||||
<div class="mb-3 mt-3 alert alert-info d-flex align-items-center">
|
||||
<i class="bi bi-lightbulb-fill me-2"></i>
|
||||
@ -283,8 +298,7 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>Sistema de Busca de Diários Oficiais</h5>
|
||||
<p class="small">Uma ferramenta avançada para pesquisa em diários oficiais.</p>
|
||||
<h5>Diários Oficiais</h5>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="small mb-0">© 2025 Todos os direitos reservados</p>
|
||||
@ -292,10 +306,88 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Botão de ajuda que abre o modal -->
|
||||
<button type="button" class="btn btn-outline-secondary position-fixed bottom-0 end-0 m-3" data-bs-toggle="modal" data-bs-target="#helpModal">
|
||||
<i class="bi bi-question-circle"></i> Ajuda
|
||||
</button>
|
||||
|
||||
<!-- Modal de Ajuda -->
|
||||
<div class="modal fade" id="helpModal" tabindex="-1" aria-labelledby="helpModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="helpModalLabel">Ajuda - Sistema de Busca de Diários Oficiais</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Fechar"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-4">
|
||||
<h6>Atenção quanto à qualidade dos dados</h6>
|
||||
<p>Alguns documentos podem conter erros de leitura devido à baixa qualidade das imagens dos PDFs originais. Isso pode afetar a precisão da busca, especialmente no modo de <strong>busca exata</strong>.</p>
|
||||
<p>Nesses casos, recomendamos utilizar o modo <strong>qualquer termo</strong>, que é mais tolerante a pequenas falhas de reconhecimento de texto.</p>
|
||||
</div>
|
||||
|
||||
<h5>Como realizar buscas</h5>
|
||||
<hr>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Busca básica</h6>
|
||||
<p>Digite o termo que deseja buscar no campo principal e clique em "Buscar". O sistema irá localizar ocorrências desse termo nos Diários Oficiais.</p>
|
||||
<ul>
|
||||
<li><strong>Número do Diário:</strong> Se souber o número específico do diário, digite-o neste campo para filtrar os resultados.</li>
|
||||
<li><strong>Modo de Busca:</strong>
|
||||
<ul>
|
||||
<li><em>Busca exata</em> - Encontra apenas documentos que contenham exatamente o termo informado.</li>
|
||||
<li><em>Qualquer termo</em> - Encontra documentos que contenham qualquer um dos termos informados.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Ordenação dos resultados</h6>
|
||||
<p>Você pode ordenar os resultados de três formas:</p>
|
||||
<ul>
|
||||
<li><strong>Relevância:</strong> Mostra primeiro os documentos mais relevantes para sua busca.</li>
|
||||
<li><strong>Data (Decrescente):</strong> Mostra os diários mais recentes primeiro.</li>
|
||||
<li><strong>Data (Crescente):</strong> Mostra os diários mais antigos primeiro.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Filtros avançados</h6>
|
||||
<p>Clique em "Mostrar filtros avançados" para acessar opções adicionais:</p>
|
||||
<ul>
|
||||
<li><strong>Data inicial e Data final:</strong> Restringe a busca a diários publicados dentro do período informado.</li>
|
||||
<li><strong>Resultados por página:</strong> Define quantos resultados serão exibidos em cada página.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Resultados da busca</h6>
|
||||
<p>Nos resultados, você verá:</p>
|
||||
<ul>
|
||||
<li>Tipo e número do diário, com a data de publicação.</li>
|
||||
<li>A página com melhor correspondência aparecerá destacada.</li>
|
||||
<li>Para ver mais páginas do mesmo diário, clique no botão de expansão.</li>
|
||||
<li>Use o botão "Ver Diário Completo" para abrir o arquivo PDF do diário inteiro.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Sugestões de busca</h6>
|
||||
<p>Se o sistema encontrar um termo semelhante ao que você buscou, mostrará uma sugestão que você pode clicar para realizar uma nova busca com o termo sugerido.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Entendi</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bootstrap Bundle with Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
|
||||
<!-- Script da aplicação -->
|
||||
<script src="script.js"></script>
|
||||
<script src="{% static 'js/config.js' %}"></script>
|
||||
<script src="{% static 'js/script.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user