GameSentenceMiner 2.19.16__py3-none-any.whl → 2.20.0__py3-none-any.whl
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 GameSentenceMiner might be problematic. Click here for more details.
- GameSentenceMiner/__init__.py +39 -0
- GameSentenceMiner/anki.py +6 -3
- GameSentenceMiner/gametext.py +13 -2
- GameSentenceMiner/gsm.py +40 -3
- GameSentenceMiner/locales/en_us.json +4 -0
- GameSentenceMiner/locales/ja_jp.json +4 -0
- GameSentenceMiner/locales/zh_cn.json +4 -0
- GameSentenceMiner/obs.py +4 -1
- GameSentenceMiner/owocr/owocr/ocr.py +304 -134
- GameSentenceMiner/owocr/owocr/run.py +1 -1
- GameSentenceMiner/ui/anki_confirmation.py +4 -2
- GameSentenceMiner/ui/config_gui.py +12 -0
- GameSentenceMiner/util/configuration.py +6 -2
- GameSentenceMiner/util/cron/__init__.py +12 -0
- GameSentenceMiner/util/cron/daily_rollup.py +613 -0
- GameSentenceMiner/util/cron/jiten_update.py +397 -0
- GameSentenceMiner/util/cron/populate_games.py +154 -0
- GameSentenceMiner/util/cron/run_crons.py +148 -0
- GameSentenceMiner/util/cron/setup_populate_games_cron.py +118 -0
- GameSentenceMiner/util/cron_table.py +334 -0
- GameSentenceMiner/util/db.py +236 -49
- GameSentenceMiner/util/ffmpeg.py +23 -4
- GameSentenceMiner/util/games_table.py +340 -93
- GameSentenceMiner/util/jiten_api_client.py +188 -0
- GameSentenceMiner/util/stats_rollup_table.py +216 -0
- GameSentenceMiner/web/anki_api_endpoints.py +438 -220
- GameSentenceMiner/web/database_api.py +955 -1259
- GameSentenceMiner/web/jiten_database_api.py +1015 -0
- GameSentenceMiner/web/rollup_stats.py +672 -0
- GameSentenceMiner/web/static/css/dashboard-shared.css +75 -13
- GameSentenceMiner/web/static/css/overview.css +604 -47
- GameSentenceMiner/web/static/css/search.css +226 -0
- GameSentenceMiner/web/static/css/shared.css +762 -0
- GameSentenceMiner/web/static/css/stats.css +221 -0
- GameSentenceMiner/web/static/js/components/bar-chart.js +339 -0
- GameSentenceMiner/web/static/js/database-bulk-operations.js +320 -0
- GameSentenceMiner/web/static/js/database-game-data.js +390 -0
- GameSentenceMiner/web/static/js/database-game-operations.js +213 -0
- GameSentenceMiner/web/static/js/database-helpers.js +44 -0
- GameSentenceMiner/web/static/js/database-jiten-integration.js +750 -0
- GameSentenceMiner/web/static/js/database-popups.js +89 -0
- GameSentenceMiner/web/static/js/database-tabs.js +64 -0
- GameSentenceMiner/web/static/js/database-text-management.js +371 -0
- GameSentenceMiner/web/static/js/database.js +86 -718
- GameSentenceMiner/web/static/js/goals.js +79 -18
- GameSentenceMiner/web/static/js/heatmap.js +29 -23
- GameSentenceMiner/web/static/js/overview.js +1205 -339
- GameSentenceMiner/web/static/js/regex-patterns.js +100 -0
- GameSentenceMiner/web/static/js/search.js +215 -18
- GameSentenceMiner/web/static/js/shared.js +193 -39
- GameSentenceMiner/web/static/js/stats.js +1536 -179
- GameSentenceMiner/web/stats.py +1142 -269
- GameSentenceMiner/web/stats_api.py +2104 -0
- GameSentenceMiner/web/templates/anki_stats.html +4 -18
- GameSentenceMiner/web/templates/components/date-range.html +118 -3
- GameSentenceMiner/web/templates/components/html-head.html +40 -6
- GameSentenceMiner/web/templates/components/js-config.html +8 -8
- GameSentenceMiner/web/templates/components/regex-input.html +160 -0
- GameSentenceMiner/web/templates/database.html +564 -117
- GameSentenceMiner/web/templates/goals.html +41 -5
- GameSentenceMiner/web/templates/overview.html +159 -129
- GameSentenceMiner/web/templates/search.html +78 -9
- GameSentenceMiner/web/templates/stats.html +159 -5
- GameSentenceMiner/web/texthooking_page.py +280 -111
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/METADATA +43 -2
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/RECORD +70 -47
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.19.16.dist-info → gamesentenceminer-2.20.0.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
{% set page_title = 'Anki and GSM Kanji Stats' %}
|
|
4
4
|
{% set page_specific_css = 'stats.css' %}
|
|
5
5
|
{% set include_kanji_grid_css = true %}
|
|
6
|
-
{% set
|
|
6
|
+
{% set include_chartjs = true %}
|
|
7
7
|
{% set include_html2canvas = true %}
|
|
8
8
|
{% include 'components/html-head.html' %}
|
|
9
9
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/loading-skeleton.css') }}">
|
|
@@ -135,23 +135,6 @@
|
|
|
135
135
|
</div>
|
|
136
136
|
-->
|
|
137
137
|
|
|
138
|
-
<!-- Missing High-Frequency Kanji Section -->
|
|
139
|
-
<!-- Mining Heatmap Card (Full Width) -->
|
|
140
|
-
<div class="dashboard-card all-games" id="miningHeatmapCard" style="margin-bottom: 24px;">
|
|
141
|
-
<div class="dashboard-card-header">
|
|
142
|
-
<h3 class="dashboard-card-title">
|
|
143
|
-
<span class="dashboard-card-icon">📊</span>
|
|
144
|
-
Mining Heatmap
|
|
145
|
-
</h3>
|
|
146
|
-
<p class="dashboard-card-subtitle">Track your daily sentence mining progress</p>
|
|
147
|
-
</div>
|
|
148
|
-
|
|
149
|
-
<div class="dashboard-progress-section">
|
|
150
|
-
<div class="dashboard-progress-title">Activity Heatmap</div>
|
|
151
|
-
<div id="miningHeatmapContainer"></div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
138
|
<!-- Dashboard Statistics Sections -->
|
|
156
139
|
<div class="dashboard-container">
|
|
157
140
|
<!-- Missing High-Frequency Kanji Card -->
|
|
@@ -328,6 +311,9 @@
|
|
|
328
311
|
{% include 'components/settings-modal.html' %}
|
|
329
312
|
</div>
|
|
330
313
|
|
|
314
|
+
<!-- Include shared settings modal -->
|
|
315
|
+
{% include 'components/settings-modal.html' %}
|
|
316
|
+
|
|
331
317
|
<!-- Include shared JavaScript configuration -->
|
|
332
318
|
{% set include_kanji_grid_js = true %}
|
|
333
319
|
{% set include_heatmap_js = true %}
|
|
@@ -7,13 +7,128 @@
|
|
|
7
7
|
</h3>
|
|
8
8
|
</div>
|
|
9
9
|
<div class="dashboard-date-range">
|
|
10
|
-
<div class="dashboard-date-item tooltip"
|
|
10
|
+
<div class="dashboard-date-item tooltip"
|
|
11
|
+
data-tooltip="{{ from_tooltip | default('Select the start date for your stats') }}">
|
|
11
12
|
<label for="{{ from_id | default('fromDate') }}">From</label>
|
|
12
13
|
<input type="date" id="{{ from_id | default('fromDate') }}" class="dashboard-date-input">
|
|
13
14
|
</div>
|
|
14
|
-
<div class="dashboard-date-item tooltip"
|
|
15
|
+
<div class="dashboard-date-item tooltip"
|
|
16
|
+
data-tooltip="{{ to_tooltip | default('Select the end date for your stats') }}">
|
|
15
17
|
<label for="{{ to_id | default('toDate') }}">To</label>
|
|
16
18
|
<input type="date" id="{{ to_id | default('toDate') }}" class="dashboard-date-input">
|
|
17
19
|
</div>
|
|
20
|
+
<div class="dashboard-date-presets" aria-label="Date presets">
|
|
21
|
+
<label for="datePresets" class="dashboard-presets-label">Quick ranges</label>
|
|
22
|
+
<select id="datePresets" class="dashboard-presets-select" aria-label="Quick date ranges">
|
|
23
|
+
<option value="" selected>Quick ranges…</option>
|
|
24
|
+
<option value="today">Today</option>
|
|
25
|
+
<option value="week">Current Week</option>
|
|
26
|
+
<option value="month">Current Month</option>
|
|
27
|
+
<option value="year">Current Year</option>
|
|
28
|
+
<option value="7days">Last 7 Days</option>
|
|
29
|
+
<option value="30days">Last 30 Days</option>
|
|
30
|
+
<option value="365days">Last 365 Days</option>
|
|
31
|
+
</select>
|
|
32
|
+
</div>
|
|
18
33
|
</div>
|
|
19
|
-
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<script>
|
|
37
|
+
/* Date presets script: calculates ranges and fills the 'from' and 'to' inputs.
|
|
38
|
+
Assumptions: week starts on Monday. If different behaviour is desired, adjust getWeekStart.
|
|
39
|
+
Uses IDs from template defaults; if you provide `from_id`/`to_id` in the template context
|
|
40
|
+
these will be targeted.
|
|
41
|
+
*/
|
|
42
|
+
(function () {
|
|
43
|
+
function pad(n) { return n < 10 ? '0' + n : String(n); }
|
|
44
|
+
function toISODate(d) { return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()); }
|
|
45
|
+
|
|
46
|
+
var fromId = "{{ from_id | default('fromDate') }}";
|
|
47
|
+
var toId = "{{ to_id | default('toDate') }}";
|
|
48
|
+
var fromInput = document.getElementById(fromId);
|
|
49
|
+
var toInput = document.getElementById(toId);
|
|
50
|
+
if (!fromInput || !toInput) return;
|
|
51
|
+
|
|
52
|
+
function getWeekStart(date) {
|
|
53
|
+
// ISO week: Monday = 1. Compute Monday of current week.
|
|
54
|
+
var d = new Date(date);
|
|
55
|
+
var day = d.getDay(); // 0 (Sun) .. 6 (Sat)
|
|
56
|
+
var diff = (day === 0 ? -6 : 1 - day); // move to Monday
|
|
57
|
+
d.setDate(d.getDate() + diff);
|
|
58
|
+
d.setHours(0, 0, 0, 0);
|
|
59
|
+
return d;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var handlers = {
|
|
63
|
+
today: function () {
|
|
64
|
+
var t = new Date(); t.setHours(0, 0, 0, 0);
|
|
65
|
+
fromInput.value = toISODate(t);
|
|
66
|
+
toInput.value = toISODate(new Date());
|
|
67
|
+
fromInput.dispatchEvent(new Event('change'));
|
|
68
|
+
toInput.dispatchEvent(new Event('change'));
|
|
69
|
+
},
|
|
70
|
+
week: function () {
|
|
71
|
+
var now = new Date();
|
|
72
|
+
var start = getWeekStart(now);
|
|
73
|
+
var end = new Date(); end.setHours(23, 59, 59, 999);
|
|
74
|
+
fromInput.value = toISODate(start);
|
|
75
|
+
toInput.value = toISODate(end);
|
|
76
|
+
fromInput.dispatchEvent(new Event('change'));
|
|
77
|
+
toInput.dispatchEvent(new Event('change'));
|
|
78
|
+
},
|
|
79
|
+
month: function () {
|
|
80
|
+
var now = new Date();
|
|
81
|
+
var start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
82
|
+
var end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
|
83
|
+
fromInput.value = toISODate(start);
|
|
84
|
+
toInput.value = toISODate(end);
|
|
85
|
+
fromInput.dispatchEvent(new Event('change'));
|
|
86
|
+
toInput.dispatchEvent(new Event('change'));
|
|
87
|
+
},
|
|
88
|
+
year: function () {
|
|
89
|
+
var now = new Date();
|
|
90
|
+
var start = new Date(now.getFullYear(), 0, 1);
|
|
91
|
+
var end = new Date(now.getFullYear(), 11, 31);
|
|
92
|
+
fromInput.value = toISODate(start);
|
|
93
|
+
toInput.value = toISODate(end);
|
|
94
|
+
fromInput.dispatchEvent(new Event('change'));
|
|
95
|
+
toInput.dispatchEvent(new Event('change'));
|
|
96
|
+
},
|
|
97
|
+
'7days': function () {
|
|
98
|
+
var end = new Date();
|
|
99
|
+
var start = new Date(); start.setDate(end.getDate() - 6); // include today
|
|
100
|
+
fromInput.value = toISODate(start);
|
|
101
|
+
toInput.value = toISODate(end);
|
|
102
|
+
fromInput.dispatchEvent(new Event('change'));
|
|
103
|
+
toInput.dispatchEvent(new Event('change'));
|
|
104
|
+
},
|
|
105
|
+
'30days': function () {
|
|
106
|
+
var end = new Date();
|
|
107
|
+
var start = new Date(); start.setDate(end.getDate() - 29);
|
|
108
|
+
fromInput.value = toISODate(start);
|
|
109
|
+
toInput.value = toISODate(end);
|
|
110
|
+
fromInput.dispatchEvent(new Event('change'));
|
|
111
|
+
toInput.dispatchEvent(new Event('change'));
|
|
112
|
+
},
|
|
113
|
+
'365days': function () {
|
|
114
|
+
var end = new Date();
|
|
115
|
+
var start = new Date(); start.setDate(end.getDate() - 364);
|
|
116
|
+
fromInput.value = toISODate(start);
|
|
117
|
+
toInput.value = toISODate(end);
|
|
118
|
+
fromInput.dispatchEvent(new Event('change'));
|
|
119
|
+
toInput.dispatchEvent(new Event('change'));
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
var presetSelect = document.getElementById('datePresets');
|
|
124
|
+
if (presetSelect && presetSelect.tagName === 'SELECT') {
|
|
125
|
+
presetSelect.addEventListener('change', function (e) {
|
|
126
|
+
var range = e.target.value;
|
|
127
|
+
if (range && handlers[range]) {
|
|
128
|
+
handlers[range]();
|
|
129
|
+
// Keep the selected option visible instead of resetting
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
})();
|
|
134
|
+
</script>
|
|
@@ -1,19 +1,53 @@
|
|
|
1
1
|
<!-- HTML Head Component -->
|
|
2
2
|
<head>
|
|
3
|
+
<!-- Inline script to prevent dark mode flash - must run before any rendering -->
|
|
4
|
+
<script>
|
|
5
|
+
(function() {
|
|
6
|
+
const savedTheme = localStorage.getItem('theme');
|
|
7
|
+
if (savedTheme) {
|
|
8
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
9
|
+
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
10
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
11
|
+
}
|
|
12
|
+
})();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
3
15
|
<meta charset="UTF-8">
|
|
4
16
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
5
17
|
<title>{{ page_title | default('GSM Dashboard') }}</title>
|
|
6
18
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<
|
|
19
|
+
<!-- Resource hints for CDN to speed up connections -->
|
|
20
|
+
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
|
|
21
|
+
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
|
|
22
|
+
|
|
23
|
+
<!-- Preload critical CSS files for faster rendering -->
|
|
24
|
+
<link rel="preload" href="/static/css/shared.css" as="style">
|
|
25
|
+
{% if include_dashboard_css | default(true) %}
|
|
26
|
+
<link rel="preload" href="/static/css/dashboard-shared.css" as="style">
|
|
27
|
+
<link rel="preload" href="/static/css/popups-shared.css" as="style">
|
|
28
|
+
{% endif %}
|
|
29
|
+
{% if page_specific_css %}
|
|
30
|
+
<link rel="preload" href="/static/css/{{ page_specific_css }}" as="style">
|
|
31
|
+
{% endif %}
|
|
32
|
+
{% if include_kanji_grid_css | default(false) %}
|
|
33
|
+
<link rel="preload" href="/static/css/kanji-grid.css" as="style">
|
|
34
|
+
{% endif %}
|
|
35
|
+
|
|
36
|
+
{% if include_chartjs | default(false) %}
|
|
37
|
+
<!-- Include Chart.js from a CDN - deferred to not block rendering -->
|
|
38
|
+
<!-- Note: Chart.js is now lazy-loaded by default. Set include_chartjs=true to load upfront if needed -->
|
|
39
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js" defer></script>
|
|
10
40
|
{% endif %}
|
|
11
41
|
|
|
12
|
-
{% if include_html2canvas | default(
|
|
13
|
-
<!-- Include html2canvas for screenshot functionality -->
|
|
14
|
-
|
|
42
|
+
{% if include_html2canvas | default(false) %}
|
|
43
|
+
<!-- Include html2canvas for screenshot functionality - deferred to not block rendering -->
|
|
44
|
+
<!-- Note: html2canvas is now lazy-loaded when screenshot button is clicked -->
|
|
45
|
+
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js" defer></script>
|
|
15
46
|
{% endif %}
|
|
16
47
|
|
|
48
|
+
<!-- Include canvas-confetti for celebration effects - deferred to not block rendering -->
|
|
49
|
+
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/dist/confetti.browser.min.js" defer></script>
|
|
50
|
+
|
|
17
51
|
<!-- Include shared theme styles -->
|
|
18
52
|
{% include 'components/theme-styles.html' %}
|
|
19
53
|
|
|
@@ -6,26 +6,26 @@
|
|
|
6
6
|
</script>
|
|
7
7
|
{% endif %}
|
|
8
8
|
|
|
9
|
-
<!-- Include shared JavaScript first (required dependency) -->
|
|
10
|
-
<script src="/static/js/shared.js"></script>
|
|
9
|
+
<!-- Include shared JavaScript first (required dependency) - deferred to not block rendering -->
|
|
10
|
+
<script src="/static/js/shared.js" defer></script>
|
|
11
11
|
|
|
12
12
|
{% if include_kanji_grid_js | default(false) %}
|
|
13
|
-
<script src="/static/js/kanji-grid.js"></script>
|
|
13
|
+
<script src="/static/js/kanji-grid.js" defer></script>
|
|
14
14
|
{% endif %}
|
|
15
15
|
|
|
16
16
|
{% if include_heatmap_js | default(false) %}
|
|
17
|
-
<script src="/static/js/heatmap.js"></script>
|
|
17
|
+
<script src="/static/js/heatmap.js" defer></script>
|
|
18
18
|
{% endif %}
|
|
19
19
|
|
|
20
20
|
{% if page_specific_js %}
|
|
21
|
-
<!-- Include page-specific JavaScript -->
|
|
22
|
-
<script src="/static/js/{{ page_specific_js }}"></script>
|
|
21
|
+
<!-- Include page-specific JavaScript - deferred to not block rendering -->
|
|
22
|
+
<script src="/static/js/{{ page_specific_js }}" defer></script>
|
|
23
23
|
{% endif %}
|
|
24
24
|
|
|
25
25
|
{% if additional_js %}
|
|
26
|
-
<!-- Additional JavaScript files -->
|
|
26
|
+
<!-- Additional JavaScript files - deferred to not block rendering -->
|
|
27
27
|
{% for js_file in additional_js %}
|
|
28
|
-
<script src="/static/js/{{ js_file }}"></script>
|
|
28
|
+
<script src="/static/js/{{ js_file }}" defer></script>
|
|
29
29
|
{% endfor %}
|
|
30
30
|
{% endif %}
|
|
31
31
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<!-- Reusable Regex Input Component
|
|
2
|
+
Parameters (set via data attributes on container):
|
|
3
|
+
- data-show-exact-text: "true" to show exact text input (for deletion use case)
|
|
4
|
+
- data-preset-id: ID prefix for preset select (default: "presetPatterns")
|
|
5
|
+
- data-custom-id: ID prefix for custom regex input (default: "customRegex")
|
|
6
|
+
- data-case-id: ID prefix for case sensitive checkbox (default: "caseSensitive")
|
|
7
|
+
- data-regex-id: ID prefix for use regex checkbox (default: "useRegex")
|
|
8
|
+
- data-exact-id: ID prefix for exact text textarea (default: "exactText")
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
<div class="regex-input-component">
|
|
12
|
+
<div class="form-group">
|
|
13
|
+
<label class="form-label">Preset Patterns:</label>
|
|
14
|
+
<select class="form-input regex-preset-select">
|
|
15
|
+
<option value="">Select a preset pattern...</option>
|
|
16
|
+
<option value="everything">Everything</option>
|
|
17
|
+
<option value="lines_over_50">Lines over 50 characters</option>
|
|
18
|
+
<option value="lines_over_100">Lines over 100 characters</option>
|
|
19
|
+
<option value="non_japanese">Non-Japanese text</option>
|
|
20
|
+
<option value="ascii_only">ASCII-only lines</option>
|
|
21
|
+
<option value="empty_lines">Empty or whitespace-only lines</option>
|
|
22
|
+
<option value="numbers_only">Lines with numbers only</option>
|
|
23
|
+
<option value="single_char">Single character lines</option>
|
|
24
|
+
<option value="repeated_chars">Lines with repeated characters (3+ times)</option>
|
|
25
|
+
</select>
|
|
26
|
+
<small style="color: var(--text-tertiary); font-size: 12px;">
|
|
27
|
+
Select a common pattern or create a custom regex below.
|
|
28
|
+
</small>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="form-group">
|
|
32
|
+
<label class="form-label">Custom Regex Pattern:</label>
|
|
33
|
+
<input type="text" class="form-input regex-custom-input" placeholder="Enter custom regex pattern (e.g., ^.{0,10}$ for lines 10 chars or less)">
|
|
34
|
+
<small style="color: var(--text-tertiary); font-size: 12px;">
|
|
35
|
+
Use regex syntax. Leave empty to use exact text matching below.
|
|
36
|
+
</small>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="regex-exact-text-group" style="display: none;">
|
|
40
|
+
<div class="form-group">
|
|
41
|
+
<label class="form-label">Exact Text to Match:</label>
|
|
42
|
+
<textarea class="form-textarea regex-exact-textarea" rows="4" placeholder="Enter exact text lines to match, one per line..."></textarea>
|
|
43
|
+
<small style="color: var(--text-tertiary); font-size: 12px;">
|
|
44
|
+
Each line will be treated as an exact match. Only used if no regex pattern is provided.
|
|
45
|
+
</small>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="form-group">
|
|
50
|
+
<div class="checkbox-container">
|
|
51
|
+
<input type="checkbox" class="checkbox-input regex-case-checkbox">
|
|
52
|
+
<label class="checkbox-label">Case sensitive matching</label>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="form-group">
|
|
57
|
+
<div class="checkbox-container">
|
|
58
|
+
<input type="checkbox" class="checkbox-input regex-mode-checkbox">
|
|
59
|
+
<label class="checkbox-label">Treat custom pattern as regex</label>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.regex-input-component {
|
|
66
|
+
width: 100%;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.regex-input-component .form-group {
|
|
70
|
+
margin-bottom: 15px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.regex-input-component .form-label {
|
|
74
|
+
display: block;
|
|
75
|
+
margin-bottom: 5px;
|
|
76
|
+
font-weight: 500;
|
|
77
|
+
color: var(--text-primary);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.regex-input-component .form-input,
|
|
81
|
+
.regex-input-component .form-textarea {
|
|
82
|
+
width: 100%;
|
|
83
|
+
padding: 8px 12px;
|
|
84
|
+
border: 1px solid var(--border-color);
|
|
85
|
+
border-radius: 4px;
|
|
86
|
+
background: var(--bg-secondary);
|
|
87
|
+
color: var(--text-primary);
|
|
88
|
+
font-family: 'Courier New', monospace;
|
|
89
|
+
font-size: 14px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.regex-input-component .form-input:focus,
|
|
93
|
+
.regex-input-component .form-textarea:focus {
|
|
94
|
+
outline: none;
|
|
95
|
+
border-color: var(--accent-color);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.regex-input-component .checkbox-container {
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 8px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.regex-input-component .checkbox-input {
|
|
105
|
+
width: 18px;
|
|
106
|
+
height: 18px;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.regex-input-component .checkbox-label {
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
user-select: none;
|
|
113
|
+
color: var(--text-primary);
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
116
|
+
|
|
117
|
+
<script>
|
|
118
|
+
// Initialize regex component functionality
|
|
119
|
+
(function() {
|
|
120
|
+
// This script runs when the component is loaded
|
|
121
|
+
// The parent page's JavaScript should handle the actual logic
|
|
122
|
+
// by accessing the component's elements via their classes
|
|
123
|
+
|
|
124
|
+
// Add event listener for preset pattern selection
|
|
125
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
126
|
+
const presetSelects = document.querySelectorAll('.regex-preset-select');
|
|
127
|
+
|
|
128
|
+
presetSelects.forEach(select => {
|
|
129
|
+
select.addEventListener('change', function() {
|
|
130
|
+
const customInput = this.closest('.regex-input-component').querySelector('.regex-custom-input');
|
|
131
|
+
const regexCheckbox = this.closest('.regex-input-component').querySelector('.regex-mode-checkbox');
|
|
132
|
+
|
|
133
|
+
if (this.value) {
|
|
134
|
+
// Import and use the pattern
|
|
135
|
+
import('/static/js/regex-patterns.js').then(module => {
|
|
136
|
+
const pattern = module.getPattern(this.value);
|
|
137
|
+
if (pattern && customInput) {
|
|
138
|
+
customInput.value = pattern;
|
|
139
|
+
customInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
140
|
+
if (regexCheckbox) {
|
|
141
|
+
regexCheckbox.checked = true;
|
|
142
|
+
regexCheckbox.dispatchEvent(new Event('change', { bubbles: true }));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
if (customInput) {
|
|
148
|
+
customInput.value = '';
|
|
149
|
+
customInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
150
|
+
}
|
|
151
|
+
if (regexCheckbox) {
|
|
152
|
+
regexCheckbox.checked = false;
|
|
153
|
+
regexCheckbox.dispatchEvent(new Event('change', { bubbles: true }));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
})();
|
|
160
|
+
</script>
|