leadingbridge commited on
Commit
ba66fd7
·
verified ·
1 Parent(s): bb6842a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +632 -94
app.py CHANGED
@@ -1,94 +1,632 @@
1
- <p><em>Frufru 1 Day Amane Brown </em>features a sweet and plump brown design that instantly brightens your look with a soft, flirty charm. With a colored diameter of 13.9mm, it provides noticeable yet natural enlargement for rounder, more defined eyes. The lens combines a delicate thin rim&nbsp; with a sheer brown tone, creating a fresh, radiant finish that makes your eyes pop the moment you put them on. Melo Brown delivers that ultimate irresistibly cute and subtly alluring eye look—perfect for achieving a lovable, sparkling gaze with everyday ease.</p>
2
- <p><span style="color: #000000;"><strong><u>Candy Magic 1 Day Contacts Information</u></strong></span></p>
3
- <table border="1">
4
- <tbody>
5
- <tr>
6
- <td><span>Product Name:</span></td>
7
- <td><span>Frufru 1 Day Amane Brown Contact Lenses 10 pack</span></td>
8
- </tr>
9
- <tr>
10
- <td><span>Water Content:</span></td>
11
- <td>55%</td>
12
- </tr>
13
- <tr>
14
- <td><span>Base Curve Radius:</span></td>
15
- <td>8.6mm</td>
16
- </tr>
17
- <tr>
18
- <td><span>Diameter:</span></td>
19
- <td><span>14.50 mm (</span><span>G.DIA: </span><span>13.90mm)</span></td>
20
- </tr>
21
- <tr>
22
- <td><span>Package:</span></td>
23
- <td>Each box contains 10 pieces of daily contacts with same prescription. Please order more than 1 box if you have different prescription for each eye.</td>
24
- </tr>
25
- <tr>
26
- <td><span>Using period:</span></td>
27
- <td><span>Daily</span></td>
28
- </tr>
29
- <tr>
30
- <td><span>Contact Lenses Prescription:</span></td>
31
- <td><span>Plano, -1.00D to -8.00D</span></td>
32
- </tr>
33
- <tr>
34
- <td><span>Price Unit:</span></td>
35
- <td><span>Per Box</span></td>
36
- </tr>
37
- </tbody>
38
- </table>
39
- <p>&nbsp;</p>
40
- <p><span style="text-decoration: underline;"><strong>About Candy Magic</strong></span><br><br><span>Candy Magic is a famous contact lense manufacturer based in Japan. As one of the </span><span>Japan</span><span> leader in eye care, </span><span>Candy Magic</span><span> develops and manufactures innovative medicines and devices to serve the full life cycle of eye care needs. The </span><span>commercial image model for </span><span>Candy Magic is the famous Japanese actor Ms. </span><span>Airi Suzuki.</span><br><br></p>
41
- <p><strong><u>Instructions on Frufru 1 Day Amane Brown Contact Lenses</u></strong></p>
42
- <p>Frufru 1 Day Amane Brown contact lenses are daily wear soft contact lenses. Daily lenses are that once a lens has been removed from the eye, it must be thrown away.</p>
43
- <ol>
44
- <li>Keep your fingernails short and smooth to avoid scratching your eye or your Frufru 1 Day Amane Brown contacts.</li>
45
- <li>Always wash your hands with soap and rinse carefully before handling your Frufru 1 Day Amane Brown color contact lenses. Lenses may become contaminated if you do not wash your hands properly.</li>
46
- <li>Always handle your Frufru 1 Day Amane Brown color contacts somewhere that is well-lit. clean and sterile.</li>
47
- <li>Your Frufru 1 Day Amane Brown colour contact lenses may become damaged depending on how you handle them.</li>
48
- </ol>
49
- <p><strong><u>Checking the lenses and blister pack packaging</u></strong></p>
50
- <ol>
51
- <li>Check which Frufru 1 Day Amane Brown colour contacts are for the right eye and which lenses are for the left eye.</li>
52
- <li>Check if there is anything wrong with the Frufru 1 Day Amane Brown colored contact lenses and blister pack packaging before opening. If there is anything wrong, for example, if there is any damage or leakage, do not open and contact the point of purchase.</li>
53
- </ol>
54
- <p><strong><u>Removing the blister pack packaging</u></strong></p>
55
- <ol>
56
- <li>Break off the blister pack packaging of the Frufru 1 Day Amane Brown colour contacts by folding down the sides.</li>
57
- <li>Remove the label, keeping a firm hold on the blister pack and the label. When doing this, be careful not to cut your hand on the blister pack or label. Also note that the Frufru 1 Day Amane Brown color contacts storage solution inside the blister pack may splash upon removal of the label.</li>
58
- <li>Remove the Frufru 1 Day Amane Brown colored contacts from the blister pack packaging by sticking the Frufru 1 Day Amane Brown color contact lenses to the pad of your index finger. Alternatively, turn the blister pack packaging upside down and empty the lens together with the storage solution into the palm of your hand. Take care, because if you dig your nail into the lens or scrape the lens on the rim of the blister pack packaging on removal, the lens may be damaged.</li>
59
- </ol>
60
- <p><strong><u>Checking the Frufru 1 Day Amane Brown Contact Lenses</u></strong></p>
61
- <ol>
62
- <li>Hold the edges of the Frufru 1 Day Amane Brown contact lenses from the side and gently move up and down to remove the liquid.</li>
63
- <li>Put the concave surface on the index finger of your dominant hand and check which is the right side and which is the wrong side of the Frufru 1 Day Amane Brown contacts. A lens has a right side and a wrong side.</li>
64
- </ol>
65
- <p>Ensure that the Frufru 1 Day Amane Brown color contacts is not inside out. Place the lens on your right index finger and verify the shape.</p>
66
- <ol start="3">
67
- <li>Check if there is anything wrong with the Frufru 1 Day Amane Brown colour contact lenses, for example, deformation, discolouration, foreign matter, damage, scratches and dirt, and do not wear the lens if there is anything wrong with it.</li>
68
- </ol>
69
- <p><strong><u>Inserting Frufru 1 Day Amane Brown Contact Lenses</u></strong></p>
70
- <ol>
71
- <li>Put the Frufru 1 Day Amane Brown contact lenses onto your finger, and, with the middle finger of the same hand, pull down your lower eyelid. With the index finger of your other hand, pull up your top eyelid. If you put your finger on your eyelash line, you can open your eyes wide.</li>
72
- <li>Look in the mirror so that your cornea comes into the center of your eye. Slowly bring the Frufru 1 Day Amane Brown colour contact lenses close to your eye and put it gently in place, still looking in the mirror. If you glance upwards when doing this, the lens will not be properly centered on your cornea.</li>
73
- <li>When you have placed the Frufru 1 Day Amane Brown colour contacts properly on the cornea, slowly remove your index finger and then remove your fingers which had been holding your eyelids, starting with the bottom lid first and then the top lid. If you remove your fingers quickly and blink your eye shut. the lens may change position or fall out.</li>
74
- <li>Check in the mirror that the Frufru 1 Day Amane Brown colored contact lenses is on top of your cornea and, covering the opposite eye with your hand, check your sight in your right and left eyes.</li>
75
- <li>Follow the same procedure to fit the Frufru 1 Day Amane Brown colored contacts in your other eye.</li>
76
- </ol>
77
- <p><strong><u>Removing the Frufru 1 Day Amane Brown Contact Lenses</u></strong></p>
78
- <ol>
79
- <li>Check the position of the Frufru 1 Day Amane Brown contact lenses in the mirror.</li>
80
- <li>Looking straight in the mirror, hold the mirror upward and look at the eye reflected in the mirror.</li>
81
- <li>With your middle finger, slowly pull your lower lid down, and with the index finger of the same hand, move the Frufru 1 Day Amane Brown contacts down from the cornea.</li>
82
- <li>In this position, use the pads of your thumb and index finger to gently take hold of the bottom of the Frufru 1 Day Amane Brown color contact lenses.</li>
83
- </ol>
84
- <p>To avoid scratching your eye, take care not to touch your eye directly with the tips of your fingers or your fingernails.</p>
85
- <p>If you are unable to remove the Frufru 1 Day Amane Brown colored contact lenses, blink several times and then try again.</p>
86
- <p><strong><u>Safety usage of Frufru 1 Day Amane Brown Contact Lenses</u></strong></p>
87
- <p>If you have inadvertently worn a Frufru 1 Day Amane Brown colored contacts which is damaged or otherwise faulty, or if a lens becomes damaged while you are wearing it, remove the lens immediately and consult your eye care practitioner immediately, regardless of whether you have any subjective symptoms.</p>
88
- <p>Until you are used to your lenses, you may experience symptoms such as slight discomfort, slightly bloodshot eyes, unstable vision, watering eyes or itchy eyes. Symptoms vary from person to person but will gradually diminish as you become used to your lenses. However, if you experience acute symptoms or if symptoms persist, stop wearing your Frufru 1 Day Amane Brown contact lenses and promptly consult your eye care practitioner.</p>
89
- <p>&nbsp;</p>
90
- <div style="text-align: start;"><img src="https://cdn.shopify.com/s/files/1/0553/1962/5919/files/candy-magic-1-day-melo-brown-blue-light-barrier-contact-lenses-10-pack-1_480x480.jpg?v=1770918126" alt="Frufru 1 Day Amane Brown blue light cut contact lenses 13.9mm colored diameter natural brown" style="float: none;"></div>
91
- <div style="text-align: start;"><img src="https://cdn.shopify.com/s/files/1/0553/1962/5919/files/Candy-magic-1-day-melo-brown-blue-light-barrier-contact-lenses-10-pack-3_480x480.jpg?v=1770918126" alt="Frufru 1 Day Amane Brown model wearing daily brown contact lenses natural enlargement effect" style="float: none;"></div>
92
- <div style="text-align: start;"><img src="https://cdn.shopify.com/s/files/1/0553/1962/5919/files/Candy-magic-1-day-melo-brown-blue-light-barrier-contact-lenses-10-pack-4_480x480.jpg?v=1770918126" alt="Frufru 1 Day Amane Brown comparison clear lens vs wearing effect 13.9mm color diameter" style="float: none;"></div>
93
- <div style="text-align: start;"><img src="https://cdn.shopify.com/s/files/1/0553/1962/5919/files/Candy-magic-1-day-melo-brown-blue-light-barrier-contact-lenses-10-pack-5_480x480.jpg?v=1770918126" alt="Frufru 1 Day Amane Brown upgraded blue light barrier contact lenses new transparent design" style="float: none;"></div>
94
- <p><iframe class="video" src="https://www.youtube.com/embed/eGZCCIDHIuo"></iframe></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
+ import gradio as gr
4
+
5
+
6
+ def replace_first(
7
+ text: str,
8
+ find_text: str,
9
+ replace_text: str,
10
+ case_sensitive: bool,
11
+ ) -> tuple[str, str]:
12
+ text = text or ""
13
+
14
+ if not find_text:
15
+ return text, "Enter text to find."
16
+
17
+ if case_sensitive:
18
+ position = text.find(find_text)
19
+
20
+ if position == -1:
21
+ return text, "Cannot find the specified text."
22
+
23
+ updated_text = (
24
+ text[:position]
25
+ + replace_text
26
+ + text[position + len(find_text):]
27
+ )
28
+ else:
29
+ pattern = re.compile(re.escape(find_text), re.IGNORECASE)
30
+ match = pattern.search(text)
31
+
32
+ if not match:
33
+ return text, "Cannot find the specified text."
34
+
35
+ updated_text = (
36
+ text[:match.start()]
37
+ + replace_text
38
+ + text[match.end():]
39
+ )
40
+
41
+ return updated_text, "Replaced the first match."
42
+
43
+
44
+ def replace_all(
45
+ text: str,
46
+ find_text: str,
47
+ replace_text: str,
48
+ case_sensitive: bool,
49
+ ) -> tuple[str, str]:
50
+ text = text or ""
51
+
52
+ if not find_text:
53
+ return text, "Enter text to find."
54
+
55
+ if case_sensitive:
56
+ match_count = text.count(find_text)
57
+
58
+ if match_count == 0:
59
+ return text, "Cannot find the specified text."
60
+
61
+ updated_text = text.replace(find_text, replace_text)
62
+ else:
63
+ pattern = re.compile(re.escape(find_text), re.IGNORECASE)
64
+ match_count = len(pattern.findall(text))
65
+
66
+ if match_count == 0:
67
+ return text, "Cannot find the specified text."
68
+
69
+ updated_text = pattern.sub(lambda _: replace_text, text)
70
+
71
+ word = "match" if match_count == 1 else "matches"
72
+
73
+ return updated_text, f"Replaced {match_count} {word}."
74
+
75
+
76
+ def clear_editor() -> tuple[str, str, str, str]:
77
+ return "", "", "", "Editor cleared."
78
+
79
+
80
+ CSS = """
81
+ .gradio-container {
82
+ max-width: none !important;
83
+ width: 99% !important;
84
+ margin: 0 auto !important;
85
+ padding-left: 12px !important;
86
+ padding-right: 12px !important;
87
+ }
88
+
89
+ #main-layout {
90
+ gap: 14px !important;
91
+ }
92
+
93
+ #editor-column {
94
+ min-width: 0 !important;
95
+ }
96
+
97
+ #main-editor textarea {
98
+ min-height: 760px !important;
99
+ height: 82vh !important;
100
+ resize: vertical !important;
101
+
102
+ font-family:
103
+ ui-monospace,
104
+ SFMono-Regular,
105
+ Menlo,
106
+ Monaco,
107
+ Consolas,
108
+ "Liberation Mono",
109
+ monospace !important;
110
+
111
+ font-size: 16px !important;
112
+ line-height: 1.55 !important;
113
+ white-space: pre-wrap !important;
114
+ tab-size: 4 !important;
115
+ }
116
+
117
+ #side-panel {
118
+ min-width: 260px !important;
119
+ max-width: 290px !important;
120
+ }
121
+
122
+ #side-panel button {
123
+ min-height: 42px !important;
124
+ }
125
+
126
+ #status-box textarea {
127
+ min-height: 70px !important;
128
+ }
129
+
130
+ @media (max-width: 900px) {
131
+ #side-panel {
132
+ max-width: none !important;
133
+ }
134
+
135
+ #main-editor textarea {
136
+ height: 65vh !important;
137
+ }
138
+ }
139
+ """
140
+
141
+
142
+ HEAD = """
143
+ <script>
144
+ let lastFindText = "";
145
+ let lastCaseSensitive = false;
146
+ let lastMatchEnd = 0;
147
+
148
+ function getEditor() {
149
+ return document.querySelector("#main-editor textarea");
150
+ }
151
+
152
+ function getFindInput() {
153
+ return document.querySelector("#find-input textarea");
154
+ }
155
+
156
+ function getCaseSensitiveCheckbox() {
157
+ return document.querySelector(
158
+ "#case-sensitive input[type='checkbox']"
159
+ );
160
+ }
161
+
162
+ function setStatus(message) {
163
+ const statusBox = document.querySelector("#status-box textarea");
164
+
165
+ if (!statusBox) {
166
+ return;
167
+ }
168
+
169
+ const nativeSetter = Object.getOwnPropertyDescriptor(
170
+ window.HTMLTextAreaElement.prototype,
171
+ "value"
172
+ ).set;
173
+
174
+ nativeSetter.call(statusBox, message);
175
+ statusBox.dispatchEvent(
176
+ new Event("input", {
177
+ bubbles: true
178
+ })
179
+ );
180
+ }
181
+
182
+ function resetFindPosition() {
183
+ lastFindText = "";
184
+ lastMatchEnd = 0;
185
+ }
186
+
187
+ function findNext() {
188
+ const editor = getEditor();
189
+ const findInput = getFindInput();
190
+ const caseCheckbox = getCaseSensitiveCheckbox();
191
+
192
+ if (!editor || !findInput) {
193
+ return;
194
+ }
195
+
196
+ const originalText = editor.value || "";
197
+ const findText = findInput.value || "";
198
+ const caseSensitive = caseCheckbox
199
+ ? caseCheckbox.checked
200
+ : false;
201
+
202
+ if (!findText) {
203
+ setStatus("Enter text to find.");
204
+ findInput.focus();
205
+ return;
206
+ }
207
+
208
+ const searchText = caseSensitive
209
+ ? originalText
210
+ : originalText.toLowerCase();
211
+
212
+ const searchTerm = caseSensitive
213
+ ? findText
214
+ : findText.toLowerCase();
215
+
216
+ /*
217
+ When the search term or case-sensitive option changes,
218
+ begin from the editor's current cursor position.
219
+ */
220
+ if (
221
+ findText !== lastFindText ||
222
+ caseSensitive !== lastCaseSensitive
223
+ ) {
224
+ lastFindText = findText;
225
+ lastCaseSensitive = caseSensitive;
226
+ lastMatchEnd = editor.selectionEnd || 0;
227
+ } else {
228
+ /*
229
+ Continue after the currently selected match.
230
+ */
231
+ lastMatchEnd = editor.selectionEnd || lastMatchEnd;
232
+ }
233
+
234
+ let position = searchText.indexOf(
235
+ searchTerm,
236
+ lastMatchEnd
237
+ );
238
+
239
+ let wrapped = false;
240
+
241
+ /*
242
+ Like Notepad, return to the beginning when the end
243
+ of the text is reached.
244
+ */
245
+ if (position === -1 && lastMatchEnd > 0) {
246
+ position = searchText.indexOf(searchTerm, 0);
247
+ wrapped = position !== -1;
248
+ }
249
+
250
+ if (position === -1) {
251
+ setStatus(
252
+ 'Cannot find "' + findText + '".'
253
+ );
254
+ return;
255
+ }
256
+
257
+ const matchEnd = position + findText.length;
258
+
259
+ editor.focus();
260
+ editor.setSelectionRange(position, matchEnd);
261
+
262
+ lastMatchEnd = matchEnd;
263
+
264
+ /*
265
+ Scroll the selected match into view. A temporary hidden
266
+ mirror element is used to estimate the vertical position.
267
+ */
268
+ scrollSelectionIntoView(
269
+ editor,
270
+ position,
271
+ matchEnd
272
+ );
273
+
274
+ if (wrapped) {
275
+ setStatus(
276
+ "Reached the end. Continued from the beginning."
277
+ );
278
+ } else {
279
+ setStatus(
280
+ "Match found at character " + (position + 1) + "."
281
+ );
282
+ }
283
+ }
284
+
285
+ function scrollSelectionIntoView(
286
+ textarea,
287
+ selectionStart,
288
+ selectionEnd
289
+ ) {
290
+ const mirror = document.createElement("div");
291
+ const style = window.getComputedStyle(textarea);
292
+
293
+ const copiedProperties = [
294
+ "boxSizing",
295
+ "width",
296
+ "height",
297
+ "overflowX",
298
+ "overflowY",
299
+ "borderTopWidth",
300
+ "borderRightWidth",
301
+ "borderBottomWidth",
302
+ "borderLeftWidth",
303
+ "paddingTop",
304
+ "paddingRight",
305
+ "paddingBottom",
306
+ "paddingLeft",
307
+ "fontStyle",
308
+ "fontVariant",
309
+ "fontWeight",
310
+ "fontStretch",
311
+ "fontSize",
312
+ "fontSizeAdjust",
313
+ "lineHeight",
314
+ "fontFamily",
315
+ "textAlign",
316
+ "textTransform",
317
+ "textIndent",
318
+ "textDecoration",
319
+ "letterSpacing",
320
+ "wordSpacing",
321
+ "tabSize"
322
+ ];
323
+
324
+ copiedProperties.forEach((property) => {
325
+ mirror.style[property] = style[property];
326
+ });
327
+
328
+ mirror.style.position = "absolute";
329
+ mirror.style.visibility = "hidden";
330
+ mirror.style.whiteSpace = "pre-wrap";
331
+ mirror.style.wordWrap = "break-word";
332
+ mirror.style.top = "0";
333
+ mirror.style.left = "-9999px";
334
+ mirror.style.height = "auto";
335
+
336
+ const textBeforeMatch = textarea.value.substring(
337
+ 0,
338
+ selectionStart
339
+ );
340
+
341
+ mirror.textContent = textBeforeMatch;
342
+
343
+ const marker = document.createElement("span");
344
+ marker.textContent =
345
+ textarea.value.substring(
346
+ selectionStart,
347
+ selectionEnd
348
+ ) || ".";
349
+
350
+ mirror.appendChild(marker);
351
+ document.body.appendChild(mirror);
352
+
353
+ const targetTop = marker.offsetTop;
354
+ const visibleTop = textarea.scrollTop;
355
+ const visibleBottom =
356
+ textarea.scrollTop + textarea.clientHeight;
357
+
358
+ if (
359
+ targetTop < visibleTop ||
360
+ targetTop > visibleBottom - 40
361
+ ) {
362
+ textarea.scrollTop = Math.max(
363
+ 0,
364
+ targetTop - textarea.clientHeight / 2
365
+ );
366
+ }
367
+
368
+ document.body.removeChild(mirror);
369
+ }
370
+
371
+ /*
372
+ Keyboard shortcuts:
373
+
374
+ F3 Find next
375
+ Enter Find next while Find box is focused
376
+ Ctrl/Cmd + F Focus the Find box
377
+ Escape Return focus to the editor
378
+ */
379
+ document.addEventListener("keydown", function(event) {
380
+ const findInput = getFindInput();
381
+ const editor = getEditor();
382
+
383
+ if (event.key === "F3") {
384
+ event.preventDefault();
385
+ findNext();
386
+ return;
387
+ }
388
+
389
+ if (
390
+ (event.ctrlKey || event.metaKey) &&
391
+ event.key.toLowerCase() === "f"
392
+ ) {
393
+ event.preventDefault();
394
+
395
+ if (findInput) {
396
+ findInput.focus();
397
+ findInput.select();
398
+ }
399
+
400
+ return;
401
+ }
402
+
403
+ if (
404
+ event.key === "Enter" &&
405
+ document.activeElement === findInput
406
+ ) {
407
+ event.preventDefault();
408
+ findNext();
409
+ return;
410
+ }
411
+
412
+ if (event.key === "Escape" && editor) {
413
+ editor.focus();
414
+ }
415
+ });
416
+
417
+ /*
418
+ Reset search continuation when the search text changes.
419
+ */
420
+ document.addEventListener("input", function(event) {
421
+ if (
422
+ event.target === getFindInput() ||
423
+ event.target === getEditor()
424
+ ) {
425
+ resetFindPosition();
426
+ }
427
+ });
428
+
429
+ /*
430
+ Expose the function so the Gradio button can call it.
431
+ */
432
+ window.findNext = findNext;
433
+ </script>
434
+ """
435
+
436
+
437
+ with gr.Blocks(
438
+ title="Plain Text Editor",
439
+ css=CSS,
440
+ head=HEAD,
441
+ ) as demo:
442
+ gr.Markdown("# Plain Text Editor")
443
+
444
+ with gr.Row(
445
+ equal_height=False,
446
+ elem_id="main-layout",
447
+ ):
448
+ with gr.Column(
449
+ scale=10,
450
+ min_width=700,
451
+ elem_id="editor-column",
452
+ ):
453
+ editor = gr.Textbox(
454
+ label="Text",
455
+ placeholder="Type or paste your plain text here...",
456
+ lines=30,
457
+ max_lines=3000,
458
+ elem_id="main-editor",
459
+ buttons=["copy"],
460
+ autofocus=True,
461
+ )
462
+
463
+ with gr.Column(
464
+ scale=2,
465
+ min_width=260,
466
+ elem_id="side-panel",
467
+ ):
468
+ paste_button = gr.Button(
469
+ "Paste",
470
+ variant="primary",
471
+ )
472
+
473
+ clear_button = gr.Button(
474
+ "Clear Text",
475
+ variant="stop",
476
+ )
477
+
478
+ gr.Markdown("### Find and Replace")
479
+
480
+ find_input = gr.Textbox(
481
+ label="Find",
482
+ placeholder="Text to find",
483
+ lines=1,
484
+ elem_id="find-input",
485
+ )
486
+
487
+ find_next_button = gr.Button(
488
+ "Find Next (F3)",
489
+ variant="primary",
490
+ )
491
+
492
+ replace_input = gr.Textbox(
493
+ label="Replace with",
494
+ placeholder="Replacement text",
495
+ lines=1,
496
+ )
497
+
498
+ case_sensitive = gr.Checkbox(
499
+ label="Match case",
500
+ value=False,
501
+ elem_id="case-sensitive",
502
+ )
503
+
504
+ replace_first_button = gr.Button(
505
+ "Replace First"
506
+ )
507
+
508
+ replace_all_button = gr.Button(
509
+ "Replace All"
510
+ )
511
+
512
+ status = gr.Textbox(
513
+ label="Status",
514
+ value="Ready.",
515
+ interactive=False,
516
+ elem_id="status-box",
517
+ )
518
+
519
+ paste_button.click(
520
+ fn=None,
521
+ inputs=None,
522
+ outputs=editor,
523
+ js="""
524
+ async () => {
525
+ const editor = document.querySelector(
526
+ "#main-editor textarea"
527
+ );
528
+
529
+ if (!editor) {
530
+ return "";
531
+ }
532
+
533
+ try {
534
+ const clipboardText =
535
+ await navigator.clipboard.readText();
536
+
537
+ const currentText = editor.value || "";
538
+ const start =
539
+ editor.selectionStart ?? currentText.length;
540
+ const end =
541
+ editor.selectionEnd ?? currentText.length;
542
+
543
+ const updatedText =
544
+ currentText.slice(0, start) +
545
+ clipboardText +
546
+ currentText.slice(end);
547
+
548
+ setTimeout(() => {
549
+ const updatedEditor =
550
+ document.querySelector(
551
+ "#main-editor textarea"
552
+ );
553
+
554
+ if (updatedEditor) {
555
+ const newCursor =
556
+ start + clipboardText.length;
557
+
558
+ updatedEditor.focus();
559
+ updatedEditor.setSelectionRange(
560
+ newCursor,
561
+ newCursor
562
+ );
563
+ }
564
+ }, 100);
565
+
566
+ return updatedText;
567
+ } catch (error) {
568
+ alert(
569
+ "Clipboard access was blocked. " +
570
+ "Please allow clipboard permission, " +
571
+ "or use Ctrl+V / Cmd+V."
572
+ );
573
+
574
+ return editor.value || "";
575
+ }
576
+ }
577
+ """,
578
+ )
579
+
580
+ find_next_button.click(
581
+ fn=None,
582
+ inputs=None,
583
+ outputs=None,
584
+ js="""
585
+ () => {
586
+ window.findNext();
587
+ }
588
+ """,
589
+ )
590
+
591
+ replace_first_button.click(
592
+ fn=replace_first,
593
+ inputs=[
594
+ editor,
595
+ find_input,
596
+ replace_input,
597
+ case_sensitive,
598
+ ],
599
+ outputs=[
600
+ editor,
601
+ status,
602
+ ],
603
+ )
604
+
605
+ replace_all_button.click(
606
+ fn=replace_all,
607
+ inputs=[
608
+ editor,
609
+ find_input,
610
+ replace_input,
611
+ case_sensitive,
612
+ ],
613
+ outputs=[
614
+ editor,
615
+ status,
616
+ ],
617
+ )
618
+
619
+ clear_button.click(
620
+ fn=clear_editor,
621
+ inputs=None,
622
+ outputs=[
623
+ editor,
624
+ find_input,
625
+ replace_input,
626
+ status,
627
+ ],
628
+ )
629
+
630
+
631
+ if __name__ == "__main__":
632
+ demo.launch()