feat: modularização
This commit is contained in:
parent
6085f1b117
commit
6b79a44e39
@ -20,5 +20,10 @@ namespace OnlyOneAccessTemplate.Models
|
|||||||
|
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public string? JavaScriptUrl { get; set; } // URL do arquivo JS
|
||||||
|
public string? JavaScriptFunction { get; set; } // Função de inicialização
|
||||||
|
public string? CssUrl { get; set; } // CSS opcional
|
||||||
|
public Dictionary<string, string> Assets { get; set; } = new(); // Assets adicionais
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,6 +49,8 @@
|
|||||||
<script>@Html.Raw(ViewBag.ConversionPixel)</script>
|
<script>@Html.Raw(ViewBag.ConversionPixel)</script>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<script src="~/js/module-loader.js"></script>
|
||||||
|
<script>
|
||||||
<script>
|
<script>
|
||||||
window.ModuleSystem = {
|
window.ModuleSystem = {
|
||||||
async loadModule(moduleId, targetElementId) {
|
async loadModule(moduleId, targetElementId) {
|
||||||
@ -90,8 +92,14 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Auto-executar quando página carrega
|
// Auto-executar quando página carrega
|
||||||
|
<!--SUBSTITUIR o script antigo do ModuleSystem por: -->
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
window.ModuleSystem.loadModules();
|
console.log('🚀 ModuleLoader v2.0.0 inicializado');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listener para módulos carregados
|
||||||
|
document.addEventListener('moduleLoaded', function (event) {
|
||||||
|
console.log('✅ Módulo carregado:', event.detail);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
269
OnlyOneAccessTemplate/wwwroot/js/module-loader.js
Normal file
269
OnlyOneAccessTemplate/wwwroot/js/module-loader.js
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
// Dynamic Module Loader System
|
||||||
|
window.ModuleLoader = (function () {
|
||||||
|
const loadedScripts = new Set();
|
||||||
|
const loadedStyles = new Set();
|
||||||
|
const moduleInstances = new Map();
|
||||||
|
|
||||||
|
// Cache de recursos carregados
|
||||||
|
const resourceCache = new Map();
|
||||||
|
|
||||||
|
function log(message, ...args) {
|
||||||
|
console.log(`🔧 ModuleLoader: ${message}`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(message, ...args) {
|
||||||
|
console.error(`❌ ModuleLoader: ${message}`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadScript(url, moduleBaseUrl) {
|
||||||
|
const fullUrl = resolveUrl(url, moduleBaseUrl);
|
||||||
|
|
||||||
|
if (loadedScripts.has(fullUrl)) {
|
||||||
|
log(`Script já carregado: ${fullUrl}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
log(`Carregando script: ${fullUrl}`);
|
||||||
|
|
||||||
|
const response = await fetch(fullUrl);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptContent = await response.text();
|
||||||
|
|
||||||
|
// Criar script element e executar
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.textContent = scriptContent;
|
||||||
|
script.setAttribute('data-module-script', fullUrl);
|
||||||
|
document.head.appendChild(script);
|
||||||
|
|
||||||
|
loadedScripts.add(fullUrl);
|
||||||
|
log(`✅ Script carregado com sucesso: ${fullUrl}`);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
error(`Falha ao carregar script ${fullUrl}:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadStyle(url, moduleBaseUrl) {
|
||||||
|
const fullUrl = resolveUrl(url, moduleBaseUrl);
|
||||||
|
|
||||||
|
if (loadedStyles.has(fullUrl)) {
|
||||||
|
log(`CSS já carregado: ${fullUrl}`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
log(`Carregando CSS: ${fullUrl}`);
|
||||||
|
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.href = fullUrl;
|
||||||
|
link.setAttribute('data-module-style', fullUrl);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
link.onload = () => {
|
||||||
|
loadedStyles.add(fullUrl);
|
||||||
|
log(`✅ CSS carregado com sucesso: ${fullUrl}`);
|
||||||
|
resolve(true);
|
||||||
|
};
|
||||||
|
link.onerror = () => {
|
||||||
|
error(`Falha ao carregar CSS: ${fullUrl}`);
|
||||||
|
reject(false);
|
||||||
|
};
|
||||||
|
document.head.appendChild(link);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
error(`Erro ao carregar CSS ${fullUrl}:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveUrl(url, baseUrl) {
|
||||||
|
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.startsWith('/')) {
|
||||||
|
const base = new URL(baseUrl);
|
||||||
|
return `${base.protocol}//${base.host}${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new URL(url, baseUrl).href;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractModuleMetadata(container) {
|
||||||
|
const metadataScript = container.querySelector('#module-metadata');
|
||||||
|
if (!metadataScript) {
|
||||||
|
log('Nenhum metadata encontrado no módulo');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const metadata = JSON.parse(metadataScript.textContent);
|
||||||
|
log('Metadata extraído:', metadata);
|
||||||
|
return metadata;
|
||||||
|
} catch (err) {
|
||||||
|
error('Erro ao parsear metadata:', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModuleBaseUrl(moduleUrl) {
|
||||||
|
try {
|
||||||
|
const url = new URL(moduleUrl, window.location.href);
|
||||||
|
const pathParts = url.pathname.split('/');
|
||||||
|
pathParts.pop(); // Remove o último segmento (nome do endpoint)
|
||||||
|
url.pathname = pathParts.join('/');
|
||||||
|
return url.href;
|
||||||
|
} catch (err) {
|
||||||
|
error('Erro ao extrair base URL:', err);
|
||||||
|
return window.location.origin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeModule(containerId, moduleUrl, metadata) {
|
||||||
|
const moduleBaseUrl = getModuleBaseUrl(moduleUrl);
|
||||||
|
log(`Inicializando módulo em ${containerId} com base URL: ${moduleBaseUrl}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Carregar CSS se especificado
|
||||||
|
if (metadata.cssUrl) {
|
||||||
|
await loadStyle(metadata.cssUrl, moduleBaseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carregar JavaScript
|
||||||
|
if (metadata.jsUrl) {
|
||||||
|
const scriptLoaded = await loadScript(metadata.jsUrl, moduleBaseUrl);
|
||||||
|
if (!scriptLoaded) {
|
||||||
|
throw new Error('Falha ao carregar script principal');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aguardar um momento para o script ser executado
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Chamar função de inicialização
|
||||||
|
if (metadata.jsFunction) {
|
||||||
|
const functionPath = metadata.jsFunction.split('.');
|
||||||
|
let func = window;
|
||||||
|
|
||||||
|
for (const part of functionPath) {
|
||||||
|
func = func[part];
|
||||||
|
if (!func) {
|
||||||
|
throw new Error(`Função ${metadata.jsFunction} não encontrada`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof func === 'function') {
|
||||||
|
log(`Chamando função de inicialização: ${metadata.jsFunction}`);
|
||||||
|
const result = func(containerId);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
moduleInstances.set(containerId, {
|
||||||
|
metadata,
|
||||||
|
moduleUrl,
|
||||||
|
moduleBaseUrl,
|
||||||
|
initialized: true
|
||||||
|
});
|
||||||
|
log(`✅ Módulo ${metadata.moduleId} inicializado com sucesso`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
error(`Erro ao inicializar módulo:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadModule(moduleId, containerId, moduleUrl) {
|
||||||
|
log(`Carregando módulo ${moduleId} em ${containerId} de ${moduleUrl}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Carregar HTML do módulo
|
||||||
|
const response = await fetch(moduleUrl);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await response.text();
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
throw new Error(`Container ${containerId} não encontrado`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserir HTML
|
||||||
|
container.innerHTML = html;
|
||||||
|
|
||||||
|
// Extrair metadata
|
||||||
|
const metadata = extractModuleMetadata(container);
|
||||||
|
if (!metadata) {
|
||||||
|
log('⚠️ Módulo sem metadata, tentando funcionar sem JavaScript');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializar módulo
|
||||||
|
const success = await initializeModule(containerId, moduleUrl, metadata);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// Disparar evento
|
||||||
|
const event = new CustomEvent('moduleLoaded', {
|
||||||
|
detail: {
|
||||||
|
moduleId: metadata.moduleId,
|
||||||
|
containerId,
|
||||||
|
moduleUrl,
|
||||||
|
metadata
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
error(`Erro ao carregar módulo ${moduleId}:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModuleInfo(containerId) {
|
||||||
|
return moduleInstances.get(containerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unloadModule(containerId) {
|
||||||
|
const info = moduleInstances.get(containerId);
|
||||||
|
if (info) {
|
||||||
|
// Limpar container
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleInstances.delete(containerId);
|
||||||
|
log(`Módulo removido: ${containerId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API pública
|
||||||
|
return {
|
||||||
|
loadModule,
|
||||||
|
getModuleInfo,
|
||||||
|
unloadModule,
|
||||||
|
version: '2.0.0'
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Compatibilidade com sistema antigo
|
||||||
|
window.ModuleSystem = {
|
||||||
|
loadModule: window.ModuleLoader.loadModule,
|
||||||
|
version: '2.0.0-compat'
|
||||||
|
};
|
||||||
@ -1,12 +1,99 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using SentenceConverterModule.Services.Contracts;
|
||||||
|
|
||||||
namespace SentenceConverterModule.Controllers
|
namespace SentenceConverterModule.Controllers
|
||||||
{
|
{
|
||||||
public class HomeController : Controller
|
public class HomeController : Controller
|
||||||
{
|
{
|
||||||
public IActionResult Index()
|
private readonly ISentenceConverterService _converterService;
|
||||||
|
private readonly ITextConversionApiService _apiService;
|
||||||
|
|
||||||
|
public HomeController(
|
||||||
|
ISentenceConverterService converterService,
|
||||||
|
ITextConversionApiService apiService)
|
||||||
|
{
|
||||||
|
_converterService = converterService;
|
||||||
|
_apiService = apiService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult Index(string language = "pt")
|
||||||
|
{
|
||||||
|
ViewBag.Language = language;
|
||||||
|
ViewBag.Config = _converterService.GetConfiguration(language);
|
||||||
|
|
||||||
|
// Dados para a página de teste
|
||||||
|
ViewBag.PageTitle = language switch
|
||||||
|
{
|
||||||
|
"en" => "Sentence Case Converter - Standalone Test",
|
||||||
|
"es" => "Convertidor a Mayúscula Inicial - Prueba Independiente",
|
||||||
|
_ => "Conversor para Primeira Maiúscula - Teste Standalone"
|
||||||
|
};
|
||||||
|
|
||||||
|
ViewBag.PageDescription = language switch
|
||||||
|
{
|
||||||
|
"en" => "Test the sentence case converter module independently",
|
||||||
|
"es" => "Prueba el módulo convertidor independientemente",
|
||||||
|
_ => "Teste o módulo conversor independentemente"
|
||||||
|
};
|
||||||
|
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> HealthCheck()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var isApiHealthy = await _apiService.IsHealthyAsync();
|
||||||
|
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
status = "healthy",
|
||||||
|
service = "sentence-converter-module",
|
||||||
|
apiHealth = isApiHealthy,
|
||||||
|
timestamp = DateTime.UtcNow,
|
||||||
|
version = "1.0.0"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Json(new
|
||||||
|
{
|
||||||
|
status = "unhealthy",
|
||||||
|
error = ex.Message,
|
||||||
|
timestamp = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult TestEndpoints()
|
||||||
|
{
|
||||||
|
var baseUrl = $"{Request.Scheme}://{Request.Host}";
|
||||||
|
|
||||||
|
var endpoints = new
|
||||||
|
{
|
||||||
|
module_widget = $"{baseUrl}/modules/sentence-converter",
|
||||||
|
footer_message = $"{baseUrl}/modules/footer-message",
|
||||||
|
api_convert = $"{baseUrl}/api/converter/convert",
|
||||||
|
api_config = $"{baseUrl}/api/converter/config",
|
||||||
|
api_health = $"{baseUrl}/api/converter/health",
|
||||||
|
home_health = $"{baseUrl}/home/healthcheck"
|
||||||
|
};
|
||||||
|
|
||||||
|
return Json(endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult Privacy()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
|
public IActionResult Error()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
UpperFirstLetter/Models/ModuleConfig.cs
Normal file
25
UpperFirstLetter/Models/ModuleConfig.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
namespace OnlyOneAccessTemplate.Models
|
||||||
|
{
|
||||||
|
public class ModuleConfig
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string ModuleId { get; set; } = string.Empty; // Ex: "footer-message"
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Url { get; set; } = string.Empty; // URL do endpoint externo
|
||||||
|
public string RequestBy { get; set; } = string.Empty; // Identificador único
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
public Dictionary<string, string> Headers { get; set; } = new();
|
||||||
|
public Dictionary<string, object> Parameters { get; set; } = new();
|
||||||
|
public int CacheMinutes { get; set; } = 5; // Cache do conteúdo
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public string? JavaScriptUrl { get; set; } // URL do arquivo JS
|
||||||
|
public string? JavaScriptFunction { get; set; } // Função de inicialização
|
||||||
|
public string? CssUrl { get; set; } // CSS opcional
|
||||||
|
public Dictionary<string, string> Assets { get; set; } = new(); // Assets adicionais
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +0,0 @@
|
|||||||
using SentenceConverterModule.Models;
|
|
||||||
|
|
||||||
namespace SentenceConverterModule.Services.Contracts
|
|
||||||
{
|
|
||||||
public interface IConverterService
|
|
||||||
{
|
|
||||||
string ConverterType { get; }
|
|
||||||
string ConverterName { get; }
|
|
||||||
Task<ConversionResult> ConvertAsync(ConversionRequest request);
|
|
||||||
Task<bool> ValidateInputAsync(ConversionRequest request);
|
|
||||||
ConverterConfiguration GetConfiguration(string language);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -146,55 +146,55 @@ namespace SentenceConverterModule.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Novo serviço que substitui o UpperLowerConversorService usando a API
|
// Novo serviço que substitui o UpperLowerConversorService usando a API
|
||||||
public class ApiBasedSentenceConverterService : BaseConverterService
|
//public class ApiBasedSentenceConverterService : BaseConverterService
|
||||||
{
|
//{
|
||||||
private readonly ITextConversionApiService _apiService;
|
// private readonly ITextConversionApiService _apiService;
|
||||||
|
|
||||||
public override string ConverterType => "text-case-sentence";
|
// public override string ConverterType => "text-case-sentence";
|
||||||
public override string ConverterName => "Maiúsculas para minúsculas";
|
// public override string ConverterName => "Maiúsculas para minúsculas";
|
||||||
|
|
||||||
public ApiBasedSentenceConverterService(
|
// public ApiBasedSentenceConverterService(
|
||||||
ILogger<ApiBasedSentenceConverterService> logger,
|
// ILogger<ApiBasedSentenceConverterService> logger,
|
||||||
IConfiguration configuration,
|
// IConfiguration configuration,
|
||||||
ITextConversionApiService apiService)
|
// ITextConversionApiService apiService)
|
||||||
: base(logger, configuration)
|
// : base(logger, configuration)
|
||||||
{
|
// {
|
||||||
_apiService = apiService;
|
// _apiService = apiService;
|
||||||
}
|
// }
|
||||||
|
|
||||||
public override async Task<ConversionResult> ConvertAsync(ConversionRequest request)
|
// public override async Task<ConversionResult> ConvertAsync(ConversionRequest request)
|
||||||
{
|
// {
|
||||||
try
|
// try
|
||||||
{
|
// {
|
||||||
var resultado = await _apiService.ConvertToSentenceCaseAsync(request.TextInput);
|
// var resultado = await _apiService.ConvertToSentenceCaseAsync(request.TextInput);
|
||||||
return new ConversionResult(true, OutputText: resultado);
|
// return new ConversionResult(true, OutputText: resultado);
|
||||||
}
|
// }
|
||||||
catch (Exception ex)
|
// catch (Exception ex)
|
||||||
{
|
// {
|
||||||
_logger.LogError(ex, "Erro na conversão via API");
|
// _logger.LogError(ex, "Erro na conversão via API");
|
||||||
return new ConversionResult(false, ErrorMessage: "Erro ao processar via API");
|
// return new ConversionResult(false, ErrorMessage: "Erro ao processar via API");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
public override ConverterConfiguration GetConfiguration(string language)
|
// public override ConverterConfiguration GetConfiguration(string language)
|
||||||
{
|
// {
|
||||||
var texts = GetLocalizedTexts(language);
|
// var texts = GetLocalizedTexts(language);
|
||||||
|
|
||||||
texts["ConverterTitle"] = language switch
|
// texts["ConverterTitle"] = language switch
|
||||||
{
|
// {
|
||||||
"en" => "Sentence Case Convert",
|
// "en" => "Sentence Case Convert",
|
||||||
"es" => "Convertir a Mayúscula Inicial",
|
// "es" => "Convertir a Mayúscula Inicial",
|
||||||
_ => "Converter para primeira maiúscula"
|
// _ => "Converter para primeira maiúscula"
|
||||||
};
|
// };
|
||||||
|
|
||||||
return new ConverterConfiguration
|
// return new ConverterConfiguration
|
||||||
{
|
// {
|
||||||
ConverterType = "text",
|
// ConverterType = "text",
|
||||||
OutputType = "text",
|
// OutputType = "text",
|
||||||
HasAdvancedOptions = false,
|
// HasAdvancedOptions = false,
|
||||||
AllowShare = true,
|
// AllowShare = true,
|
||||||
LocalizedTexts = texts
|
// LocalizedTexts = texts
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
@ -1,8 +1,405 @@
|
|||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Home Page";
|
ViewData["Title"] = "Home";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="text-center">
|
<!-- Hero Section -->
|
||||||
<h1 class="display-4">Welcome</h1>
|
<section class="hero-section">
|
||||||
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
|
<div class="container">
|
||||||
</div>
|
<div class="row align-items-center">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h1 class="display-4 fw-bold mb-4">
|
||||||
|
@ViewBag.Config.LocalizedTexts["ConverterTitle"]
|
||||||
|
</h1>
|
||||||
|
<p class="lead mb-4">
|
||||||
|
@ViewBag.Config.LocalizedTexts["ConverterDescription"]
|
||||||
|
</p>
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<span class="badge bg-light text-dark fs-6">
|
||||||
|
<i class="fas fa-code me-1"></i> Standalone
|
||||||
|
</span>
|
||||||
|
<span class="badge bg-light text-dark fs-6">
|
||||||
|
<i class="fas fa-globe me-1"></i> Multi-idioma
|
||||||
|
</span>
|
||||||
|
<span class="badge bg-light text-dark fs-6">
|
||||||
|
<i class="fas fa-bolt me-1"></i> API Ready
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 text-center">
|
||||||
|
<i class="fas fa-text-height fa-5x opacity-75"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Módulo Demo -->
|
||||||
|
<section class="py-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-10">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h2>🧪 Teste do Módulo</h2>
|
||||||
|
<p class="text-muted">Este é o módulo funcionando independentemente</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="module-demo">
|
||||||
|
<div id="sentence-converter-demo">
|
||||||
|
<!-- O módulo será carregado aqui -->
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Carregando...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-3">Carregando módulo...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Test Section -->
|
||||||
|
<section class="test-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<h3 class="text-center mb-5">🔗 Endpoints Disponíveis</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" id="endpoints-container">
|
||||||
|
<!-- Endpoints serão carregados via JavaScript -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SUBSTITUIR os cards existentes por: -->
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card endpoint-card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
<i class="fas fa-puzzle-piece text-primary me-2"></i>
|
||||||
|
Widget Dinâmico
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">Módulo carregado com o novo sistema de loading dinâmico</p>
|
||||||
|
<button class="btn btn-primary" onclick="loadWidgetManually()">
|
||||||
|
<i class="fas fa-sync me-1"></i> Recarregar Widget
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card endpoint-card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
<i class="fas fa-code text-success me-2"></i>
|
||||||
|
Teste de API
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">Teste direto da API de conversão com resultado visual</p>
|
||||||
|
<button class="btn btn-success" onclick="testApi()">
|
||||||
|
<i class="fas fa-play me-1"></i> Testar API
|
||||||
|
</button>
|
||||||
|
<div id="api-result" class="mt-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card endpoint-card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
<i class="fas fa-cogs text-info me-2"></i>
|
||||||
|
Sistema de Módulos
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">Informações sobre o sistema de carregamento dinâmico</p>
|
||||||
|
<button class="btn btn-info" onclick="showSystemInfo()">
|
||||||
|
<i class="fas fa-info-circle me-1"></i> Ver Detalhes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// Auto-carregar o widget usando o novo sistema
|
||||||
|
loadFullWidgetDynamic();
|
||||||
|
loadEndpoints();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadFullWidgetDynamic() {
|
||||||
|
const containerId = 'sentence-converter-demo';
|
||||||
|
const endpoint = `/modules/sentence-converter?language=@ViewBag.Language`;
|
||||||
|
|
||||||
|
console.log('🚀 Carregando widget usando sistema dinâmico...');
|
||||||
|
|
||||||
|
const success = await window.loadTestModule(containerId, endpoint);
|
||||||
|
if (success) {
|
||||||
|
console.log('✅ Widget carregado com sistema dinâmico!');
|
||||||
|
} else {
|
||||||
|
console.error('❌ Falha ao carregar widget dinamicamente');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadEndpoints() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/home/testendpoints');
|
||||||
|
const endpoints = await response.json();
|
||||||
|
|
||||||
|
const container = document.getElementById('endpoints-container');
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
Object.entries(endpoints).forEach(([name, url]) => {
|
||||||
|
const displayName = name.replace(/_/g, ' ').toUpperCase();
|
||||||
|
const isModuleEndpoint = name.includes('module');
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div class="col-md-6 col-lg-4 mb-3">
|
||||||
|
<div class="card endpoint-card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title">
|
||||||
|
${isModuleEndpoint ? '<i class="fas fa-puzzle-piece text-primary me-1"></i>' : ''}
|
||||||
|
${displayName}
|
||||||
|
</h6>
|
||||||
|
<small class="text-muted">${url}</small>
|
||||||
|
<div class="mt-2">
|
||||||
|
${isModuleEndpoint ?
|
||||||
|
`<button class="btn btn-sm btn-primary" onclick="testModuleLoad('${url}')">
|
||||||
|
<i class="fas fa-play me-1"></i> Carregar
|
||||||
|
</button>` :
|
||||||
|
`<button class="btn btn-sm btn-outline-primary" onclick="testEndpoint('${url}')">
|
||||||
|
Testar
|
||||||
|
</button>`
|
||||||
|
}
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="copyUrl('${url}')">
|
||||||
|
Copiar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao carregar endpoints:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testModuleLoad(url) {
|
||||||
|
// Criar container temporário para teste
|
||||||
|
const testId = 'test-module-' + Date.now();
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'modal fade';
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="fas fa-vial me-2"></i>Teste de Carregamento do Módulo
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p><strong>URL:</strong> <code>${url}</code></p>
|
||||||
|
<div id="${testId}" class="border rounded p-3 bg-light">
|
||||||
|
<!-- Módulo será carregado aqui -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fechar</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="window.loadTestModule('${testId}', '${url}')">
|
||||||
|
<i class="fas fa-redo me-1"></i> Recarregar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
const bsModal = new bootstrap.Modal(modal);
|
||||||
|
|
||||||
|
// Limpar modal quando fechado
|
||||||
|
modal.addEventListener('hidden.bs.modal', () => {
|
||||||
|
document.body.removeChild(modal);
|
||||||
|
});
|
||||||
|
|
||||||
|
bsModal.show();
|
||||||
|
|
||||||
|
// Carregar módulo no modal
|
||||||
|
setTimeout(() => {
|
||||||
|
window.loadTestModule(testId, url);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testEndpoint(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const result = await response.text();
|
||||||
|
|
||||||
|
// Abrir resultado em nova janela
|
||||||
|
const newWindow = window.open('', '_blank');
|
||||||
|
newWindow.document.write(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Resultado do Teste</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="p-4">
|
||||||
|
<div class="container">
|
||||||
|
<h3>Teste de Endpoint</h3>
|
||||||
|
<p><strong>URL:</strong> <code>${url}</code></p>
|
||||||
|
<p><strong>Status:</strong> <span class="badge bg-${response.ok ? 'success' : 'danger'}">${response.status}</span></p>
|
||||||
|
<hr>
|
||||||
|
<h5>Resposta:</h5>
|
||||||
|
<pre class="bg-light p-3 rounded"><code>${result}</code></pre>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
} catch (error) {
|
||||||
|
alert('Erro ao testar endpoint: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testApi() {
|
||||||
|
const resultDiv = document.getElementById('api-result');
|
||||||
|
resultDiv.innerHTML = '<div class="spinner-border spinner-border-sm" role="status"></div> Testando API...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('TextInput', 'este é um teste. vamos ver se funciona!');
|
||||||
|
formData.append('Language', '@ViewBag.Language');
|
||||||
|
|
||||||
|
const response = await fetch('/api/converter/convert', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
resultDiv.innerHTML = `
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<h6><i class="fas fa-check-circle me-2"></i>Sucesso!</h6>
|
||||||
|
<p class="mb-1"><strong>Entrada:</strong> este é um teste. vamos ver se funciona!</p>
|
||||||
|
<p class="mb-0"><strong>Saída:</strong> <em>${result.outputText}</em></p>
|
||||||
|
</div>`;
|
||||||
|
} else {
|
||||||
|
resultDiv.innerHTML = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<h6><i class="fas fa-times-circle me-2"></i>Erro</h6>
|
||||||
|
<p class="mb-0">${result.message}</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
resultDiv.innerHTML = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<h6><i class="fas fa-exclamation-triangle me-2"></i>Erro de Conexão</h6>
|
||||||
|
<p class="mb-0">${error.message}</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyUrl(url) {
|
||||||
|
navigator.clipboard.writeText(url).then(() => {
|
||||||
|
// Feedback visual
|
||||||
|
const button = event.target;
|
||||||
|
const originalText = button.textContent;
|
||||||
|
const originalClass = button.className;
|
||||||
|
|
||||||
|
button.textContent = 'Copiado!';
|
||||||
|
button.className = button.className.replace('btn-outline-secondary', 'btn-success');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.textContent = originalText;
|
||||||
|
button.className = originalClass;
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para demonstrar carregamento manual
|
||||||
|
function loadWidgetManually() {
|
||||||
|
const endpoint = `/modules/sentence-converter?language=@ViewBag.Language`;
|
||||||
|
window.loadTestModule('sentence-converter-demo', endpoint);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function showSystemInfo() {
|
||||||
|
const info = {
|
||||||
|
moduleLoader: !!window.ModuleLoaderLite,
|
||||||
|
version: window.ModuleLoaderLite?.version || 'N/A',
|
||||||
|
loadedModules: document.querySelectorAll('[data-module-id]').length,
|
||||||
|
loadedScripts: document.querySelectorAll('[data-test-script]').length,
|
||||||
|
loadedStyles: document.querySelectorAll('[data-test-style]').length
|
||||||
|
};
|
||||||
|
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'modal fade';
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-info text-white">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>Informações do Sistema
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-sm-6">Module Loader:</dt>
|
||||||
|
<dd class="col-sm-6">
|
||||||
|
<span class="badge bg-${info.moduleLoader ? 'success' : 'danger'}">
|
||||||
|
${info.moduleLoader ? 'Ativo' : 'Inativo'}
|
||||||
|
</span>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-6">Versão:</dt>
|
||||||
|
<dd class="col-sm-6"><code>${info.version}</code></dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-6">Módulos Carregados:</dt>
|
||||||
|
<dd class="col-sm-6"><span class="badge bg-primary">${info.loadedModules}</span></dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-6">Scripts Dinâmicos:</dt>
|
||||||
|
<dd class="col-sm-6"><span class="badge bg-secondary">${info.loadedScripts}</span></dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-6">Estilos Dinâmicos:</dt>
|
||||||
|
<dd class="col-sm-6"><span class="badge bg-secondary">${info.loadedStyles}</span></dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h6>Recursos Carregados:</h6>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<small class="text-muted">Scripts:</small>
|
||||||
|
<ul class="list-unstyled small">
|
||||||
|
${Array.from(document.querySelectorAll('[data-test-script]')).map(s =>
|
||||||
|
`<li><code>${s.getAttribute('data-test-script')}</code></li>`
|
||||||
|
).join('') || '<li class="text-muted">Nenhum script dinâmico carregado</li>'}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fechar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
const bsModal = new bootstrap.Modal(modal);
|
||||||
|
|
||||||
|
modal.addEventListener('hidden.bs.modal', () => {
|
||||||
|
document.body.removeChild(modal);
|
||||||
|
});
|
||||||
|
|
||||||
|
bsModal.show();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Privacy Policy";
|
ViewData["Title"] = "Privacy Policy";
|
||||||
}
|
}
|
||||||
<h1>@ViewData["Title"]</h1>
|
<div class="container py-5">
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
<p>Use this page to detail your site's privacy policy.</p>
|
<p>Esta é uma página de teste para o módulo Sentence Converter.</p>
|
||||||
|
<p>Use este ambiente para testar funcionalidades antes de integrar com o projeto principal.</p>
|
||||||
|
</div>
|
||||||
@ -1,25 +1,16 @@
|
|||||||
@model ErrorViewModel
|
@model string
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Error";
|
ViewData["Title"] = "Error";
|
||||||
}
|
}
|
||||||
|
|
||||||
<h1 class="text-danger">Error.</h1>
|
<div class="container py-5">
|
||||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
<h1 class="text-danger">Error.</h1>
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
|
|
||||||
@if (Model.ShowRequestId)
|
@if (!string.IsNullOrEmpty(Model))
|
||||||
{
|
{
|
||||||
<p>
|
<p>
|
||||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
<strong>Error details:</strong> @Model
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
<h3>Development Mode</h3>
|
|
||||||
<p>
|
|
||||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
|
||||||
It can result in displaying sensitive information from exceptions to end users.
|
|
||||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
|
||||||
and restarting the app.
|
|
||||||
</p>
|
|
||||||
@ -1,4 +1,31 @@
|
|||||||
<div class="sentence-converter-widget" data-converter-id="sentence-converter">
|
<!-- Metadados do Módulo -->
|
||||||
|
@* <script type="application/json" id="module-metadata">
|
||||||
|
{
|
||||||
|
"moduleId": "sentence-converter",
|
||||||
|
"jsUrl": "/js/sentence-converter-widget.js",
|
||||||
|
"jsFunction": "SentenceConverterWidget.init",
|
||||||
|
//"cssUrl": "/css/sentence-converter-widget.css",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": []
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
*@
|
||||||
|
|
||||||
|
<script type="application/json" id="module-metadata">
|
||||||
|
{
|
||||||
|
"moduleId": "sentence-converter",
|
||||||
|
"jsUrl": "/js/sentence-converter-widget.js",
|
||||||
|
"jsFunction": "SentenceConverterWidget.init",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": []
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Widget HTML -->
|
||||||
|
<div class="sentence-converter-widget"
|
||||||
|
data-converter-id="sentence-converter"
|
||||||
|
data-module-id="sentence-converter">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label fw-bold">@ViewBag.Config.LocalizedTexts["InputPlaceholder"]</label>
|
<label class="form-label fw-bold">@ViewBag.Config.LocalizedTexts["InputPlaceholder"]</label>
|
||||||
@ -38,90 +65,29 @@
|
|||||||
<div id="conversionStatus" class="mt-3"></div>
|
<div id="conversionStatus" class="mt-3"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<!-- Configuração para o JavaScript -->
|
||||||
(function () {
|
<script type="application/json" id="widget-config">
|
||||||
const widget = document.querySelector('[data-converter-id="sentence-converter"]');
|
{
|
||||||
if (!widget) return;
|
"language": "@ViewBag.Language",
|
||||||
|
"convertButtonText": "@ViewBag.Config.LocalizedTexts["ConvertButton"]",
|
||||||
const inputText = widget.querySelector('#inputText');
|
"labels": {
|
||||||
const outputText = widget.querySelector('#outputText');
|
"inputPlaceholder": "@ViewBag.Config.LocalizedTexts["InputPlaceholder"]",
|
||||||
const convertBtn = widget.querySelector('#convertBtn');
|
"outputLabel": "@ViewBag.Config.LocalizedTexts["OutputLabel"]",
|
||||||
const copyBtn = widget.querySelector('#copyBtn');
|
"convertButton": "@ViewBag.Config.LocalizedTexts["ConvertButton"]",
|
||||||
const clearBtn = widget.querySelector('#clearBtn');
|
"copyButton": "@ViewBag.Config.LocalizedTexts["CopyButton"]",
|
||||||
const charCount = widget.querySelector('#charCount');
|
"clearButton": "@ViewBag.Config.LocalizedTexts["ClearButton"]"
|
||||||
const status = widget.querySelector('#conversionStatus');
|
},
|
||||||
|
"messages": {
|
||||||
// Contador de caracteres
|
"enterText": "Por favor, digite algum texto.",
|
||||||
inputText.addEventListener('input', function () {
|
"converting": "Convertendo...",
|
||||||
charCount.textContent = this.value.length;
|
"success": "Conversão realizada com sucesso!",
|
||||||
});
|
"copied": "Texto copiado para a área de transferência!",
|
||||||
|
"connectionError": "Erro de conexão. Tente novamente.",
|
||||||
// Converter texto
|
"copyError": "Erro ao copiar texto."
|
||||||
convertBtn.addEventListener('click', async function () {
|
},
|
||||||
const text = inputText.value.trim();
|
"endpoints": {
|
||||||
if (!text) {
|
"convert": "/api/converter/convert",
|
||||||
showStatus('Por favor, digite algum texto.', 'warning');
|
"health": "/api/converter/health"
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
convertBtn.disabled = true;
|
|
||||||
convertBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Convertendo...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('TextInput', text);
|
|
||||||
formData.append('Language', '@ViewBag.Language');
|
|
||||||
|
|
||||||
const response = await fetch('/api/converter/convert', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
outputText.value = result.outputText;
|
|
||||||
copyBtn.disabled = false;
|
|
||||||
showStatus('Conversão realizada com sucesso!', 'success');
|
|
||||||
} else {
|
|
||||||
showStatus('Erro: ' + result.message, 'danger');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
showStatus('Erro de conexão. Tente novamente.', 'danger');
|
|
||||||
} finally {
|
|
||||||
convertBtn.disabled = false;
|
|
||||||
convertBtn.innerHTML = '<i class="fas fa-exchange-alt me-2"></i>@ViewBag.Config.LocalizedTexts["ConvertButton"]';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Copiar resultado
|
|
||||||
copyBtn.addEventListener('click', async function () {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(outputText.value);
|
|
||||||
showStatus('Texto copiado para a área de transferência!', 'success');
|
|
||||||
} catch (error) {
|
|
||||||
showStatus('Erro ao copiar texto.', 'danger');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Limpar campos
|
|
||||||
clearBtn.addEventListener('click', function () {
|
|
||||||
inputText.value = '';
|
|
||||||
outputText.value = '';
|
|
||||||
copyBtn.disabled = true;
|
|
||||||
charCount.textContent = '0';
|
|
||||||
status.innerHTML = '';
|
|
||||||
});
|
|
||||||
|
|
||||||
function showStatus(message, type) {
|
|
||||||
status.innerHTML = `<div class="alert alert-${type} alert-dismissible fade show" role="alert">
|
|
||||||
${message}
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
status.innerHTML = '';
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
@ -1,49 +1,244 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="@ViewBag.Language">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>@ViewData["Title"] - UpperFirstLetter</title>
|
<title>@(ViewBag.PageTitle ?? "Sentence Converter Module")</title>
|
||||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
|
||||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
<!-- Bootstrap CSS -->
|
||||||
<link rel="stylesheet" href="~/UpperFirstLetter.styles.css" asp-append-version="true" />
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<!-- Font Awesome -->
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hero-section {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 60px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-section {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-card {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.endpoint-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-healthy {
|
||||||
|
background-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-unhealthy {
|
||||||
|
background-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-unknown {
|
||||||
|
background-color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-demo {
|
||||||
|
border: 2px dashed #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
<div class="container">
|
||||||
<div class="container-fluid">
|
<a class="navbar-brand" href="/">
|
||||||
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">UpperFirstLetter</a>
|
<i class="fas fa-text-height me-2"></i>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
Sentence Converter
|
||||||
aria-expanded="false" aria-label="Toggle navigation">
|
</a>
|
||||||
|
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
|
|
||||||
<ul class="navbar-nav flex-grow-1">
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
|
<a class="nav-link" href="/">Home</a>
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div class="navbar-nav">
|
||||||
|
<div class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||||
|
@(ViewBag.Language?.ToString().ToUpper() ?? "PT")
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="/?language=pt">Português</a></li>
|
||||||
|
<li><a class="dropdown-item" href="/?language=en">English</a></li>
|
||||||
|
<li><a class="dropdown-item" href="/?language=es">Español</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="navbar-text ms-3">
|
||||||
|
<span class="status-indicator" id="health-indicator"></span>
|
||||||
|
<span id="health-text">Verificando...</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
|
||||||
<div class="container">
|
<main>
|
||||||
<main role="main" class="pb-3">
|
|
||||||
@RenderBody()
|
@RenderBody()
|
||||||
</main>
|
</main>
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="border-top footer text-muted">
|
<footer class="bg-dark text-light py-4 mt-5">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
© 2025 - UpperFirstLetter - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Sentence Converter Module</h6>
|
||||||
|
<p class="mb-0">Módulo standalone para conversão de texto</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-md-end">
|
||||||
|
<small class="text-muted">Versão: 1.0.0 | Port: @Context.Request.Host.Port</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
|
||||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
<!-- SUBSTITUIR a seção de scripts por: -->
|
||||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Module Loader Lite para testes standalone -->
|
||||||
|
<script src="~/js/module-loader-lite.js"></script>
|
||||||
|
|
||||||
|
<!-- Sistema de carregamento para ambiente de teste -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
console.log('🚀 ModuleLoaderLite v1.0.0 inicializado para testes');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event listener para módulos carregados localmente
|
||||||
|
document.addEventListener('moduleLoadedLocal', function (event) {
|
||||||
|
console.log('✅ Módulo local carregado:', event.detail);
|
||||||
|
|
||||||
|
// Mostrar feedback visual
|
||||||
|
showModuleLoadedFeedback(event.detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
function showModuleLoadedFeedback(detail) {
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'toast position-fixed top-0 end-0 m-3';
|
||||||
|
toast.style.zIndex = '9999';
|
||||||
|
toast.innerHTML = `
|
||||||
|
<div class="toast-header bg-success text-white">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>
|
||||||
|
<strong class="me-auto">Módulo Carregado</strong>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body">
|
||||||
|
<strong>${detail.moduleId}</strong> inicializado com sucesso!<br>
|
||||||
|
<small class="text-muted">Modo: ${detail.mode}</small>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
const bsToast = new bootstrap.Toast(toast);
|
||||||
|
bsToast.show();
|
||||||
|
|
||||||
|
// Remove o toast após ser ocultado
|
||||||
|
toast.addEventListener('hidden.bs.toast', () => {
|
||||||
|
document.body.removeChild(toast);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função global para carregar módulos em testes
|
||||||
|
window.loadTestModule = async function (containerId, endpoint) {
|
||||||
|
console.log(`🧪 Carregando módulo de teste: ${endpoint} -> ${containerId}`);
|
||||||
|
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
if (container) {
|
||||||
|
// Mostrar loading
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Carregando...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-3">Carregando módulo de teste...</p>
|
||||||
|
<small class="text-muted">Endpoint: ${endpoint}</small>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const success = await window.ModuleLoaderLite.loadModule(containerId, endpoint);
|
||||||
|
if (!success) {
|
||||||
|
throw new Error('Falha ao inicializar módulo');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erro ao carregar módulo de teste:', error);
|
||||||
|
if (container) {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<h6><i class="fas fa-exclamation-triangle me-2"></i>Erro ao Carregar Módulo</h6>
|
||||||
|
<p class="mb-2"><strong>Endpoint:</strong> ${endpoint}</p>
|
||||||
|
<p class="mb-2"><strong>Erro:</strong> ${error.message}</p>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" onclick="loadTestModule('${containerId}', '${endpoint}')">
|
||||||
|
<i class="fas fa-redo me-1"></i> Tentar Novamente
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Health Check Script (manter existente) -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
checkHealth();
|
||||||
|
setInterval(checkHealth, 30000);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function checkHealth() {
|
||||||
|
const indicator = document.getElementById('health-indicator');
|
||||||
|
const text = document.getElementById('health-text');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/home/healthcheck');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === 'healthy') {
|
||||||
|
indicator.className = 'status-indicator status-healthy';
|
||||||
|
text.textContent = 'Online';
|
||||||
|
} else {
|
||||||
|
indicator.className = 'status-indicator status-unhealthy';
|
||||||
|
text.textContent = 'Erro';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
indicator.className = 'status-indicator status-unknown';
|
||||||
|
text.textContent = 'Offline';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
@await RenderSectionAsync("Scripts", required: false)
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
189
UpperFirstLetter/wwwroot/js/module-loader-lite.js
Normal file
189
UpperFirstLetter/wwwroot/js/module-loader-lite.js
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
// Module Loader Lite - Versão simplificada para testes standalone
|
||||||
|
window.ModuleLoaderLite = (function () {
|
||||||
|
|
||||||
|
function log(message, ...args) {
|
||||||
|
console.log(`🧪 ModuleLoaderLite: ${message}`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(message, ...args) {
|
||||||
|
console.error(`❌ ModuleLoaderLite: ${message}`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractModuleMetadata(container) {
|
||||||
|
const metadataScript = container.querySelector('#module-metadata');
|
||||||
|
if (!metadataScript) {
|
||||||
|
log('Nenhum metadata encontrado no módulo');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const metadata = JSON.parse(metadataScript.textContent);
|
||||||
|
log('Metadata extraído:', metadata);
|
||||||
|
return metadata;
|
||||||
|
} catch (err) {
|
||||||
|
error('Erro ao parsear metadata:', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLocalScript(url) {
|
||||||
|
try {
|
||||||
|
log(`Carregando script local: ${url}`);
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scriptContent = await response.text();
|
||||||
|
|
||||||
|
// Criar script element e executar
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.textContent = scriptContent;
|
||||||
|
script.setAttribute('data-test-script', url);
|
||||||
|
document.head.appendChild(script);
|
||||||
|
|
||||||
|
log(`✅ Script local carregado: ${url}`);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
error(`Falha ao carregar script ${url}:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLocalStyle(url) {
|
||||||
|
try {
|
||||||
|
log(`Carregando CSS local: ${url}`);
|
||||||
|
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('data-test-style', url);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
link.onload = () => {
|
||||||
|
log(`✅ CSS local carregado: ${url}`);
|
||||||
|
resolve(true);
|
||||||
|
};
|
||||||
|
link.onerror = () => {
|
||||||
|
error(`Falha ao carregar CSS: ${url}`);
|
||||||
|
reject(false);
|
||||||
|
};
|
||||||
|
document.head.appendChild(link);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
error(`Erro ao carregar CSS ${url}:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initializeLocalModule(containerId) {
|
||||||
|
log(`Inicializando módulo local em: ${containerId}`);
|
||||||
|
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
if (!container) {
|
||||||
|
error(`Container não encontrado: ${containerId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extrair metadata
|
||||||
|
const metadata = extractModuleMetadata(container);
|
||||||
|
if (!metadata) {
|
||||||
|
error('Metadata não encontrado, não é possível inicializar');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Carregar CSS se especificado
|
||||||
|
if (metadata.cssUrl) {
|
||||||
|
await loadLocalStyle(metadata.cssUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carregar JavaScript
|
||||||
|
if (metadata.jsUrl) {
|
||||||
|
const scriptLoaded = await loadLocalScript(metadata.jsUrl);
|
||||||
|
if (!scriptLoaded) {
|
||||||
|
throw new Error('Falha ao carregar script principal');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aguardar execução do script
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
|
|
||||||
|
// Chamar função de inicialização
|
||||||
|
if (metadata.jsFunction) {
|
||||||
|
const functionPath = metadata.jsFunction.split('.');
|
||||||
|
let func = window;
|
||||||
|
|
||||||
|
for (const part of functionPath) {
|
||||||
|
func = func[part];
|
||||||
|
if (!func) {
|
||||||
|
throw new Error(`Função ${metadata.jsFunction} não encontrada`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof func === 'function') {
|
||||||
|
log(`Chamando função: ${metadata.jsFunction}`);
|
||||||
|
const result = func(containerId);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
log(`✅ Módulo ${metadata.moduleId} inicializado localmente`);
|
||||||
|
|
||||||
|
// Disparar evento
|
||||||
|
const event = new CustomEvent('moduleLoadedLocal', {
|
||||||
|
detail: {
|
||||||
|
moduleId: metadata.moduleId,
|
||||||
|
containerId,
|
||||||
|
metadata,
|
||||||
|
mode: 'standalone'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
error(`Erro ao inicializar módulo local:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadModuleLocally(containerId, moduleEndpoint) {
|
||||||
|
log(`Carregando módulo local em ${containerId} de ${moduleEndpoint}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(moduleEndpoint);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await response.text();
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
throw new Error(`Container ${containerId} não encontrado`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserir HTML
|
||||||
|
container.innerHTML = html;
|
||||||
|
|
||||||
|
// Inicializar
|
||||||
|
return await initializeLocalModule(containerId);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
error(`Erro ao carregar módulo local:`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API pública
|
||||||
|
return {
|
||||||
|
loadModule: loadModuleLocally,
|
||||||
|
initializeModule: initializeLocalModule,
|
||||||
|
version: '1.0.0-lite'
|
||||||
|
};
|
||||||
|
})();
|
||||||
196
UpperFirstLetter/wwwroot/js/sentence-converter-widget.js
Normal file
196
UpperFirstLetter/wwwroot/js/sentence-converter-widget.js
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
// Sentence Converter Widget JavaScript
|
||||||
|
window.SentenceConverterWidget = (function () {
|
||||||
|
|
||||||
|
function initializeWidget(containerId) {
|
||||||
|
console.log('🚀 Inicializando SentenceConverterWidget em:', containerId);
|
||||||
|
|
||||||
|
const container = document.getElementById(containerId);
|
||||||
|
if (!container) {
|
||||||
|
console.error('❌ Container não encontrado:', containerId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widget = container.querySelector('[data-converter-id="sentence-converter"]');
|
||||||
|
if (!widget) {
|
||||||
|
console.error('❌ Widget não encontrado no container');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buscar configuração
|
||||||
|
const configScript = widget.querySelector('#widget-config');
|
||||||
|
let config = {};
|
||||||
|
|
||||||
|
if (configScript) {
|
||||||
|
try {
|
||||||
|
config = JSON.parse(configScript.textContent);
|
||||||
|
console.log('✅ Configuração carregada:', config);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('⚠️ Erro ao parsear configuração, usando fallback');
|
||||||
|
config = getFallbackConfig();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ Configuração não encontrada, usando fallback');
|
||||||
|
config = getFallbackConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elementos do widget
|
||||||
|
const elements = {
|
||||||
|
inputText: widget.querySelector('#inputText'),
|
||||||
|
outputText: widget.querySelector('#outputText'),
|
||||||
|
convertBtn: widget.querySelector('#convertBtn'),
|
||||||
|
copyBtn: widget.querySelector('#copyBtn'),
|
||||||
|
clearBtn: widget.querySelector('#clearBtn'),
|
||||||
|
charCount: widget.querySelector('#charCount'),
|
||||||
|
status: widget.querySelector('#conversionStatus')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verificar se todos os elementos existem
|
||||||
|
const missingElements = Object.entries(elements)
|
||||||
|
.filter(([key, element]) => !element)
|
||||||
|
.map(([key]) => key);
|
||||||
|
|
||||||
|
if (missingElements.length > 0) {
|
||||||
|
console.error('❌ Elementos não encontrados:', missingElements);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Todos os elementos encontrados');
|
||||||
|
|
||||||
|
// Event Listeners
|
||||||
|
setupEventListeners(elements, config);
|
||||||
|
|
||||||
|
console.log('✅ SentenceConverterWidget inicializado com sucesso!');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupEventListeners(elements, config) {
|
||||||
|
const { inputText, outputText, convertBtn, copyBtn, clearBtn, charCount, status } = elements;
|
||||||
|
|
||||||
|
// Contador de caracteres
|
||||||
|
inputText.addEventListener('input', function () {
|
||||||
|
charCount.textContent = this.value.length;
|
||||||
|
console.log('📝 Caracteres digitados:', this.value.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Converter texto
|
||||||
|
convertBtn.addEventListener('click', async function () {
|
||||||
|
console.log('🔄 Iniciando conversão...');
|
||||||
|
|
||||||
|
const text = inputText.value.trim();
|
||||||
|
if (!text) {
|
||||||
|
showStatus(status, config.messages.enterText || 'Por favor, digite algum texto.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI Loading state
|
||||||
|
convertBtn.disabled = true;
|
||||||
|
const originalText = convertBtn.innerHTML;
|
||||||
|
convertBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>' + (config.messages.converting || 'Convertendo...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('TextInput', text);
|
||||||
|
formData.append('Language', config.language || 'pt');
|
||||||
|
|
||||||
|
console.log('📤 Enviando requisição para /api/converter/convert');
|
||||||
|
|
||||||
|
const response = await fetch('/api/converter/convert', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
console.log('📥 Resposta recebida:', result);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
outputText.value = result.outputText;
|
||||||
|
copyBtn.disabled = false;
|
||||||
|
showStatus(status, config.messages.success || 'Conversão realizada com sucesso!', 'success');
|
||||||
|
console.log('✅ Conversão bem-sucedida');
|
||||||
|
} else {
|
||||||
|
showStatus(status, 'Erro: ' + result.message, 'danger');
|
||||||
|
console.error('❌ Erro na conversão:', result.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showStatus(status, config.messages.connectionError || 'Erro de conexão. Tente novamente.', 'danger');
|
||||||
|
console.error('❌ Erro de conexão:', error);
|
||||||
|
} finally {
|
||||||
|
convertBtn.disabled = false;
|
||||||
|
convertBtn.innerHTML = originalText;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copiar resultado
|
||||||
|
copyBtn.addEventListener('click', async function () {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(outputText.value);
|
||||||
|
showStatus(status, config.messages.copied || 'Texto copiado para a área de transferência!', 'success');
|
||||||
|
console.log('📋 Texto copiado com sucesso');
|
||||||
|
} catch (error) {
|
||||||
|
showStatus(status, config.messages.copyError || 'Erro ao copiar texto.', 'danger');
|
||||||
|
console.error('❌ Erro ao copiar:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Limpar campos
|
||||||
|
clearBtn.addEventListener('click', function () {
|
||||||
|
inputText.value = '';
|
||||||
|
outputText.value = '';
|
||||||
|
copyBtn.disabled = true;
|
||||||
|
charCount.textContent = '0';
|
||||||
|
status.innerHTML = '';
|
||||||
|
console.log('🧹 Campos limpos');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🎯 Event listeners configurados');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showStatus(statusElement, message, type) {
|
||||||
|
statusElement.innerHTML = `
|
||||||
|
<div class="alert alert-${type} alert-dismissible fade show" role="alert">
|
||||||
|
${message}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
statusElement.innerHTML = '';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFallbackConfig() {
|
||||||
|
return {
|
||||||
|
language: 'pt',
|
||||||
|
convertButtonText: 'Converter Texto',
|
||||||
|
messages: {
|
||||||
|
enterText: 'Por favor, digite algum texto.',
|
||||||
|
converting: 'Convertendo...',
|
||||||
|
success: 'Conversão realizada com sucesso!',
|
||||||
|
copied: 'Texto copiado para a área de transferência!',
|
||||||
|
connectionError: 'Erro de conexão. Tente novamente.',
|
||||||
|
copyError: 'Erro ao copiar texto.'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// API pública
|
||||||
|
return {
|
||||||
|
init: initializeWidget,
|
||||||
|
version: '1.0.0'
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Auto-inicializar se encontrar widgets na página
|
||||||
|
// Auto-inicializar se encontrar widgets na página (para uso standalone)
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// Só auto-inicializar se não estivermos em um sistema de módulos
|
||||||
|
if (!window.ModuleLoader && !window.ModuleSystem) {
|
||||||
|
const widgets = document.querySelectorAll('[data-converter-id="sentence-converter"]');
|
||||||
|
widgets.forEach((widget, index) => {
|
||||||
|
const containerId = widget.closest('[id]')?.id || `widget-container-${index}`;
|
||||||
|
if (!widget.closest('[id]')) {
|
||||||
|
widget.parentElement.id = containerId;
|
||||||
|
}
|
||||||
|
window.SentenceConverterWidget.init(containerId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user