fix: ajustes de javascript e funcionamento
This commit is contained in:
parent
b4b87f42c1
commit
1b74de34e6
3
.gitignore
vendored
3
.gitignore
vendored
@ -380,4 +380,5 @@ temp/
|
|||||||
# Certificates
|
# Certificates
|
||||||
*.pfx
|
*.pfx
|
||||||
*.crt
|
*.crt
|
||||||
*.key
|
*.key
|
||||||
|
wwwroot/dist/
|
||||||
|
|||||||
19
AGENTS.md
Normal file
19
AGENTS.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Repository Guidelines
|
||||||
|
|
||||||
|
## Project Structure & Module Organization
|
||||||
|
Core MVC code lives in `Controllers`, `Services`, `Models`, and `Middleware`, while Razor views sit in `Views` and static assets in `wwwroot`. Data access and caching helpers are under `Data` and `Providers`. Localized resources (PT-BR, ES, EN) reside in `Resources`. Tests target service logic in `Tests/Services` via `QRRapidoApp.Tests.csproj`. Runtime settings use the `appsettings.*.json` files, and Docker assets, including `docker-compose.yml`, remain at the repository root.
|
||||||
|
|
||||||
|
## Build, Test, and Development Commands
|
||||||
|
Install dependencies with `dotnet restore`, then run `dotnet build` for a release-ready compile. Use `dotnet run` to launch the Kestrel server locally or `dotnet watch run` for hot reload during UI work. Execute `dotnet test` from the root to run xUnit tests; add `--collect:"XPlat Code Coverage"` when you need coverage reports. For full-stack parity, start infrastructure with `docker-compose up -d` and follow component logs through `docker-compose logs -f qrrapido`.
|
||||||
|
|
||||||
|
## Coding Style & Naming Conventions
|
||||||
|
Stick to standard C# styling: four-space indentation, PascalCase for types and public members, camelCase for locals, and `I` prefixes for interfaces. Keep services focused; prefer small, testable classes instead of partials. Place shared copy in `Resources` and surface it via `IStringLocalizer`. Run `dotnet format` before committing to normalize imports, analyzer fixes, and whitespace.
|
||||||
|
|
||||||
|
## Testing Guidelines
|
||||||
|
Keep unit and integration tests close to the subject, e.g., `Tests/Services/QRRapidoServiceTests.cs`. Name classes `<Subject>Tests` and methods as scenario sentences such as `GenerateRapidAsync_WithValidRequest_ReturnsSuccess`. Use Moq for external dependencies and configure defaults in constructor helpers. Every new behavior should gain at least one happy-path and one guard test, and maintain quick, deterministic fixtures.
|
||||||
|
|
||||||
|
## Commit & Pull Request Guidelines
|
||||||
|
Follow the Conventional Commit style observed here (`fix:`, `feat:`, `chore:`) with concise Portuguese summaries for user-facing updates. Group related changes per commit to simplify reversions. Pull requests should outline the context, enumerate key changes, attach `dotnet test` output or coverage deltas, and include UI screenshots or GIFs when behavior shifts. Reference Azure or GitHub issue IDs and call out configuration or migration steps prominently.
|
||||||
|
|
||||||
|
## Security & Configuration Tips
|
||||||
|
Never commit secrets or the contents of `keys/`. Document new configuration values in the PR description and supply safe defaults in `appsettings.Development.json`. Protect added endpoints with existing rate-limiting and authorization middleware, and rotate published test keys after demos.
|
||||||
19
Program.cs
19
Program.cs
@ -25,7 +25,6 @@ using Microsoft.AspNetCore.DataProtection;
|
|||||||
using Microsoft.AspNetCore.RateLimiting;
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
using WebOptimizer;
|
|
||||||
|
|
||||||
// Fix for WSL path issues - disable StaticWebAssets completely
|
// Fix for WSL path issues - disable StaticWebAssets completely
|
||||||
var options = new WebApplicationOptions
|
var options = new WebApplicationOptions
|
||||||
@ -96,22 +95,6 @@ Log.Logger = loggerConfig.CreateLogger();
|
|||||||
builder.Host.UseSerilog();
|
builder.Host.UseSerilog();
|
||||||
|
|
||||||
// Add services to the container
|
// Add services to the container
|
||||||
builder.Services.AddWebOptimizer(pipelines =>
|
|
||||||
{
|
|
||||||
pipelines.AddCssBundle(
|
|
||||||
"/css/app.min.css",
|
|
||||||
"css/site.css",
|
|
||||||
"css/qrrapido-theme.css");
|
|
||||||
|
|
||||||
pipelines.AddJavaScriptBundle(
|
|
||||||
"/js/app.min.js",
|
|
||||||
"js/test.js",
|
|
||||||
"js/simple-opcacity.js",
|
|
||||||
"js/qr-speed-generator.js",
|
|
||||||
"js/language-switcher.js",
|
|
||||||
"js/theme-toggle.js",
|
|
||||||
"js/cookie-consent.js");
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.AddControllersWithViews()
|
builder.Services.AddControllersWithViews()
|
||||||
.AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
|
.AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
|
||||||
@ -316,8 +299,6 @@ if (!app.Environment.IsDevelopment())
|
|||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
app.UseWebOptimizer();
|
|
||||||
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
// Language redirection middleware (before routing)
|
// Language redirection middleware (before routing)
|
||||||
|
|||||||
@ -9,7 +9,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="LigerShark.WebOptimizer.Core" Version="3.0.477" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="8.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="8.0.0" />
|
||||||
<PackageReference Include="MongoDB.Driver" Version="2.22.0" />
|
<PackageReference Include="MongoDB.Driver" Version="2.22.0" />
|
||||||
@ -66,4 +65,10 @@
|
|||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
<Target Name="BuildFrontend" BeforeTargets="Build" Condition="'$(Configuration)'=='Release'">
|
||||||
|
<Message Importance="High" Text="Executando build do frontend com Vite..." />
|
||||||
|
<Exec Command="npm install" WorkingDirectory="$(MSBuildProjectDirectory)" Condition="!Exists('node_modules')" />
|
||||||
|
<Exec Command="npm run build" WorkingDirectory="$(MSBuildProjectDirectory)" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|||||||
@ -1284,12 +1284,11 @@
|
|||||||
@await Html.PartialAsync("_AdSpace", new { position = "footer" })
|
@await Html.PartialAsync("_AdSpace", new { position = "footer" })
|
||||||
|
|
||||||
|
|
||||||
<script src="~/js/simple-opcacity.js"></script>
|
@section Scripts {
|
||||||
|
<!-- Script para controles de logo aprimorados -->
|
||||||
<!-- Script para controles de logo aprimorados -->
|
<script>
|
||||||
<script>
|
// JavaScript para controles de logo aprimorados
|
||||||
// JavaScript para controles de logo aprimorados
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const logoUpload = document.getElementById('logo-upload');
|
const logoUpload = document.getElementById('logo-upload');
|
||||||
const logoSizeSlider = document.getElementById('logo-size-slider');
|
const logoSizeSlider = document.getElementById('logo-size-slider');
|
||||||
const logoSizeDisplay = document.getElementById('logo-size-display');
|
const logoSizeDisplay = document.getElementById('logo-size-display');
|
||||||
@ -1430,8 +1429,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Toast positioning improvements */
|
/* Toast positioning improvements */
|
||||||
@ -1481,4 +1481,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
#vcard-website {
|
#vcard-website {
|
||||||
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -3,6 +3,11 @@
|
|||||||
@using Microsoft.Extensions.Localization
|
@using Microsoft.Extensions.Localization
|
||||||
@inject AdDisplayService AdService
|
@inject AdDisplayService AdService
|
||||||
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
||||||
|
@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment HostEnvironment
|
||||||
|
|
||||||
|
@{
|
||||||
|
var isDevelopment = HostEnvironment?.IsDevelopment() ?? false;
|
||||||
|
}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="pt-BR">
|
<html lang="pt-BR">
|
||||||
<head>
|
<head>
|
||||||
@ -49,8 +54,8 @@
|
|||||||
<link rel="preload" href="/webfonts/fa-solid-900.woff2" as="font" type="font/woff2" crossorigin>
|
<link rel="preload" href="/webfonts/fa-solid-900.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
<link rel="preload" href="/webfonts/fa-brands-400.woff2" as="font" type="font/woff2" crossorigin>
|
<link rel="preload" href="/webfonts/fa-brands-400.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
<link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" as="style">
|
<link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" as="style">
|
||||||
<link rel="preload" href="~/css/app.min.css" as="style">
|
<link rel="preload" href="~/css/site.css" as="style">
|
||||||
<link rel="preload" href="~/js/app.min.js" as="script">
|
<link rel="preload" href="~/css/qrrapido-theme.css" as="style">
|
||||||
|
|
||||||
<!-- Structured Data Schema.org -->
|
<!-- Structured Data Schema.org -->
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
@ -149,7 +154,8 @@
|
|||||||
<link rel="stylesheet" href="~/css/vendor/fontawesome.min.css" asp-append-version="true" media="print" onload="this.media='all'" />
|
<link rel="stylesheet" href="~/css/vendor/fontawesome.min.css" asp-append-version="true" media="print" onload="this.media='all'" />
|
||||||
|
|
||||||
<!-- Custom CSS - Critical above fold with cache busting -->
|
<!-- Custom CSS - Critical above fold with cache busting -->
|
||||||
<link rel="stylesheet" href="~/css/app.min.css?v=@DateTime.Now.Ticks" />
|
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||||
|
<link rel="stylesheet" href="~/css/qrrapido-theme.css" asp-append-version="true" />
|
||||||
|
|
||||||
<!-- Translation variables for JavaScript -->
|
<!-- Translation variables for JavaScript -->
|
||||||
<script>
|
<script>
|
||||||
@ -369,11 +375,20 @@
|
|||||||
<!-- Bootstrap 5 JS -->
|
<!-- Bootstrap 5 JS -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<!-- Custom JS with cache busting -->
|
@if (isDevelopment)
|
||||||
<script src="~/js/app.min.js?v=@DateTime.Now.Ticks" defer></script>
|
{
|
||||||
|
<script src="~/js/simple-opcacity.js" asp-append-version="true" defer></script>
|
||||||
<!-- Performance optimizations moved to external file -->
|
<script src="~/js/test.js" asp-append-version="true" defer></script>
|
||||||
<script src="~/js/performance-optimizations.js?v=@DateTime.Now.Ticks" defer></script>
|
<script src="~/js/qr-speed-generator.js" asp-append-version="true" defer></script>
|
||||||
|
<script src="~/js/language-switcher.js" asp-append-version="true" defer></script>
|
||||||
|
<script src="~/js/theme-toggle.js" asp-append-version="true" defer></script>
|
||||||
|
<script src="~/js/cookie-consent.js" asp-append-version="true" defer></script>
|
||||||
|
<script src="~/js/performance-optimizations.js" asp-append-version="true" defer></script>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<script src="~/dist/app.min.js" asp-append-version="true"></script>
|
||||||
|
}
|
||||||
|
|
||||||
@await RenderSectionAsync("Scripts", required: false)
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
13
package.json
Normal file
13
package.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "qrrapido-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --config vite.config.js",
|
||||||
|
"build": "vite build --config vite.config.js",
|
||||||
|
"preview": "vite preview --config vite.config.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^5.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
vite.config.js
Normal file
21
vite.config.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => ({
|
||||||
|
root: '.',
|
||||||
|
publicDir: false,
|
||||||
|
build: {
|
||||||
|
target: 'es2020',
|
||||||
|
outDir: 'wwwroot/dist',
|
||||||
|
emptyOutDir: true,
|
||||||
|
assetsDir: '.',
|
||||||
|
sourcemap: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: 'wwwroot/js/app.entry.js',
|
||||||
|
output: {
|
||||||
|
format: 'iife',
|
||||||
|
entryFileNames: 'app.min.js',
|
||||||
|
assetFileNames: '[name][extname]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
7
wwwroot/js/app.entry.js
Normal file
7
wwwroot/js/app.entry.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import './simple-opcacity.js';
|
||||||
|
import './test.js';
|
||||||
|
import './qr-speed-generator.js';
|
||||||
|
import './language-switcher.js';
|
||||||
|
import './theme-toggle.js';
|
||||||
|
import './cookie-consent.js';
|
||||||
|
import './performance-optimizations.js';
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// QR Rapido Speed Generator
|
// QR Rapido Speed Generator
|
||||||
class QRRapidoGenerator {
|
class QRRapidoGenerator {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.startTime = 0;
|
this.startTime = 0;
|
||||||
@ -42,6 +42,7 @@ class QRRapidoGenerator {
|
|||||||
this.currentLang = localStorage.getItem('qrrapido-lang') || 'pt-BR';
|
this.currentLang = localStorage.getItem('qrrapido-lang') || 'pt-BR';
|
||||||
|
|
||||||
this.selectedType = null;
|
this.selectedType = null;
|
||||||
|
this.previousType = null;
|
||||||
this.selectedStyle = 'classic'; // Estilo padrão
|
this.selectedStyle = 'classic'; // Estilo padrão
|
||||||
this.contentValid = false;
|
this.contentValid = false;
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ class QRRapidoGenerator {
|
|||||||
this.contentDelayTimer = null;
|
this.contentDelayTimer = null;
|
||||||
this.hasShownContentToast = false;
|
this.hasShownContentToast = false;
|
||||||
this.buttonReadyState = false;
|
this.buttonReadyState = false;
|
||||||
|
this.urlPrefix = 'https://';
|
||||||
|
|
||||||
this.initializeEvents();
|
this.initializeEvents();
|
||||||
this.checkAdFreeStatus();
|
this.checkAdFreeStatus();
|
||||||
@ -132,9 +134,6 @@ class QRRapidoGenerator {
|
|||||||
this.updateGenerateButton();
|
this.updateGenerateButton();
|
||||||
}, 200); // Additional URL validation delay
|
}, 200); // Additional URL validation delay
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger UX improvements with delay
|
|
||||||
this.handleContentInputWithDelay(e.target.value);
|
|
||||||
}, 300);
|
}, 300);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -198,6 +197,146 @@ class QRRapidoGenerator {
|
|||||||
this.generateQRWithTimer(e);
|
this.generateQRWithTimer(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setupUrlFieldHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupUrlFieldHandlers() {
|
||||||
|
const contentField = document.getElementById('qr-content');
|
||||||
|
if (!contentField) return;
|
||||||
|
|
||||||
|
contentField.addEventListener('focus', () => {
|
||||||
|
if (this.selectedType === 'url') {
|
||||||
|
if (!contentField.value.trim()) {
|
||||||
|
contentField.value = this.urlPrefix;
|
||||||
|
} else {
|
||||||
|
contentField.value = this.ensureUrlPrefix(contentField.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isProtocolOnly(contentField.value.trim())) {
|
||||||
|
this.clearValidationError();
|
||||||
|
contentField.classList.remove('is-valid', 'is-invalid');
|
||||||
|
this.contentValid = false;
|
||||||
|
this.updateGenerateButton();
|
||||||
|
this.setCaretPosition(contentField, this.getProtocolLength(contentField.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
contentField.addEventListener('paste', (event) => {
|
||||||
|
if (this.selectedType !== 'url') return;
|
||||||
|
event.preventDefault();
|
||||||
|
const text = (event.clipboardData || window.clipboardData)?.getData('text') || '';
|
||||||
|
const normalized = this.normalizeUrlInput(text);
|
||||||
|
contentField.value = normalized;
|
||||||
|
this.setCaretPosition(contentField, normalized.length);
|
||||||
|
this.handleContentChange(normalized);
|
||||||
|
this.updateGenerateButton();
|
||||||
|
});
|
||||||
|
|
||||||
|
contentField.addEventListener('input', () => {
|
||||||
|
if (this.selectedType !== 'url') return;
|
||||||
|
const currentValue = contentField.value;
|
||||||
|
const sanitized = this.ensureUrlPrefix(currentValue);
|
||||||
|
if (sanitized !== currentValue) {
|
||||||
|
const caret = typeof contentField.selectionStart === 'number' ? contentField.selectionStart : sanitized.length;
|
||||||
|
const delta = sanitized.length - currentValue.length;
|
||||||
|
contentField.value = sanitized;
|
||||||
|
const protocolLength = this.getProtocolLength(sanitized);
|
||||||
|
const newPos = Math.max(protocolLength, caret + delta);
|
||||||
|
this.setCaretPosition(contentField, newPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isProtocolOnly(contentField.value.trim())) {
|
||||||
|
this.clearValidationError();
|
||||||
|
contentField.classList.remove('is-valid', 'is-invalid');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
prefillContentField(type, previousType = null) {
|
||||||
|
const contentField = document.getElementById('qr-content');
|
||||||
|
if (!contentField) return;
|
||||||
|
|
||||||
|
if (type === 'url') {
|
||||||
|
const returningToUrl = previousType === 'url';
|
||||||
|
const hasValue = !!contentField.value.trim();
|
||||||
|
|
||||||
|
if (!returningToUrl || !hasValue || this.isProtocolOnly(contentField.value.trim())) {
|
||||||
|
contentField.value = this.urlPrefix;
|
||||||
|
} else {
|
||||||
|
contentField.value = this.ensureUrlPrefix(contentField.value);
|
||||||
|
}
|
||||||
|
contentField.classList.remove('is-valid', 'is-invalid');
|
||||||
|
this.clearValidationError();
|
||||||
|
this.contentValid = false;
|
||||||
|
} else {
|
||||||
|
contentField.value = '';
|
||||||
|
contentField.classList.remove('is-valid', 'is-invalid');
|
||||||
|
this.clearValidationError();
|
||||||
|
this.contentValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateGenerateButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
isProtocolOnly(value) {
|
||||||
|
if (!value) return true;
|
||||||
|
const normalized = value.toString().trim().toLowerCase();
|
||||||
|
return normalized === 'https://' || normalized === 'http://';
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizeUrlInput(raw) {
|
||||||
|
if (!raw) {
|
||||||
|
return this.urlPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = raw.toString().trim();
|
||||||
|
if (!value) {
|
||||||
|
return this.urlPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
const protocolMatch = value.match(/^(https?:\/\/)/i);
|
||||||
|
let protocol = this.urlPrefix;
|
||||||
|
if (protocolMatch) {
|
||||||
|
protocol = protocolMatch[0].toLowerCase();
|
||||||
|
value = value.slice(protocolMatch[0].length);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = value.replace(/^(https?:\/\/)/i, '');
|
||||||
|
return protocol + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureUrlPrefix(value) {
|
||||||
|
if (!value) {
|
||||||
|
return this.urlPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
let working = value.toString().trimStart();
|
||||||
|
const protocolMatch = working.match(/^(https?:\/\/)/i);
|
||||||
|
let protocol = this.urlPrefix;
|
||||||
|
if (protocolMatch) {
|
||||||
|
protocol = protocolMatch[0].toLowerCase();
|
||||||
|
working = working.slice(protocolMatch[0].length);
|
||||||
|
}
|
||||||
|
|
||||||
|
working = working.replace(/^(https?:\/\/)/i, '');
|
||||||
|
return protocol + working;
|
||||||
|
}
|
||||||
|
|
||||||
|
getProtocolLength(value) {
|
||||||
|
if (!value) return this.urlPrefix.length;
|
||||||
|
if (value.startsWith('http://')) return 'http://'.length;
|
||||||
|
if (value.startsWith('https://')) return 'https://'.length;
|
||||||
|
return this.urlPrefix.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCaretPosition(input, position) {
|
||||||
|
if (!input || typeof input.setSelectionRange !== 'function') return;
|
||||||
|
const pos = Math.max(0, Math.min(position, input.value.length));
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
input.setSelectionRange(pos, pos);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setupDownloadButtons() {
|
setupDownloadButtons() {
|
||||||
@ -1597,6 +1736,7 @@ class QRRapidoGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTypeSelection(type) {
|
handleTypeSelection(type) {
|
||||||
|
const previousType = this.selectedType;
|
||||||
this.selectedType = type;
|
this.selectedType = type;
|
||||||
|
|
||||||
// Reset UX improvements flags for new type selection
|
// Reset UX improvements flags for new type selection
|
||||||
@ -1610,13 +1750,15 @@ class QRRapidoGenerator {
|
|||||||
this.removeInitialHighlight();
|
this.removeInitialHighlight();
|
||||||
// Sempre habilitar campos de conteúdo após selecionar tipo
|
// Sempre habilitar campos de conteúdo após selecionar tipo
|
||||||
this.enableContentFields(type);
|
this.enableContentFields(type);
|
||||||
|
this.prefillContentField(type, previousType);
|
||||||
// Show guidance toast for the selected type
|
// Show guidance toast for the selected type
|
||||||
this.showTypeGuidanceToast(type);
|
this.showTypeGuidanceToast(type);
|
||||||
} else {
|
} else {
|
||||||
this.disableAllFields();
|
this.disableAllFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateGenerateButton();
|
this.updateGenerateButton();
|
||||||
|
this.previousType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStyleSelection(style) {
|
handleStyleSelection(style) {
|
||||||
@ -1626,50 +1768,40 @@ class QRRapidoGenerator {
|
|||||||
|
|
||||||
handleContentChange(content) {
|
handleContentChange(content) {
|
||||||
const contentField = document.getElementById('qr-content');
|
const contentField = document.getElementById('qr-content');
|
||||||
this.contentValid = this.validateContent(content);
|
const trimmedContent = typeof content === 'string' ? content.trim() : '';
|
||||||
|
|
||||||
|
if (this.selectedType === 'url' && this.isProtocolOnly(trimmedContent)) {
|
||||||
|
this.contentValid = false;
|
||||||
|
} else {
|
||||||
|
this.contentValid = this.validateContent(content);
|
||||||
|
}
|
||||||
|
|
||||||
// Feedback visual para campo de conteúdo
|
// Feedback visual para campo de conteúdo
|
||||||
if (contentField) {
|
if (contentField) {
|
||||||
this.validateField(contentField, this.contentValid, window.QRRapidoTranslations?.validationContentMinLength || 'Content must have at least 3 characters');
|
if (this.selectedType === 'url') {
|
||||||
|
contentField.classList.remove('is-valid');
|
||||||
|
if (!trimmedContent || this.isProtocolOnly(trimmedContent)) {
|
||||||
|
contentField.classList.remove('is-invalid');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.validateField(contentField, this.contentValid, window.QRRapidoTranslations?.validationContentMinLength || 'Content must have at least 3 characters');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateGenerateButton();
|
this.updateGenerateButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
// UX Improvements - Intelligent delay system
|
// UX Improvements - Intelligent delay system
|
||||||
handleContentInputWithDelay(content) {
|
handleContentInputWithDelay(content) {
|
||||||
// Clear existing timer
|
// UX delay desabilitado para evitar correções automáticas tardias
|
||||||
if (this.contentDelayTimer) {
|
if (this.contentDelayTimer) {
|
||||||
clearTimeout(this.contentDelayTimer);
|
clearTimeout(this.contentDelayTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only proceed with delay logic if we have valid content and selected type
|
|
||||||
if (this.selectedType && this.validateContent(content) && !this.hasShownContentToast) {
|
|
||||||
this.contentDelayTimer = setTimeout(() => {
|
|
||||||
this.triggerContentReadyUX();
|
|
||||||
}, 7000); // 7 seconds delay after the initial 300ms debounce
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerContentReadyUX() {
|
triggerContentReadyUX() {
|
||||||
if (this.selectedType && this.contentValid && !this.hasShownContentToast) {
|
// Função mantida por compatibilidade, sem efeitos colaterais
|
||||||
// Mark as shown to prevent multiple toasts
|
return;
|
||||||
this.hasShownContentToast = true;
|
|
||||||
|
|
||||||
const contentField = document.getElementById('qr-content');
|
|
||||||
const originalValue = contentField.value.trim();
|
|
||||||
const fixedValue = this.autoFixURL(originalValue);
|
|
||||||
if (originalValue !== fixedValue) {
|
|
||||||
contentField.value = fixedValue;
|
|
||||||
this.updateGenerateButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show educational toast
|
|
||||||
this.showContentAddedToast();
|
|
||||||
|
|
||||||
// Update button state with ready indicator
|
|
||||||
this.updateGenerateButtonToReady();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showContentAddedToast() {
|
showContentAddedToast() {
|
||||||
@ -1995,12 +2127,12 @@ class QRRapidoGenerator {
|
|||||||
const contentField = document.getElementById('qr-content');
|
const contentField = document.getElementById('qr-content');
|
||||||
const content = contentField?.value || '';
|
const content = contentField?.value || '';
|
||||||
const trimmedContent = content.trim();
|
const trimmedContent = content.trim();
|
||||||
|
const protocolOnly = this.isProtocolOnly(trimmedContent);
|
||||||
|
|
||||||
if (!trimmedContent) {
|
if (!trimmedContent || protocolOnly) {
|
||||||
this.showValidationError('URL é obrigatória');
|
this.clearValidationError();
|
||||||
if (contentField) {
|
if (contentField) {
|
||||||
contentField.classList.remove('is-valid');
|
contentField.classList.remove('is-valid', 'is-invalid');
|
||||||
contentField.classList.add('is-invalid');
|
|
||||||
}
|
}
|
||||||
isValid = false;
|
isValid = false;
|
||||||
} else if (!this.isValidURL(trimmedContent)) {
|
} else if (!this.isValidURL(trimmedContent)) {
|
||||||
@ -3508,4 +3640,4 @@ window.trackUpgradeClick = function(location) {
|
|||||||
'click_location': location
|
'click_location': location
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
class SimpleOpacityController {
|
class SimpleOpacityController {
|
||||||
constructor(controlSelector, targetSelector) {
|
constructor(controlSelector, targetSelector) {
|
||||||
this.controlSelector = controlSelector;
|
this.controlSelector = controlSelector;
|
||||||
this.targetSelector = targetSelector;
|
this.targetSelector = targetSelector;
|
||||||
@ -61,3 +61,6 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.SimpleOpacityController = SimpleOpacityController;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user