@tushar-br/desktop 1.0.234 → 1.0.237
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @tushar-br/desktop might be problematic. Click here for more details.
- package/package.json +1 -1
- package/staging_area/desktop/resume web/dist/assets/explorer/thispc/1771614269438_ENGLISH__diplomaitmorbi.netlify.app.pdf.ts_part_1 +0 -0
- package/staging_area/desktop/resume web/AUTO_SAVE_FEATURE.md +0 -128
- package/staging_area/desktop/resume web/Add Certificate.bat +0 -5
- package/staging_area/desktop/resume web/Add Post.bat +0 -46
- package/staging_area/desktop/resume web/Add Project.bat +0 -5
- package/staging_area/desktop/resume web/Add Status.bat +0 -41
- package/staging_area/desktop/resume web/BOOK_EDITOR_COMPLETE.md +0 -100
- package/staging_area/desktop/resume web/BOOK_EDITOR_TEST.md +0 -82
- package/staging_area/desktop/resume web/BOOK_FINAL.md +0 -120
- package/staging_area/desktop/resume web/BOOK_FINAL_SUMMARY.md +0 -290
- package/staging_area/desktop/resume web/BOOK_FIXES.md +0 -196
- package/staging_area/desktop/resume web/BOOK_README.md +0 -250
- package/staging_area/desktop/resume web/CHANGELOG.md +0 -29
- package/staging_area/desktop/resume web/api/chat-admin-otp.ts +0 -62
- package/staging_area/desktop/resume web/api/chat-admin-verify.ts +0 -41
- package/staging_area/desktop/resume web/api/chat-telegram-alert.ts +0 -30
- package/staging_area/desktop/resume web/api/cloudinary.ts +0 -98
- package/staging_area/desktop/resume web/api/email-notification.ts +0 -82
- package/staging_area/desktop/resume web/api/groq-chat.ts +0 -113
- package/staging_area/desktop/resume web/api/imagekit-auth.ts +0 -37
- package/staging_area/desktop/resume web/book.html +0 -527
- package/staging_area/desktop/resume web/chat-database.rules.json +0 -16
- package/staging_area/desktop/resume web/chat-firestore.rules +0 -18
- package/staging_area/desktop/resume web/database.rules.json +0 -8
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
export default async function handler(req: any, res: any) {
|
|
3
|
-
// Allow CORS
|
|
4
|
-
res.setHeader('Access-Control-Allow-Credentials', true);
|
|
5
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
6
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT');
|
|
7
|
-
res.setHeader(
|
|
8
|
-
'Access-Control-Allow-Headers',
|
|
9
|
-
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
if (req.method === 'OPTIONS') {
|
|
13
|
-
res.status(200).end();
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (req.method !== 'POST') {
|
|
18
|
-
return res.status(405).json({ error: 'Method Not Allowed' });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const { messages } = req.body;
|
|
23
|
-
const apiKey = process.env.GROQ_API_KEY;
|
|
24
|
-
|
|
25
|
-
if (!apiKey) {
|
|
26
|
-
console.error("Missing GROQ_API_KEY");
|
|
27
|
-
return res.status(500).json({ error: "Server Configuration Error: Missing AI Keys" });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const response = await fetch('https://api.groq.com/openai/v1/chat/completions', {
|
|
31
|
-
method: 'POST',
|
|
32
|
-
headers: {
|
|
33
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
34
|
-
'Content-Type': 'application/json',
|
|
35
|
-
},
|
|
36
|
-
body: JSON.stringify({
|
|
37
|
-
model: 'llama-3.3-70b-versatile',
|
|
38
|
-
messages: [
|
|
39
|
-
{
|
|
40
|
-
role: 'system',
|
|
41
|
-
content: `### 🤖 OFFICIAL AI ASSISTANT: TUSHAR RATHOD
|
|
42
|
-
You are the advanced interactive intelligence of TUSHAR RATHOD's Portfolio.
|
|
43
|
-
|
|
44
|
-
### 👤 IDENTITY
|
|
45
|
-
- **Full Name:** Rathod Tushar Babubhai (@tusharbr)
|
|
46
|
-
- **Role:** Web & Game Developer (Full Stack)
|
|
47
|
-
- **Location:** Lalpur, Jamnagar, Gujarat, India
|
|
48
|
-
- **Education:** Diploma in IT, L.E. College Morbi (GTU), 5th Sem completed.
|
|
49
|
-
- **LinkedIn:** Tushar(ᯤ).ix
|
|
50
|
-
|
|
51
|
-
### 🛠️ SKILLS
|
|
52
|
-
- **Web:** React, Vite, Next.js, Node.js, PHP, Bootstrap, Tailwind, Three.js, Framer Motion.
|
|
53
|
-
- **Games:** Unity Engine, C#, Game UI, 3D Environments.
|
|
54
|
-
- **Other:** SEO, Web Analytics, Canva, Photoshop, Video Editing.
|
|
55
|
-
|
|
56
|
-
### 🚀 PROJECTS
|
|
57
|
-
- **Tusharbr.online:** Main OS-style portfolio.
|
|
58
|
-
- **RILVOX:** Creative web design portal.
|
|
59
|
-
- **Blockchain Voting (FOB):** Secure electronic voting system.
|
|
60
|
-
- **Media Toolkit:** Advanced processing tools.
|
|
61
|
-
- **TRX Agent:** Command-based desktop software.
|
|
62
|
-
|
|
63
|
-
### 📜 CERTIFICATES
|
|
64
|
-
- Google Fundamentals of Digital Marketing
|
|
65
|
-
- Google Analytics Individual Qualification
|
|
66
|
-
- LinkedIn AI for Business Professionals
|
|
67
|
-
- HackerRank Java (Basic)
|
|
68
|
-
- Machine Learning (Intro & Advanced)
|
|
69
|
-
- Chrome DevTools & Android Studio Professional Certification
|
|
70
|
-
|
|
71
|
-
### 🔗 SOCIALS
|
|
72
|
-
- **Email:** rathodtushar1442@gmail.com (ONLY USE THIS)
|
|
73
|
-
- **GitHub:** https://github.com/tushar-br
|
|
74
|
-
- **LinkedIn:** https://linkedin.com/in/tushar-rathod-it
|
|
75
|
-
- **Instagram:** @tushar__br
|
|
76
|
-
|
|
77
|
-
### 🚫 SECURITY RULES
|
|
78
|
-
1. **NO ADMIN ACCESS:** Do NOT talk about /admin or /chat-admin.
|
|
79
|
-
2. **STRICT IDENTITY:** Use only the data above. NEVER hallucinate false emails.
|
|
80
|
-
3. **FOCUS:** Answer about Tushar's skills and projects.
|
|
81
|
-
|
|
82
|
-
### 🎨 STYLE
|
|
83
|
-
- Professional, futuristic, helpful.
|
|
84
|
-
- Use Emojis and Bold text for clean formatting.`
|
|
85
|
-
},
|
|
86
|
-
...messages
|
|
87
|
-
],
|
|
88
|
-
max_tokens: 1024,
|
|
89
|
-
temperature: 0.7,
|
|
90
|
-
}),
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const data = await response.json();
|
|
94
|
-
|
|
95
|
-
if (!response.ok) {
|
|
96
|
-
console.error("Groq API Error:", data);
|
|
97
|
-
return res.status(response.status).json({ error: data.error?.message || "AI Service Error" });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// --- FAILSAFE REPLACEMENT ---
|
|
101
|
-
if (data.choices && data.choices[0] && data.choices[0].message.content) {
|
|
102
|
-
let content = data.choices[0].message.content;
|
|
103
|
-
content = content.replace(/tusharrathod@gmail\.com/gi, 'rathodtushar1442@gmail.com');
|
|
104
|
-
content = content.replace(/tusharrathod\.com/gi, 'tusharbr.online');
|
|
105
|
-
data.choices[0].message.content = content;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
res.status(200).json(data);
|
|
109
|
-
} catch (error) {
|
|
110
|
-
console.error("Chat Error:", error);
|
|
111
|
-
res.status(500).json({ error: "Failed to connect to AI service" });
|
|
112
|
-
}
|
|
113
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import ImageKit from "imagekit";
|
|
2
|
-
|
|
3
|
-
export default async function handler(req: any, res: any) {
|
|
4
|
-
// Allow CORS
|
|
5
|
-
res.setHeader('Access-Control-Allow-Credentials', true);
|
|
6
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
7
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT');
|
|
8
|
-
res.setHeader(
|
|
9
|
-
'Access-Control-Allow-Headers',
|
|
10
|
-
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
if (req.method === 'OPTIONS') {
|
|
14
|
-
res.status(200).end();
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
if (!process.env.IMAGEKIT_PRIVATE_KEY || !process.env.IMAGEKIT_PUBLIC_KEY || !process.env.IMAGEKIT_URL_ENDPOINT) {
|
|
20
|
-
console.error("Missing ImageKit Env Vars");
|
|
21
|
-
return res.status(500).json({ error: "Server Configuration Error: Missing ImageKit Keys" });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const imagekit = new ImageKit({
|
|
25
|
-
publicKey: process.env.IMAGEKIT_PUBLIC_KEY,
|
|
26
|
-
privateKey: process.env.IMAGEKIT_PRIVATE_KEY,
|
|
27
|
-
urlEndpoint: process.env.IMAGEKIT_URL_ENDPOINT,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const authenticationParameters = imagekit.getAuthenticationParameters();
|
|
31
|
-
|
|
32
|
-
res.status(200).json(authenticationParameters);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error("ImageKit Auth Error:", error);
|
|
35
|
-
res.status(500).json({ error: "Failed to generate auth parameters" });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
@@ -1,527 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>Book Editor - Pro</title>
|
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2.39.3/dist/umd/supabase.min.js"></script>
|
|
9
|
-
<style>
|
|
10
|
-
/* CSS Variables */
|
|
11
|
-
:root {
|
|
12
|
-
/* Light Theme */
|
|
13
|
-
--bg-body: #f3f4f6;
|
|
14
|
-
--bg-panel: #ffffff;
|
|
15
|
-
--text-main: #1f2937;
|
|
16
|
-
--text-muted: #6b7280;
|
|
17
|
-
--border: #e5e7eb;
|
|
18
|
-
--accent: #2563eb;
|
|
19
|
-
--canvas-bg: #ffffff;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
[data-theme="dark"] {
|
|
23
|
-
/* Dark Theme (Default) */
|
|
24
|
-
--bg-body: #000000;
|
|
25
|
-
--bg-panel: #121212;
|
|
26
|
-
--text-main: #e5e7eb;
|
|
27
|
-
--text-muted: #9ca3af;
|
|
28
|
-
--border: #27272a;
|
|
29
|
-
--accent: #3b82f6;
|
|
30
|
-
--canvas-bg: #ffffff;
|
|
31
|
-
/* Whiteboard is always white in Paint Mode */
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
* {
|
|
35
|
-
margin: 0;
|
|
36
|
-
padding: 0;
|
|
37
|
-
box-sizing: border-box;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
body {
|
|
41
|
-
font-family: 'Segoe UI', system-ui, sans-serif;
|
|
42
|
-
background: var(--bg-body);
|
|
43
|
-
color: var(--text-main);
|
|
44
|
-
height: 100vh;
|
|
45
|
-
overflow: hidden;
|
|
46
|
-
display: flex;
|
|
47
|
-
flex-direction: column;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/* Top Bar */
|
|
51
|
-
.topbar {
|
|
52
|
-
height: 50px;
|
|
53
|
-
background: var(--bg-panel);
|
|
54
|
-
border-bottom: 1px solid var(--border);
|
|
55
|
-
display: flex;
|
|
56
|
-
align-items: center;
|
|
57
|
-
justify-content: space-between;
|
|
58
|
-
padding: 0 20px;
|
|
59
|
-
user-select: none;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.status-pill {
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
gap: 8px;
|
|
66
|
-
font-size: 0.85rem;
|
|
67
|
-
color: var(--text-muted);
|
|
68
|
-
background: var(--bg-body);
|
|
69
|
-
padding: 4px 12px;
|
|
70
|
-
border-radius: 99px;
|
|
71
|
-
border: 1px solid var(--border);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.status-dot {
|
|
75
|
-
width: 8px;
|
|
76
|
-
height: 8px;
|
|
77
|
-
border-radius: 50%;
|
|
78
|
-
background: #22c55e;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.status-pill.saving .status-dot {
|
|
82
|
-
background: #eab308;
|
|
83
|
-
animation: pulse 1s infinite;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
@keyframes pulse {
|
|
87
|
-
0% {
|
|
88
|
-
opacity: 1;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
50% {
|
|
92
|
-
opacity: 0.5;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
100% {
|
|
96
|
-
opacity: 1;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.tabs {
|
|
101
|
-
display: flex;
|
|
102
|
-
background: var(--bg-body);
|
|
103
|
-
padding: 4px;
|
|
104
|
-
border-radius: 8px;
|
|
105
|
-
gap: 4px;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.tab {
|
|
109
|
-
padding: 6px 16px;
|
|
110
|
-
border: none;
|
|
111
|
-
background: transparent;
|
|
112
|
-
color: var(--text-muted);
|
|
113
|
-
font-size: 0.9rem;
|
|
114
|
-
font-weight: 500;
|
|
115
|
-
cursor: pointer;
|
|
116
|
-
border-radius: 6px;
|
|
117
|
-
transition: all 0.2s;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.tab.active {
|
|
121
|
-
background: var(--bg-panel);
|
|
122
|
-
color: var(--text-main);
|
|
123
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/* Editor Area */
|
|
127
|
-
.workspace {
|
|
128
|
-
flex: 1;
|
|
129
|
-
position: relative;
|
|
130
|
-
overflow: hidden;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/* Text Editor */
|
|
134
|
-
#text-mode {
|
|
135
|
-
display: flex;
|
|
136
|
-
height: 100%;
|
|
137
|
-
background: var(--bg-panel);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.line-numbers {
|
|
141
|
-
width: 50px;
|
|
142
|
-
padding: 24px 0;
|
|
143
|
-
background: var(--bg-body);
|
|
144
|
-
border-right: 1px solid var(--border);
|
|
145
|
-
text-align: right;
|
|
146
|
-
padding-right: 12px;
|
|
147
|
-
color: var(--text-muted);
|
|
148
|
-
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
149
|
-
/* VS Code Font */
|
|
150
|
-
font-size: 15px;
|
|
151
|
-
line-height: 28px;
|
|
152
|
-
/* FIXED HEIGHT */
|
|
153
|
-
user-select: none;
|
|
154
|
-
overflow: hidden;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
textarea {
|
|
158
|
-
flex: 1;
|
|
159
|
-
border: none;
|
|
160
|
-
background: transparent;
|
|
161
|
-
color: var(--text-main);
|
|
162
|
-
padding: 24px;
|
|
163
|
-
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
164
|
-
/* VS Code Font */
|
|
165
|
-
font-size: 15px;
|
|
166
|
-
line-height: 28px;
|
|
167
|
-
/* FIXED HEIGHT MATCHING LINES */
|
|
168
|
-
resize: none;
|
|
169
|
-
outline: none;
|
|
170
|
-
white-space: pre;
|
|
171
|
-
overflow-y: scroll;
|
|
172
|
-
|
|
173
|
-
/* VS Code Style Lines */
|
|
174
|
-
background-image: linear-gradient(transparent 27px, var(--border) 27px);
|
|
175
|
-
background-size: 100% 28px;
|
|
176
|
-
/* Matches line-height */
|
|
177
|
-
background-attachment: local;
|
|
178
|
-
/* Scrolls with text */
|
|
179
|
-
background-position: 0 25px;
|
|
180
|
-
/* Offset to align with text */
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/* Paint Mode */
|
|
184
|
-
#paint-mode {
|
|
185
|
-
display: none;
|
|
186
|
-
height: 100%;
|
|
187
|
-
flex-direction: column;
|
|
188
|
-
background: #f0f0f0;
|
|
189
|
-
/* Always light bg for contrast */
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.toolbar {
|
|
193
|
-
padding: 10px;
|
|
194
|
-
background: #ffffff;
|
|
195
|
-
border-bottom: 1px solid #e5e7eb;
|
|
196
|
-
display: flex;
|
|
197
|
-
gap: 15px;
|
|
198
|
-
align-items: center;
|
|
199
|
-
justify-content: center;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
canvas {
|
|
203
|
-
flex: 1;
|
|
204
|
-
background: #ffffff;
|
|
205
|
-
cursor: crosshair;
|
|
206
|
-
touch-action: none;
|
|
207
|
-
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.05);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
.icon-btn {
|
|
211
|
-
background: transparent;
|
|
212
|
-
border: none;
|
|
213
|
-
color: var(--text-muted);
|
|
214
|
-
cursor: pointer;
|
|
215
|
-
padding: 6px;
|
|
216
|
-
border-radius: 6px;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.icon-btn:hover {
|
|
220
|
-
background: var(--border);
|
|
221
|
-
color: var(--text-main);
|
|
222
|
-
}
|
|
223
|
-
</style>
|
|
224
|
-
</head>
|
|
225
|
-
|
|
226
|
-
<body data-theme="dark">
|
|
227
|
-
|
|
228
|
-
<div class="topbar">
|
|
229
|
-
<div class="status-pill" id="statusPill">
|
|
230
|
-
<div class="status-dot"></div>
|
|
231
|
-
<span id="statusText">Ready</span>
|
|
232
|
-
</div>
|
|
233
|
-
|
|
234
|
-
<div class="tabs">
|
|
235
|
-
<button class="tab active" id="tabText" onclick="app.setMode('text')">Text Editor</button>
|
|
236
|
-
<button class="tab" id="tabPaint" onclick="app.setMode('paint')">Whiteboard</button>
|
|
237
|
-
</div>
|
|
238
|
-
|
|
239
|
-
<button class="icon-btn" onclick="app.toggleTheme()">
|
|
240
|
-
<svg id="themeIcon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
241
|
-
stroke-width="2">
|
|
242
|
-
<circle cx="12" cy="12" r="5" />
|
|
243
|
-
<path
|
|
244
|
-
d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" />
|
|
245
|
-
</svg>
|
|
246
|
-
</button>
|
|
247
|
-
</div>
|
|
248
|
-
|
|
249
|
-
<div class="workspace">
|
|
250
|
-
<div id="text-mode">
|
|
251
|
-
<div class="line-numbers" id="lines">1</div>
|
|
252
|
-
<textarea id="editor" spellcheck="false" placeholder="Start typing..."></textarea>
|
|
253
|
-
</div>
|
|
254
|
-
|
|
255
|
-
<div id="paint-mode">
|
|
256
|
-
<div class="toolbar">
|
|
257
|
-
<input type="color" id="brushColor" value="#000000">
|
|
258
|
-
<input type="range" id="brushSize" min="1" max="20" value="3" style="width: 100px">
|
|
259
|
-
<button onclick="app.clearCanvas()"
|
|
260
|
-
style="padding: 4px 12px; border:1px solid #ddd; background:white; border-radius:4px; cursor:pointer;">Clear
|
|
261
|
-
Board</button>
|
|
262
|
-
</div>
|
|
263
|
-
<canvas id="canvas"></canvas>
|
|
264
|
-
</div>
|
|
265
|
-
</div>
|
|
266
|
-
|
|
267
|
-
<script>
|
|
268
|
-
const app = {
|
|
269
|
-
supabase: null,
|
|
270
|
-
saveTimeout: null,
|
|
271
|
-
|
|
272
|
-
// Config
|
|
273
|
-
storageKey: 'book_data_v4', // New key to force fresh start if needed
|
|
274
|
-
|
|
275
|
-
dom: {
|
|
276
|
-
editor: document.getElementById('editor'),
|
|
277
|
-
lines: document.getElementById('lines'),
|
|
278
|
-
canvas: document.getElementById('canvas'),
|
|
279
|
-
ctx: document.getElementById('canvas').getContext('2d'),
|
|
280
|
-
statusText: document.getElementById('statusText'),
|
|
281
|
-
statusPill: document.getElementById('statusPill')
|
|
282
|
-
},
|
|
283
|
-
|
|
284
|
-
state: {
|
|
285
|
-
mode: 'text',
|
|
286
|
-
theme: 'dark',
|
|
287
|
-
isDirty: false
|
|
288
|
-
},
|
|
289
|
-
|
|
290
|
-
init() {
|
|
291
|
-
// 1. Initial Setup
|
|
292
|
-
this.initSupabase();
|
|
293
|
-
this.setupEvents();
|
|
294
|
-
this.loadSettings();
|
|
295
|
-
|
|
296
|
-
// 2. IMPORTANT: Restore Logic
|
|
297
|
-
// Priority: URL Mode > LocalStorage Mode > Default 'text'
|
|
298
|
-
this.handleRouting();
|
|
299
|
-
|
|
300
|
-
// 3. Data Restoration (Critical for "Refresh" issue)
|
|
301
|
-
// We load LocalStorage immediately so user sees data instantly.
|
|
302
|
-
this.restoreData();
|
|
303
|
-
|
|
304
|
-
// 4. Background Sync (Don't overwrite if Local is newer)
|
|
305
|
-
this.syncCloud();
|
|
306
|
-
},
|
|
307
|
-
|
|
308
|
-
// --- Routing & URL ---
|
|
309
|
-
handleRouting() {
|
|
310
|
-
const params = new URLSearchParams(window.location.search);
|
|
311
|
-
const urlMode = params.get('mode');
|
|
312
|
-
|
|
313
|
-
if (urlMode === 'paint') {
|
|
314
|
-
this.setMode('paint', false);
|
|
315
|
-
} else {
|
|
316
|
-
this.setMode('text', false);
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
|
-
|
|
320
|
-
setMode(mode, updateUrl = true) {
|
|
321
|
-
this.state.mode = mode;
|
|
322
|
-
|
|
323
|
-
// UI
|
|
324
|
-
document.getElementById('text-mode').style.display = mode === 'text' ? 'flex' : 'none';
|
|
325
|
-
document.getElementById('paint-mode').style.display = mode === 'paint' ? 'flex' : 'none';
|
|
326
|
-
|
|
327
|
-
document.getElementById('tabText').classList.toggle('active', mode === 'text');
|
|
328
|
-
document.getElementById('tabPaint').classList.toggle('active', mode === 'paint');
|
|
329
|
-
|
|
330
|
-
if (mode === 'paint') this.resizeCanvas();
|
|
331
|
-
if (mode === 'text') this.updateLines();
|
|
332
|
-
|
|
333
|
-
// URL Update
|
|
334
|
-
if (updateUrl) {
|
|
335
|
-
const newUrl = `${window.location.pathname}?mode=${mode}`;
|
|
336
|
-
window.history.replaceState({ path: newUrl }, '', newUrl);
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
// --- Data Core ---
|
|
341
|
-
|
|
342
|
-
// Instant save to LocalStorage
|
|
343
|
-
saveLocal() {
|
|
344
|
-
const payload = {
|
|
345
|
-
text: this.dom.editor.value,
|
|
346
|
-
canvas: this.dom.canvas.toDataURL(),
|
|
347
|
-
updated: Date.now()
|
|
348
|
-
};
|
|
349
|
-
localStorage.setItem(this.storageKey, JSON.stringify(payload));
|
|
350
|
-
this.setStatus('Saved', false);
|
|
351
|
-
},
|
|
352
|
-
|
|
353
|
-
restoreData() {
|
|
354
|
-
const raw = localStorage.getItem(this.storageKey);
|
|
355
|
-
if (raw) {
|
|
356
|
-
const data = JSON.parse(raw);
|
|
357
|
-
if (data.text) {
|
|
358
|
-
this.dom.editor.value = data.text;
|
|
359
|
-
this.updateLines();
|
|
360
|
-
}
|
|
361
|
-
if (data.canvas) {
|
|
362
|
-
const img = new Image();
|
|
363
|
-
img.onload = () => this.dom.ctx.drawImage(img, 0, 0);
|
|
364
|
-
img.src = data.canvas;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
},
|
|
368
|
-
|
|
369
|
-
async syncCloud() {
|
|
370
|
-
if (!this.supabase) return;
|
|
371
|
-
|
|
372
|
-
// FETCH Cloud Data
|
|
373
|
-
const { data, error } = await this.supabase
|
|
374
|
-
.from('book_content')
|
|
375
|
-
.select('*')
|
|
376
|
-
.eq('id', 1)
|
|
377
|
-
.single();
|
|
378
|
-
|
|
379
|
-
if (error || !data) return;
|
|
380
|
-
|
|
381
|
-
const cloudText = data.text_content || '';
|
|
382
|
-
const localRaw = localStorage.getItem(this.storageKey);
|
|
383
|
-
const localData = localRaw ? JSON.parse(localRaw) : { text: '' };
|
|
384
|
-
|
|
385
|
-
// CONFLICT RESOLUTION:
|
|
386
|
-
// If Local is empty but Cloud has data -> Use Cloud
|
|
387
|
-
// If Both have data -> Use Local (Assuming user just refreshed active session)
|
|
388
|
-
|
|
389
|
-
if (!localData.text && cloudText) {
|
|
390
|
-
this.dom.editor.value = cloudText;
|
|
391
|
-
this.updateLines();
|
|
392
|
-
this.saveLocal(); // Update local copy
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// If Local has data, we trust it more for active session.
|
|
396
|
-
// We do trigger a save to cloud to ensure cloud matches eventually.
|
|
397
|
-
if (localData.text && localData.text !== cloudText) {
|
|
398
|
-
this.triggerSave();
|
|
399
|
-
}
|
|
400
|
-
},
|
|
401
|
-
|
|
402
|
-
triggerSave() {
|
|
403
|
-
// Visual
|
|
404
|
-
this.setStatus('Saving...', true);
|
|
405
|
-
|
|
406
|
-
// Instant Local Save
|
|
407
|
-
this.saveLocal();
|
|
408
|
-
|
|
409
|
-
// Debounced Cloud Save
|
|
410
|
-
if (this.saveTimeout) clearTimeout(this.saveTimeout);
|
|
411
|
-
this.saveTimeout = setTimeout(() => this.saveToCloud(), 1000);
|
|
412
|
-
},
|
|
413
|
-
|
|
414
|
-
async saveToCloud() {
|
|
415
|
-
if (!this.supabase) return;
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
await this.supabase.from('book_content').upsert({
|
|
419
|
-
id: 1,
|
|
420
|
-
text_content: this.dom.editor.value,
|
|
421
|
-
canvas_data: this.dom.canvas.toDataURL(),
|
|
422
|
-
updated_at: new Date().toISOString()
|
|
423
|
-
});
|
|
424
|
-
this.setStatus('Saved to Cloud', false);
|
|
425
|
-
} catch (e) {
|
|
426
|
-
console.error(e);
|
|
427
|
-
this.setStatus('Saved (Offline)', false);
|
|
428
|
-
}
|
|
429
|
-
},
|
|
430
|
-
|
|
431
|
-
// --- UI/UX Utils ---
|
|
432
|
-
|
|
433
|
-
setupEvents() {
|
|
434
|
-
this.dom.editor.addEventListener('input', () => {
|
|
435
|
-
this.updateLines();
|
|
436
|
-
this.triggerSave();
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
this.dom.editor.addEventListener('scroll', () => {
|
|
440
|
-
this.dom.lines.scrollTop = this.dom.editor.scrollTop;
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
window.addEventListener('resize', () => {
|
|
444
|
-
if (this.state.mode === 'paint') this.resizeCanvas();
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
// Drawing Events
|
|
448
|
-
const c = this.dom.canvas;
|
|
449
|
-
c.addEventListener('mousedown', e => this.drawStart(e));
|
|
450
|
-
c.addEventListener('mousemove', e => this.drawMove(e));
|
|
451
|
-
c.addEventListener('mouseup', () => this.drawEnd());
|
|
452
|
-
c.addEventListener('touchstart', e => { e.preventDefault(); this.drawStart(e.touches[0]); });
|
|
453
|
-
c.addEventListener('touchmove', e => { e.preventDefault(); this.drawMove(e.touches[0]); });
|
|
454
|
-
c.addEventListener('touchend', () => this.drawEnd());
|
|
455
|
-
},
|
|
456
|
-
|
|
457
|
-
updateLines() {
|
|
458
|
-
const count = Math.max(this.dom.editor.value.split('\n').length, 100);
|
|
459
|
-
this.dom.lines.innerHTML = Array.from({ length: count }, (_, i) => i + 1).join('<br>');
|
|
460
|
-
},
|
|
461
|
-
|
|
462
|
-
setStatus(text, isSaving) {
|
|
463
|
-
this.dom.statusText.innerText = text;
|
|
464
|
-
this.dom.statusPill.className = isSaving ? 'status-pill saving' : 'status-pill';
|
|
465
|
-
},
|
|
466
|
-
|
|
467
|
-
toggleTheme() {
|
|
468
|
-
this.state.theme = this.state.theme === 'dark' ? 'light' : 'dark';
|
|
469
|
-
document.body.setAttribute('data-theme', this.state.theme);
|
|
470
|
-
localStorage.setItem('book_theme_pref', this.state.theme);
|
|
471
|
-
},
|
|
472
|
-
|
|
473
|
-
loadSettings() {
|
|
474
|
-
const theme = localStorage.getItem('book_theme_pref') || 'dark';
|
|
475
|
-
this.state.theme = theme;
|
|
476
|
-
document.body.setAttribute('data-theme', theme);
|
|
477
|
-
},
|
|
478
|
-
|
|
479
|
-
// --- Canvas ---
|
|
480
|
-
isDrawing: false,
|
|
481
|
-
drawStart(e) { this.isDrawing = true; this.drawMove(e); },
|
|
482
|
-
drawMove(e) {
|
|
483
|
-
if (!this.isDrawing) return;
|
|
484
|
-
const rect = this.dom.canvas.getBoundingClientRect();
|
|
485
|
-
const ctx = this.dom.ctx;
|
|
486
|
-
ctx.lineWidth = document.getElementById('brushSize').value;
|
|
487
|
-
ctx.lineCap = 'round';
|
|
488
|
-
ctx.strokeStyle = document.getElementById('brushColor').value;
|
|
489
|
-
ctx.lineTo(e.clientX - rect.left, e.clientY - rect.top);
|
|
490
|
-
ctx.stroke();
|
|
491
|
-
ctx.beginPath();
|
|
492
|
-
ctx.moveTo(e.clientX - rect.left, e.clientY - rect.top);
|
|
493
|
-
},
|
|
494
|
-
drawEnd() {
|
|
495
|
-
this.isDrawing = false;
|
|
496
|
-
this.dom.ctx.beginPath();
|
|
497
|
-
this.triggerSave();
|
|
498
|
-
},
|
|
499
|
-
resizeCanvas() {
|
|
500
|
-
const c = this.dom.canvas;
|
|
501
|
-
if (c.width === c.offsetWidth) return;
|
|
502
|
-
const data = c.toDataURL();
|
|
503
|
-
c.width = c.offsetWidth;
|
|
504
|
-
c.height = c.offsetHeight;
|
|
505
|
-
const img = new Image();
|
|
506
|
-
img.onload = () => this.dom.ctx.drawImage(img, 0, 0);
|
|
507
|
-
img.src = data;
|
|
508
|
-
},
|
|
509
|
-
clearCanvas() {
|
|
510
|
-
this.dom.ctx.clearRect(0, 0, this.dom.canvas.width, this.dom.canvas.height);
|
|
511
|
-
this.triggerSave();
|
|
512
|
-
},
|
|
513
|
-
|
|
514
|
-
initSupabase() {
|
|
515
|
-
try {
|
|
516
|
-
const u = 'https://kyfhibapmdcnlyjuenod.supabase.co';
|
|
517
|
-
const k = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt5ZmhpYmFwbWRjbmx5anVlbm9kIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Mzg5MzgzMzgsImV4cCI6MjA1NDUxNDMzOH0.xwgFAEVR6w-sGl0wIG-w3A_i5AJDlsN';
|
|
518
|
-
if (window.supabase) this.supabase = window.supabase.createClient(u, k);
|
|
519
|
-
} catch (e) { }
|
|
520
|
-
}
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
window.onload = () => app.init();
|
|
524
|
-
</script>
|
|
525
|
-
</body>
|
|
526
|
-
|
|
527
|
-
</html>
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"rules": {
|
|
3
|
-
"status": {
|
|
4
|
-
"$sessionId": {
|
|
5
|
-
".read": true,
|
|
6
|
-
".write": true
|
|
7
|
-
}
|
|
8
|
-
},
|
|
9
|
-
"visit_history": {
|
|
10
|
-
".read": "auth != null && (auth.token.email === 'rathodtushar1442@gmail.com')",
|
|
11
|
-
".write": "auth != null"
|
|
12
|
-
},
|
|
13
|
-
".read": false,
|
|
14
|
-
".write": false
|
|
15
|
-
}
|
|
16
|
-
}
|