7 Commits

Author SHA1 Message Date
efe23a99ac 0.3.5
All checks were successful
Deploy to Production / deploy (push) Successful in 13s
2026-02-27 04:50:57 +00:00
bebb63c1de feat: improve PWA update mechanism with visibility check 2026-02-27 04:50:39 +00:00
98d76e3a35 0.3.4
All checks were successful
Deploy to Production / deploy (push) Successful in 13s
2026-02-27 04:44:34 +00:00
cc7e80a807 feat: improve mobile layout for Password Generator button 2026-02-27 04:43:58 +00:00
1f5500f7d7 0.3.3
All checks were successful
Deploy to Production / deploy (push) Successful in 13s
2026-02-27 04:37:29 +00:00
d404370027 fix: adjust layout for Password Generator and Clipboard Sniffer on smaller screens 2026-02-27 04:37:21 +00:00
8fb3ee1069 refactor: align privacy policy layout with app design
All checks were successful
Deploy to Production / deploy (push) Successful in 7s
2026-02-27 04:20:29 +00:00
6 changed files with 207 additions and 81 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "tools-app",
"version": "0.3.2",
"version": "0.3.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tools-app",
"version": "0.3.2",
"version": "0.3.5",
"dependencies": {
"lucide-vue-next": "^0.575.0",
"vue": "^3.5.25",

View File

@@ -1,7 +1,7 @@
{
"name": "tools-app",
"private": true,
"version": "0.3.2",
"version": "0.3.5",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -5,6 +5,7 @@ import Header from './components/Header.vue'
import Footer from './components/Footer.vue'
import Sidebar from './components/Sidebar.vue'
import InstallPrompt from './components/InstallPrompt.vue'
import ReloadPrompt from './components/ReloadPrompt.vue'
const isSidebarOpen = ref(window.innerWidth >= 768)
const router = useRouter()
@@ -53,6 +54,7 @@ onUnmounted(() => {
</div>
<Footer />
<InstallPrompt />
<ReloadPrompt />
</template>
<style scoped>

View File

@@ -0,0 +1,94 @@
<script setup>
import { useRegisterSW } from 'virtual:pwa-register/vue'
import { watch, onMounted } from 'vue'
// Zmieniamy na autoUpdate w configu, więc tutaj tylko nasłuchujemy i ewentualnie wymuszamy
const {
needRefresh,
updateServiceWorker,
} = useRegisterSW({
immediate: true,
onRegistered(r) {
// Sprawdzaj aktualizacje co godzinę
r && setInterval(() => {
r.update()
}, 60 * 60 * 1000)
}
})
const updateSW = async () => {
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.ready
console.log('Checking for SW update...')
await registration.update()
} catch (e) {
console.error('Failed to update SW:', e)
}
}
}
onMounted(() => {
// Check on load
updateSW()
// Check when app becomes visible again (e.g. switching tabs/apps)
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
updateSW()
}
})
})
</script>
<template>
<div
v-if="needRefresh"
class="pwa-toast"
role="alert"
>
<div class="message">
New content available, click on reload button to update.
</div>
<button @click="updateServiceWorker()">
Reload
</button>
</div>
</template>
<style scoped>
.pwa-toast {
position: fixed;
right: 0;
bottom: 0;
margin: 16px;
padding: 12px;
border: 1px solid var(--glass-border);
border-radius: 4px;
z-index: 10000;
text-align: left;
box-shadow: var(--glass-shadow);
background-color: var(--glass-bg);
backdrop-filter: blur(10px);
color: var(--text-color);
display: flex;
flex-direction: column;
gap: 8px;
}
.pwa-toast button {
border: 1px solid var(--glass-border);
outline: none;
margin-right: 5px;
border-radius: 2px;
padding: 3px 10px;
cursor: pointer;
background: var(--button-bg);
color: var(--text-color);
transition: all 0.2s;
}
.pwa-toast button:hover {
background: var(--button-hover-bg);
}
</style>

View File

@@ -75,7 +75,7 @@ const generatePasswords = () => {
<div class="tool-panel">
<div class="panel-header">
<h2 class="tool-title">Bulk Passwords Generator</h2>
<div class="action-area">
<div class="action-area desktop-only">
<button class="btn-neon generate-btn" @click="generatePasswords" v-ripple>
Generate
</button>
@@ -131,6 +131,12 @@ const generatePasswords = () => {
</div>
</div>
<div class="mobile-only" style="margin-top: 1rem; width: 100%;">
<button class="btn-neon generate-btn" @click="generatePasswords" v-ripple style="width: 100%;">
Generate
</button>
</div>
<div class="result-area" :style="{ height: textareaHeight }">
<textarea
class="tool-textarea"
@@ -189,9 +195,10 @@ const generatePasswords = () => {
.inputs-group {
display: flex;
gap: 2rem;
gap: 1rem;
flex: 1;
min-width: 300px;
min-width: 200px;
flex-wrap: wrap;
}
.input-wrapper {
@@ -199,6 +206,7 @@ const generatePasswords = () => {
flex-direction: column;
gap: 0.5rem;
flex: 1;
min-width: 140px;
}
.checkbox-label {
@@ -351,6 +359,14 @@ const generatePasswords = () => {
letter-spacing: 1px;
}
.desktop-only {
display: block;
}
.mobile-only {
display: none;
}
@media (max-width: 768px) {
.options-grid {
flex-direction: column;
@@ -370,5 +386,13 @@ const generatePasswords = () => {
.generate-btn {
width: 100%;
}
.desktop-only {
display: none;
}
.mobile-only {
display: block !important;
}
}
</style>

View File

@@ -3,8 +3,8 @@ import { ArrowLeft } from 'lucide-vue-next'
</script>
<template>
<div class="privacy-container">
<div class="privacy-content">
<div class="tool-container">
<div class="tool-panel privacy-panel">
<header class="privacy-header">
<router-link to="/" class="back-link">
<ArrowLeft size="20" />
@@ -14,96 +14,102 @@ import { ArrowLeft } from 'lucide-vue-next'
<p class="last-updated">Last Updated: February 27, 2026</p>
</header>
<section>
<h2>1. Introduction</h2>
<p>
Welcome to Tools App ("we," "our," or "us"). We are committed to protecting your privacy.
This Privacy Policy explains how our Chrome Extension ("Tools App Extension") handles your data.
</p>
</section>
<div class="privacy-body">
<section>
<h2>1. Introduction</h2>
<p>
Welcome to Tools App ("we," "our," or "us"). We are committed to protecting your privacy.
This Privacy Policy explains how our Chrome Extension ("Tools App Extension") handles your data.
</p>
</section>
<section>
<h2>2. Data Collection and Usage</h2>
<p>
The Tools App Extension is designed with privacy as a priority.
<strong>We do not collect, store, or transmit any of your personal data to external servers.</strong>
</p>
<section>
<h2>2. Data Collection and Usage</h2>
<p>
The Tools App Extension is designed with privacy as a priority.
<strong>We do not collect, store, or transmit any of your personal data to external servers.</strong>
</p>
<h3>Clipboard Data</h3>
<p>
The extension requires the <code>clipboardRead</code> permission to function.
It reads text from your clipboard <strong>only</strong> when you explicitly enable the "Clipboard Sniffer" tool in the Tools App web interface.
</p>
<ul>
<li>Clipboard data is processed locally within your browser.</li>
<li>Data is sent directly from the extension to the open Tools App tab via a secure local communication channel.</li>
<li>Once you close the tab or stop the tool, the extension stops monitoring the clipboard immediately.</li>
<li>We do not have access to your clipboard history, and it is never uploaded to any cloud storage or third-party service.</li>
</ul>
</section>
<h3>Clipboard Data</h3>
<p>
The extension requires the <code>clipboardRead</code> permission to function.
It reads text from your clipboard <strong>only</strong> when you explicitly enable the "Clipboard Sniffer" tool in the Tools App web interface.
</p>
<ul>
<li>Clipboard data is processed locally within your browser.</li>
<li>Data is sent directly from the extension to the open Tools App tab via a secure local communication channel.</li>
<li>Once you close the tab or stop the tool, the extension stops monitoring the clipboard immediately.</li>
<li>We do not have access to your clipboard history, and it is never uploaded to any cloud storage or third-party service.</li>
</ul>
</section>
<section>
<h2>3. Permissions</h2>
<p>The extension requests the following permissions for specific functional purposes:</p>
<ul>
<li><strong>clipboardRead:</strong> To detect copied text when the Sniffer tool is active.</li>
<li><strong>scripting:</strong> To communicate with the Tools App web page.</li>
<li><strong>storage:</strong> To save local user preferences (e.g., sound settings).</li>
<li><strong>alarms:</strong> To maintain the background process active during monitoring sessions.</li>
</ul>
</section>
<section>
<h2>3. Permissions</h2>
<p>The extension requests the following permissions for specific functional purposes:</p>
<ul>
<li><strong>clipboardRead:</strong> To detect copied text when the Sniffer tool is active.</li>
<li><strong>scripting:</strong> To communicate with the Tools App web page.</li>
<li><strong>storage:</strong> To save local user preferences (e.g., sound settings).</li>
<li><strong>alarms:</strong> To maintain the background process active during monitoring sessions.</li>
</ul>
</section>
<section>
<h2>4. Third-Party Services</h2>
<p>
Our extension operates independently and does not use any third-party analytics, tracking scripts, or advertising networks.
</p>
</section>
<section>
<h2>4. Third-Party Services</h2>
<p>
Our extension operates independently and does not use any third-party analytics, tracking scripts, or advertising networks.
</p>
</section>
<section>
<h2>5. Changes to This Policy</h2>
<p>
We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.
</p>
</section>
<section>
<h2>5. Changes to This Policy</h2>
<p>
We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.
</p>
</section>
<section>
<h2>6. Contact Us</h2>
<p>
If you have any questions about this Privacy Policy, please contact us via the repository or support channels provided in the Chrome Web Store listing.
</p>
</section>
<section>
<h2>6. Contact Us</h2>
<p>
If you have any questions about this Privacy Policy, please contact us via the repository or support channels provided in the Chrome Web Store listing.
</p>
</section>
</div>
</div>
</div>
</template>
<style scoped>
.privacy-container {
min-height: 100vh;
width: 100%;
display: flex;
justify-content: center;
padding: 2rem;
background: var(--bg-gradient);
color: var(--text-color);
.privacy-panel {
max-width: 900px; /* Slightly wider for reading */
margin: 0 auto;
}
.privacy-content {
width: 100%;
max-width: 800px;
background: var(--glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: 16px;
padding: 3rem;
box-shadow: var(--glass-shadow);
.privacy-body {
overflow-y: auto;
padding-right: 0.5rem;
}
/* Custom scrollbar for privacy body */
.privacy-body::-webkit-scrollbar {
width: 8px;
}
.privacy-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
.privacy-body::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
.privacy-header {
margin-bottom: 3rem;
margin-bottom: 2rem;
border-bottom: 1px solid var(--glass-border);
padding-bottom: 2rem;
padding-bottom: 1.5rem;
flex-shrink: 0;
}
.back-link {