Finished theming
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}ROM Library - DOS Frontend{% endblock %}
|
||||
{% block title %}Game Library - DOS Frontend{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="mb-6">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-4">
|
||||
<h1 class="text-3xl font-bold mb-2 sm:mb-0">ROM Library</h1>
|
||||
<h1 class="text-3xl font-bold mb-2 sm:mb-0">Game Library</h1>
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<!-- Search Form -->
|
||||
<form method="GET" class="flex gap-2">
|
||||
@@ -15,26 +15,26 @@
|
||||
<div class="relative">
|
||||
<input type="text" name="search" placeholder="Search games..."
|
||||
value="{% if search and not search.startswith('genre:') and not search.startswith('tag:') %}{{ search }}{% endif %}"
|
||||
class="w-full sm:w-64 px-4 py-2 pl-10 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
class="w-full sm:w-64 px-4 py-2 pl-10 bg-tertiary border border-theme rounded-lg text-primary placeholder-secondary focus:outline-none focus:ring-2 focus:ring-accent">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-4 h-4 text-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 21-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-white">Search</button>
|
||||
<button type="submit" class="px-4 py-2 bg-accent hover:bg-accent-hover rounded-lg text-primary transition-colors">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls Bar -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-4">
|
||||
<div class="text-gray-400">
|
||||
<div class="text-secondary">
|
||||
{% if is_demo %}
|
||||
<span class="text-yellow-400">Demo Mode:</span> You can browse ROMs but cannot download or favorite them.
|
||||
<span class="text-yellow-400">Demo Mode:</span> You can browse games but cannot download or favorite them.
|
||||
<button onclick="showLogin()" class="text-blue-400 hover:text-blue-300 underline">Login</button> for full access.
|
||||
{% else %}
|
||||
Showing {{ games|length }} of {{ total_games }} ROMs
|
||||
Showing {{ games|length }} of {{ total_games }} games
|
||||
{% if search %} for "{{ search }}"{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -42,8 +42,8 @@
|
||||
<div class="flex flex-col sm:flex-row gap-2 sm:gap-4 items-center">
|
||||
<!-- Results per page -->
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-gray-400 text-sm">Show:</label>
|
||||
<select onchange="changePerPage(this.value)" class="bg-gray-700 border border-gray-600 rounded px-2 py-1 text-white text-sm min-w-16">
|
||||
<label class="text-secondary text-sm">Show:</label>
|
||||
<select onchange="changePerPage(this.value)" class="bg-tertiary border border-theme rounded px-2 py-1 text-primary text-sm min-w-16">
|
||||
<option value="20" {% if per_page == 20 %}selected{% endif %}>20</option>
|
||||
<option value="50" {% if per_page == 50 %}selected{% endif %}>50</option>
|
||||
<option value="100" {% if per_page == 100 %}selected{% endif %}>100</option>
|
||||
@@ -51,16 +51,16 @@
|
||||
</div>
|
||||
|
||||
<!-- View Toggle -->
|
||||
<div class="flex bg-gray-700 rounded-lg p-1">
|
||||
<div class="flex bg-tertiary rounded-lg p-1">
|
||||
<button onclick="changeView('grid')"
|
||||
class="px-4 py-2 rounded text-sm touch-manipulation {{ 'bg-blue-600 text-white' if view == 'grid' else 'text-gray-400 hover:text-white active:bg-gray-600' }}">
|
||||
class="px-4 py-2 rounded text-sm touch-manipulation transition-colors {{ 'bg-accent text-primary' if view == 'grid' else 'text-secondary hover:text-primary active:bg-secondary' }}">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path>
|
||||
</svg>
|
||||
<span class="ml-1 hidden sm:inline">Grid</span>
|
||||
</button>
|
||||
<button onclick="changeView('list')"
|
||||
class="px-4 py-2 rounded text-sm touch-manipulation {{ 'bg-blue-600 text-white' if view == 'list' else 'text-gray-400 hover:text-white active:bg-gray-600' }}">
|
||||
class="px-4 py-2 rounded text-sm touch-manipulation transition-colors {{ 'bg-accent text-primary' if view == 'list' else 'text-secondary hover:text-primary active:bg-secondary' }}">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
@@ -75,29 +75,29 @@
|
||||
{% if view == 'grid' %}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
|
||||
{% for game in games %}
|
||||
<div class="bg-gray-800 rounded-lg border border-gray-700 hover:border-gray-600 transition-colors overflow-hidden hover:shadow-lg transform hover:-translate-y-1 transition-all duration-200 relative group">
|
||||
<div class="bg-secondary rounded-lg border border-theme hover:border-accent transition-colors overflow-hidden hover:shadow-lg transform hover:-translate-y-1 transition-all duration-200 relative group">
|
||||
<!-- Clickable overlay for the card -->
|
||||
<div onclick="showGameDetail({{ game.id }})" class="absolute inset-0 z-10 cursor-pointer" aria-label="View {{ game.metadata_obj.title or game.title }} details"></div>
|
||||
|
||||
<!-- Cover Image -->
|
||||
<div class="aspect-[3/4] bg-gray-900 relative overflow-hidden">
|
||||
<div class="aspect-[3/4] bg-primary relative overflow-hidden">
|
||||
{% if game.metadata_obj and (game.metadata_obj.cover_image_path or (game.metadata_obj.cover_image and game.metadata_obj.cover_image.startswith('http'))) %}
|
||||
<img data-game-id="{{ game.id }}"
|
||||
{% if game.metadata_obj.cover_image_path %}src="/images/{{ game.metadata_obj.cover_image_path.name }}"{% else %}src="{{ game.metadata_obj.cover_image }}"{% endif %}
|
||||
alt="{{ game.metadata_obj.title or game.title }}"
|
||||
class="w-full h-full object-cover transition-transform duration-200 group-hover:scale-105"
|
||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
||||
<div class="w-full h-full bg-gradient-to-br from-gray-800 to-gray-900 hidden items-center justify-center">
|
||||
<div class="text-gray-400 text-center p-4">
|
||||
<div class="w-full h-full bg-gradient-theme hidden items-center justify-center absolute inset-0">
|
||||
<div class="text-secondary text-center p-4">
|
||||
<svg class="w-16 h-16 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2h2a2 2 0 002-2z"></path>
|
||||
</svg>
|
||||
<p class="text-sm font-medium">{{ (game.metadata_obj.title or game.title)[:20] }}{% if (game.metadata_obj.title or game.title)|length > 20 %}...{% endif %}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">DOS Game</p>
|
||||
<p class="text-xs text-secondary opacity-75 mt-1">DOS Game</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="w-full h-full bg-gradient-theme flex items-center justify-center relative overflow-hidden">
|
||||
<div class="w-full h-full bg-gradient-theme flex items-center justify-center relative overflow-hidden absolute inset-0">
|
||||
<!-- Background Pattern -->
|
||||
<div class="absolute inset-0 opacity-10">
|
||||
<svg width="100%" height="100%" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -111,7 +111,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="text-center p-3 relative z-10">
|
||||
<div class="text-center p-3 relative z-10 w-full h-full flex flex-col justify-center items-center">
|
||||
<!-- DosVault Logo -->
|
||||
<div class="mb-4">
|
||||
<svg class="w-20 h-20 mx-auto text-accent" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -188,23 +188,23 @@
|
||||
</div>
|
||||
|
||||
<!-- Game Info -->
|
||||
<div class="p-3">
|
||||
<h3 class="font-semibold text-blue-400 truncate mb-1 text-sm">
|
||||
<div class="p-3 bg-tertiary border-t border-theme">
|
||||
<h3 class="font-semibold text-accent truncate mb-1 text-sm">
|
||||
{{ game.metadata_obj.title or game.title }}
|
||||
</h3>
|
||||
{% if game.metadata_obj and game.metadata_obj.year %}
|
||||
<p class="text-xs text-gray-400 mb-2">{{ game.metadata_obj.year }}</p>
|
||||
<p class="text-xs text-secondary mb-2">{{ game.metadata_obj.year }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xs text-gray-500">Click to view details</span>
|
||||
<span class="text-xs text-secondary">Click to view details</span>
|
||||
{% if not is_demo %}
|
||||
<button onclick="event.stopPropagation(); downloadGame({{ game.id }})"
|
||||
class="bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-xs z-20 relative">
|
||||
class="bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-xs z-20 relative transition-colors">
|
||||
Download
|
||||
</button>
|
||||
{% else %}
|
||||
<span class="bg-gray-600 px-2 py-1 rounded text-xs cursor-not-allowed">
|
||||
<span class="bg-tertiary border border-theme px-2 py-1 rounded text-xs cursor-not-allowed text-secondary">
|
||||
Login
|
||||
</span>
|
||||
{% endif %}
|
||||
@@ -219,20 +219,20 @@
|
||||
{% if view == 'list' %}
|
||||
<div class="space-y-2">
|
||||
{% for game in games %}
|
||||
<div class="bg-gray-800 rounded-lg border border-gray-700 hover:border-gray-600 transition-colors hover:shadow-lg transform hover:-translate-y-1 transition-all duration-200 relative group">
|
||||
<div class="bg-secondary rounded-lg border border-theme hover:border-accent transition-colors hover:shadow-lg transform hover:-translate-y-1 transition-all duration-200 relative group">
|
||||
<!-- Clickable overlay for the card -->
|
||||
<div onclick="showGameDetail({{ game.id }})" class="absolute inset-0 z-10 cursor-pointer" aria-label="View {{ game.metadata_obj.title or game.title }} details"></div>
|
||||
|
||||
<div class="p-4 flex items-center gap-4">
|
||||
<!-- Cover Image -->
|
||||
<div class="w-16 h-20 bg-gray-900 rounded overflow-hidden flex-shrink-0 relative">
|
||||
<div class="w-16 h-20 bg-primary rounded overflow-hidden flex-shrink-0 relative">
|
||||
{% if game.metadata_obj and (game.metadata_obj.cover_image_path or (game.metadata_obj.cover_image and game.metadata_obj.cover_image.startswith('http'))) %}
|
||||
<img data-game-id="{{ game.id }}"
|
||||
{% if game.metadata_obj.cover_image_path %}src="/images/{{ game.metadata_obj.cover_image_path.name }}"{% else %}src="{{ game.metadata_obj.cover_image }}"{% endif %}
|
||||
alt="{{ game.metadata_obj.title or game.title }}"
|
||||
class="w-full h-full object-cover"
|
||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
||||
<div class="w-full h-full bg-gradient-theme hidden items-center justify-center relative overflow-hidden">
|
||||
<div class="w-full h-full bg-gradient-theme hidden items-center justify-center relative overflow-hidden absolute inset-0">
|
||||
<!-- Background Pattern -->
|
||||
<div class="absolute inset-0 opacity-10">
|
||||
<svg width="100%" height="100%" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -246,10 +246,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="text-center p-3 relative z-10">
|
||||
<div class="text-center p-1 relative z-10 w-full h-full flex flex-col justify-center items-center">
|
||||
<!-- DosVault Logo -->
|
||||
<div class="mb-4">
|
||||
<svg class="w-20 h-20 mx-auto text-accent" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<div class="mb-1">
|
||||
<svg class="w-6 h-6 mx-auto text-accent" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Vault door with enhanced styling -->
|
||||
<circle cx="16" cy="16" r="15" fill="currentColor" opacity="0.15" stroke="currentColor" stroke-width="0.5"/>
|
||||
<circle cx="16" cy="16" r="12" stroke="currentColor" stroke-width="1.5" fill="none" opacity="0.8"/>
|
||||
@@ -273,18 +273,9 @@
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Game Title -->
|
||||
<div class="mb-2">
|
||||
<h3 class="text-accent font-bold text-sm leading-tight mb-1">
|
||||
{{ (game.metadata_obj.title or game.title)[:18] }}{% if (game.metadata_obj.title or game.title)|length > 18 %}...{% endif %}
|
||||
</h3>
|
||||
<div class="w-12 h-0.5 bg-accent mx-auto opacity-60"></div>
|
||||
</div>
|
||||
|
||||
<!-- Branding -->
|
||||
<div class="text-xs text-secondary opacity-75 font-medium">
|
||||
<div class="mb-1">CLASSIC DOS GAME</div>
|
||||
<div class="text-accent text-[10px] font-bold tracking-wider">DOSVAULT</div>
|
||||
<div class="text-[6px] text-accent font-bold tracking-wide opacity-75">
|
||||
DOSVAULT
|
||||
</div>
|
||||
|
||||
<!-- Decorative Corner Elements -->
|
||||
@@ -311,9 +302,9 @@
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="w-full h-full bg-gradient-theme flex items-center justify-center relative overflow-hidden">
|
||||
<div class="w-full h-full bg-gradient-theme flex items-center justify-center relative overflow-hidden absolute inset-0">
|
||||
<!-- Compact Background Pattern -->
|
||||
<div class="absolute inset-0 opacity-8">
|
||||
<div class="absolute inset-0 opacity-10">
|
||||
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none">
|
||||
<defs>
|
||||
<pattern id="pixel-grid-small" x="0" y="0" width="8" height="8" patternUnits="userSpaceOnUse">
|
||||
@@ -325,8 +316,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Compact Logo -->
|
||||
<div class="text-center relative z-10">
|
||||
<svg class="w-8 h-8 mx-auto text-accent mb-1" viewBox="0 0 32 32" fill="none">
|
||||
<div class="text-center relative z-10 w-full h-full flex flex-col justify-center items-center p-1">
|
||||
<svg class="w-6 h-6 mx-auto text-accent mb-1" viewBox="0 0 32 32" fill="none">
|
||||
<!-- Simplified vault door -->
|
||||
<circle cx="16" cy="16" r="12" stroke="currentColor" stroke-width="1.5" fill="currentColor" opacity="0.1"/>
|
||||
<circle cx="16" cy="16" r="8" stroke="currentColor" stroke-width="1" opacity="0.6"/>
|
||||
@@ -342,7 +333,7 @@
|
||||
<rect x="20" y="14" width="6" height="4" rx="2" fill="currentColor" opacity="0.7"/>
|
||||
<circle cx="22" cy="16" r="0.8" fill="var(--primary-bg)"/>
|
||||
</svg>
|
||||
<div class="text-accent text-[8px] font-bold tracking-wide opacity-75">DOSVAULT</div>
|
||||
<div class="text-accent text-[6px] font-bold tracking-wide opacity-75">DOSVAULT</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -401,7 +392,7 @@
|
||||
<nav class="flex items-center space-x-1">
|
||||
{% if current_page > 1 %}
|
||||
<a href="javascript:void(0)" onclick="goToPage({{ current_page - 1 }})"
|
||||
class="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm">
|
||||
class="px-3 py-2 bg-tertiary hover:bg-accent rounded text-sm transition-colors">
|
||||
Previous
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -409,21 +400,21 @@
|
||||
<!-- First page -->
|
||||
{% if current_page > 6 %}
|
||||
<a href="javascript:void(0)" onclick="goToPage(1)"
|
||||
class="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm">
|
||||
class="px-3 py-2 bg-tertiary hover:bg-accent rounded text-sm transition-colors">
|
||||
1
|
||||
</a>
|
||||
{% if current_page > 7 %}
|
||||
<span class="px-3 py-2 text-gray-400">...</span>
|
||||
<span class="px-3 py-2 text-secondary">...</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Page numbers around current page -->
|
||||
{% for page_num in range(max(1, current_page - 4), min(total_pages + 1, current_page + 5)) %}
|
||||
{% if page_num == current_page %}
|
||||
<span class="px-3 py-2 bg-blue-600 rounded text-sm">{{ page_num }}</span>
|
||||
<span class="px-3 py-2 bg-accent rounded text-sm text-primary">{{ page_num }}</span>
|
||||
{% else %}
|
||||
<a href="javascript:void(0)" onclick="goToPage({{ page_num }})"
|
||||
class="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm">
|
||||
class="px-3 py-2 bg-tertiary hover:bg-accent rounded text-sm transition-colors">
|
||||
{{ page_num }}
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -432,17 +423,17 @@
|
||||
<!-- Last page -->
|
||||
{% if current_page < total_pages - 5 %}
|
||||
{% if current_page < total_pages - 6 %}
|
||||
<span class="px-3 py-2 text-gray-400">...</span>
|
||||
<span class="px-3 py-2 text-secondary">...</span>
|
||||
{% endif %}
|
||||
<a href="javascript:void(0)" onclick="goToPage({{ total_pages }})"
|
||||
class="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm">
|
||||
class="px-3 py-2 bg-tertiary hover:bg-accent rounded text-sm transition-colors">
|
||||
{{ total_pages }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if current_page < total_pages %}
|
||||
<a href="javascript:void(0)" onclick="goToPage({{ current_page + 1 }})"
|
||||
class="px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm">
|
||||
class="px-3 py-2 bg-tertiary hover:bg-accent rounded text-sm transition-colors">
|
||||
Next
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -454,7 +445,7 @@
|
||||
<div id="gameDetailOverlay" class="hidden fixed inset-0 bg-black bg-opacity-75 z-50 flex items-center justify-center p-4 overflow-y-auto backdrop-blur-sm">
|
||||
<div class="bg-secondary rounded-lg max-w-6xl max-h-full w-full overflow-hidden relative shadow-2xl border border-theme animate-fade-in">
|
||||
<!-- Close Button -->
|
||||
<button onclick="closeGameDetail()" class="absolute top-4 right-4 text-primary hover:text-secondary text-2xl z-20 bg-primary bg-opacity-20 hover:bg-opacity-30 rounded-full w-10 h-10 flex items-center justify-center transition-all">
|
||||
<button onclick="closeGameDetail()" class="absolute top-4 right-4 text-primary hover:text-secondary text-2xl z-30 bg-primary bg-opacity-20 hover:bg-opacity-30 rounded-full w-10 h-10 flex items-center justify-center transition-all">
|
||||
×
|
||||
</button>
|
||||
|
||||
@@ -486,7 +477,7 @@
|
||||
|
||||
<button id="gameDownloadBtn" onclick="downloadGameFromOverlay()"
|
||||
class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded hidden">
|
||||
Download ROM
|
||||
Download Game
|
||||
</button>
|
||||
|
||||
<span id="gameDownloadDisabled" class="bg-gray-600 px-4 py-2 rounded cursor-not-allowed hidden">
|
||||
@@ -648,7 +639,10 @@
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = response.headers.get('Content-Disposition')?.split('filename=')[1] || 'game.zip';
|
||||
// Get filename from Content-Disposition header, removing quotes and underscores
|
||||
let filename = response.headers.get('Content-Disposition')?.split('filename=')[1] || 'game.zip';
|
||||
filename = filename.replace(/^["\_]+|["\_]+$/g, ''); // Remove quotes and underscores from start and end
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
Reference in New Issue
Block a user