@jjlmoya/utils-converters 1.15.0 → 1.16.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-converters",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -10,7 +10,8 @@
10
10
  "./entries": "./src/entries.ts"
11
11
  },
12
12
  "files": [
13
- "src"
13
+ "src",
14
+ "scripts"
14
15
  ],
15
16
  "publishConfig": {
16
17
  "access": "public"
@@ -29,7 +30,8 @@
29
30
  "postversion": "git push && git push --tags",
30
31
  "patch": "npm version patch",
31
32
  "minor": "npm version minor",
32
- "major": "npm version major"
33
+ "major": "npm version major",
34
+ "postinstall": "node scripts/postinstall.mjs"
33
35
  },
34
36
  "lint-staged": {
35
37
  "*.{ts,tsx,astro}": [
@@ -0,0 +1,95 @@
1
+ import os
2
+ import re
3
+
4
+ class FileDiscovery:
5
+ def __init__(self, root_path, extension):
6
+ self.root_path = root_path
7
+ self.extension = extension
8
+
9
+ def get_files(self):
10
+ matched_files = []
11
+ for root, _, files in os.walk(self.root_path):
12
+ for file in files:
13
+ if file.endswith(self.extension):
14
+ matched_files.append(os.path.join(root, file))
15
+ return matched_files
16
+
17
+ class ContentProcessor:
18
+ def __init__(self, separators):
19
+ self.separators = separators
20
+ self.target_keys = ["title", "text", "name", "faqTitle", "bibliographyTitle"]
21
+
22
+ def _clean_text(self, text):
23
+ cleaned = text
24
+ for sep in self.separators:
25
+ if sep in cleaned:
26
+ cleaned = cleaned.split(sep)[0].strip()
27
+ return cleaned
28
+
29
+ def _replace_callback(self, match):
30
+ key = match.group(1)
31
+ quote = match.group(2)
32
+ text = match.group(3)
33
+
34
+ cleaned_text = self._clean_text(text)
35
+ return f"{key}: {quote}{cleaned_text}{quote}"
36
+
37
+ def _replace_const_callback(self, match):
38
+ prefix = match.group(1)
39
+ quote = match.group(2)
40
+ text = match.group(3)
41
+
42
+ cleaned_text = self._clean_text(text)
43
+ return f"{prefix}{quote}{cleaned_text}{quote}"
44
+
45
+ def _replace_seo_callback(self, match):
46
+ prefix = match.group(1)
47
+ quote = match.group(2)
48
+ text = match.group(3)
49
+
50
+ cleaned_text = self._clean_text(text)
51
+ return f"{prefix}{quote}{cleaned_text}{quote}"
52
+
53
+ def process(self, content):
54
+ title_keys = ["title", "faqTitle", "bibliographyTitle"]
55
+ keys_pattern = "|".join(title_keys)
56
+
57
+ pattern = rf"(?<!slug)(?<!\w)({keys_pattern})\s*:\s*(['\"`])(.*?)\2"
58
+ content = re.sub(pattern, self._replace_callback, content)
59
+
60
+ const_pattern = r"(const\s+title\s*=\s*)(['\"`])(.*?)\2"
61
+ content = re.sub(const_pattern, self._replace_const_callback, content)
62
+
63
+ seo_pattern = r"(type:\s*['\"]title['\"]\s*,\s*text:\s*)(['\"`])(.*?)\2"
64
+ content = re.sub(seo_pattern, self._replace_seo_callback, content)
65
+
66
+ return content
67
+
68
+ class FileStorage:
69
+ @staticmethod
70
+ def read(file_path):
71
+ with open(file_path, "r", encoding="utf-8") as f:
72
+ return f.read()
73
+
74
+ @staticmethod
75
+ def write(file_path, content):
76
+ with open(file_path, "w", encoding="utf-8") as f:
77
+ f.write(content)
78
+
79
+ class TitleCleaner:
80
+ def __init__(self):
81
+ self.discovery = FileDiscovery("src/tool", ".ts")
82
+ self.processor = ContentProcessor(["|", "-"])
83
+ self.storage = FileStorage()
84
+
85
+ def run(self):
86
+ files = self.discovery.get_files()
87
+ for file_path in files:
88
+ content = self.storage.read(file_path)
89
+ new_content = self.processor.process(content)
90
+ if content != new_content:
91
+ self.storage.write(file_path, new_content)
92
+
93
+ if __name__ == "__main__":
94
+ cleaner = TitleCleaner()
95
+ cleaner.run()
@@ -0,0 +1,27 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const libDir = dirname(fileURLToPath(import.meta.url));
6
+ const toolsDir = join(libDir, '../src/tool');
7
+
8
+ const inNodeModules = libDir.includes('node_modules');
9
+ if (!inNodeModules) process.exit(0);
10
+
11
+ const projectRoot = join(libDir, '../../../..');
12
+ const categoryKey = JSON.parse(readFileSync(join(libDir, '../package.json'), 'utf8')).name.replace('@jjlmoya/utils-', '');
13
+ const destDir = join(projectRoot, `public/styles/lib/${categoryKey}`);
14
+
15
+ mkdirSync(destDir, { recursive: true });
16
+
17
+ const tools = readdirSync(toolsDir, { withFileTypes: true }).filter(d => d.isDirectory());
18
+ for (const tool of tools) {
19
+ const toolDir = join(toolsDir, tool.name);
20
+ let files;
21
+ try { files = readdirSync(toolDir).filter(f => f.endsWith('.css')); }
22
+ catch { continue; }
23
+ for (const file of files) {
24
+ writeFileSync(join(destDir, file), readFileSync(join(toolDir, file)));
25
+ console.log(`[@jjlmoya/utils-${categoryKey}] copied ${file}`);
26
+ }
27
+ }
@@ -5,307 +5,6 @@ interface Props { ui: ImageToBase64UI }
5
5
  const { ui } = Astro.props;
6
6
  ---
7
7
 
8
- <style>
9
- .base64-container {
10
- width: 100%;
11
- margin-bottom: 2rem;
12
- }
13
-
14
- .base64-card {
15
- background: var(--bg-surface);
16
- border: 1px solid var(--border-base);
17
- border-radius: 20px;
18
-
19
- --card-shadow: rgba(0, 0, 0, 0.04);
20
- --card-shadow-blur: 20px;
21
- --card-shadow-y: 4px;
22
-
23
- box-shadow: 0 var(--card-shadow-y) var(--card-shadow-blur) var(--card-shadow);
24
- display: flex;
25
- flex-direction: column;
26
- overflow: hidden;
27
- }
28
-
29
- @media (min-width: 800px) {
30
- .base64-card {
31
- flex-direction: row;
32
- min-height: 550px;
33
- }
34
-
35
- .upload-section {
36
- width: 40%;
37
- border-right: 1px solid var(--border-base);
38
- background: var(--bg-muted);
39
- padding: 3rem 2rem;
40
- }
41
-
42
- .result-section {
43
- flex: 1;
44
- padding: 3rem 2rem;
45
- display: flex;
46
- flex-direction: column;
47
- gap: 2rem;
48
- }
49
- }
50
-
51
- .upload-section {
52
- display: flex;
53
- flex-direction: column;
54
- justify-content: center;
55
- padding: 2rem;
56
- }
57
-
58
- .result-section {
59
- padding: 2rem;
60
- display: flex;
61
- flex-direction: column;
62
- gap: 1.5rem;
63
- }
64
-
65
- .drop-zone {
66
- position: relative;
67
- border: 2px dashed var(--border-base);
68
- border-radius: 16px;
69
- background: var(--bg-surface);
70
- padding: 2rem 1rem;
71
- text-align: center;
72
- cursor: pointer;
73
- transition: all 0.2s ease;
74
- display: flex;
75
- align-items: center;
76
- justify-content: center;
77
- min-height: 300px;
78
- height: 100%;
79
- }
80
-
81
- .drop-zone:hover {
82
- border-color: var(--accent);
83
- background: var(--bg-muted);
84
- }
85
-
86
- .drop-content {
87
- display: flex;
88
- flex-direction: column;
89
- align-items: center;
90
- gap: 0.5rem;
91
- pointer-events: none;
92
- }
93
-
94
- .upload-icon {
95
- width: 40px;
96
- height: 40px;
97
- color: var(--text-muted);
98
- margin-bottom: 0.5rem;
99
- }
100
-
101
- .drop-content h3 {
102
- margin: 0;
103
- font-size: 1.05rem;
104
- font-weight: 700;
105
- color: var(--text-base);
106
- }
107
-
108
- .drop-content p {
109
- margin: 0;
110
- font-size: 0.85rem;
111
- color: var(--text-muted);
112
- }
113
-
114
- .file-input-native {
115
- margin-top: 1.5rem;
116
- padding: 0.5rem;
117
- font-size: 0.85rem;
118
- color: var(--text-muted);
119
- background: var(--bg-surface);
120
- border: 1px solid var(--border-base);
121
- border-radius: 8px;
122
- max-width: 100%;
123
- cursor: pointer;
124
- }
125
-
126
- .drop-zone .file-input-native {
127
- pointer-events: auto;
128
- }
129
-
130
- .preview-container {
131
- position: absolute;
132
- inset: 0;
133
- background: var(--bg-surface);
134
- z-index: 10;
135
- display: flex;
136
- flex-direction: column;
137
- align-items: center;
138
- justify-content: center;
139
- padding: 1.25rem;
140
- }
141
-
142
- .preview-container.hidden {
143
- display: none;
144
- }
145
-
146
- #b64-image-preview {
147
- max-width: 100%;
148
- max-height: 250px;
149
- object-fit: contain;
150
- border-radius: 8px;
151
- }
152
-
153
- .clear-btn {
154
- position: absolute;
155
- top: 1rem;
156
- right: 1rem;
157
- background: var(--bg-surface);
158
- border: 1px solid var(--border-base);
159
- border-radius: 50%;
160
- width: 32px;
161
- height: 32px;
162
- display: flex;
163
- align-items: center;
164
- justify-content: center;
165
- color: var(--text-muted);
166
- cursor: pointer;
167
- }
168
-
169
- .image-info {
170
- position: absolute;
171
- bottom: 1rem;
172
- display: flex;
173
- background: var(--bg-muted);
174
- padding: 0.4rem 0.8rem;
175
- border-radius: 8px;
176
- border: 1px solid var(--border-base);
177
- }
178
-
179
- .info-pill {
180
- font-size: 0.7rem;
181
- font-weight: 600;
182
- color: var(--text-muted);
183
- }
184
-
185
- .name-pill {
186
- max-width: 120px;
187
- overflow: hidden;
188
- text-overflow: ellipsis;
189
- white-space: nowrap;
190
- padding-right: 0.5rem;
191
- }
192
-
193
- .size-pill {
194
- color: var(--accent);
195
- padding-left: 0.5rem;
196
- border-left: 1px solid var(--border-base);
197
- }
198
-
199
- .output-group {
200
- display: flex;
201
- flex-direction: column;
202
- gap: 0.5rem;
203
- }
204
-
205
- .output-header {
206
- display: flex;
207
- justify-content: space-between;
208
- align-items: center;
209
- }
210
-
211
- .output-header label {
212
- font-weight: 700;
213
- color: var(--text-base);
214
- font-size: 0.95rem;
215
- }
216
-
217
- .copy-btn {
218
- background: var(--bg-surface);
219
- border: 1px solid var(--border-base);
220
- color: var(--text-base);
221
- padding: 0.35rem 0.75rem;
222
- border-radius: 6px;
223
- font-size: 0.8rem;
224
- font-weight: 700;
225
- cursor: pointer;
226
- transition: all 0.2s;
227
- }
228
-
229
- .copy-btn:hover {
230
- background: var(--accent);
231
- color: var(--text-on-primary);
232
- }
233
-
234
- .code-wrapper {
235
- background: var(--bg-muted);
236
- border: 1px solid var(--border-base);
237
- border-radius: 12px;
238
- padding: 1rem;
239
- }
240
-
241
- textarea {
242
- width: 100%;
243
- min-height: 120px;
244
- background: transparent;
245
- border: none;
246
- color: var(--text-base);
247
- font-size: 0.85rem;
248
- line-height: 1.5;
249
- resize: none;
250
- outline: none;
251
- word-break: break-all;
252
- }
253
-
254
- .toast {
255
- position: fixed;
256
- bottom: 2rem;
257
- left: 50%;
258
- transform: translateX(-50%) translateY(20px);
259
- background: var(--text-base);
260
- color: var(--bg-surface);
261
- padding: 0.75rem 1.5rem;
262
- border-radius: 50px;
263
- font-weight: 700;
264
- font-size: 0.9rem;
265
- display: flex;
266
- align-items: center;
267
- gap: 0.5rem;
268
-
269
- --toast-shadow: rgba(0, 0, 0, 0.2);
270
- --toast-shadow-y: 10px;
271
- --toast-shadow-blur: 30px;
272
-
273
- box-shadow: 0 var(--toast-shadow-y) var(--toast-shadow-blur) var(--toast-shadow);
274
- opacity: 0;
275
- pointer-events: none;
276
- transition: all 0.2s;
277
- z-index: 100;
278
- }
279
-
280
- .toast.show {
281
- opacity: 1;
282
- transform: translateX(-50%) translateY(0);
283
- }
284
-
285
- @media (max-width: 600px) {
286
- .card-section {
287
- padding: 1rem;
288
- }
289
-
290
- .drop-zone {
291
- min-height: 280px;
292
- }
293
- }
294
-
295
- @media (max-width: 768px) {
296
- .card-section {
297
- padding: 1.5rem;
298
- }
299
-
300
- .drop-zone {
301
- min-height: 250px;
302
- }
303
-
304
- textarea {
305
- height: 120px;
306
- }
307
- }
308
- </style>
309
8
 
310
9
  <div class="base64-container animate-in">
311
10
  <div class="base64-card">
@@ -0,0 +1,299 @@
1
+ .base64-container {
2
+ width: 100%;
3
+ margin-bottom: 2rem;
4
+ }
5
+
6
+ .base64-card {
7
+ background: var(--bg-surface);
8
+ border: 1px solid var(--border-base);
9
+ border-radius: 20px;
10
+
11
+ --card-shadow: rgba(0, 0, 0, 0.04);
12
+ --card-shadow-blur: 20px;
13
+ --card-shadow-y: 4px;
14
+
15
+ box-shadow: 0 var(--card-shadow-y) var(--card-shadow-blur) var(--card-shadow);
16
+ display: flex;
17
+ flex-direction: column;
18
+ overflow: hidden;
19
+ }
20
+
21
+ @media (min-width: 800px) {
22
+ .base64-card {
23
+ flex-direction: row;
24
+ min-height: 550px;
25
+ }
26
+
27
+ .upload-section {
28
+ width: 40%;
29
+ border-right: 1px solid var(--border-base);
30
+ background: var(--bg-muted);
31
+ padding: 3rem 2rem;
32
+ }
33
+
34
+ .result-section {
35
+ flex: 1;
36
+ padding: 3rem 2rem;
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: 2rem;
40
+ }
41
+ }
42
+
43
+ .upload-section {
44
+ display: flex;
45
+ flex-direction: column;
46
+ justify-content: center;
47
+ padding: 2rem;
48
+ }
49
+
50
+ .result-section {
51
+ padding: 2rem;
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: 1.5rem;
55
+ }
56
+
57
+ .drop-zone {
58
+ position: relative;
59
+ border: 2px dashed var(--border-base);
60
+ border-radius: 16px;
61
+ background: var(--bg-surface);
62
+ padding: 2rem 1rem;
63
+ text-align: center;
64
+ cursor: pointer;
65
+ transition: all 0.2s ease;
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ min-height: 300px;
70
+ height: 100%;
71
+ }
72
+
73
+ .drop-zone:hover {
74
+ border-color: var(--accent);
75
+ background: var(--bg-muted);
76
+ }
77
+
78
+ .drop-content {
79
+ display: flex;
80
+ flex-direction: column;
81
+ align-items: center;
82
+ gap: 0.5rem;
83
+ pointer-events: none;
84
+ }
85
+
86
+ .upload-icon {
87
+ width: 40px;
88
+ height: 40px;
89
+ color: var(--text-muted);
90
+ margin-bottom: 0.5rem;
91
+ }
92
+
93
+ .drop-content h3 {
94
+ margin: 0;
95
+ font-size: 1.05rem;
96
+ font-weight: 700;
97
+ color: var(--text-base);
98
+ }
99
+
100
+ .drop-content p {
101
+ margin: 0;
102
+ font-size: 0.85rem;
103
+ color: var(--text-muted);
104
+ }
105
+
106
+ .file-input-native {
107
+ margin-top: 1.5rem;
108
+ padding: 0.5rem;
109
+ font-size: 0.85rem;
110
+ color: var(--text-muted);
111
+ background: var(--bg-surface);
112
+ border: 1px solid var(--border-base);
113
+ border-radius: 8px;
114
+ max-width: 100%;
115
+ cursor: pointer;
116
+ }
117
+
118
+ .drop-zone .file-input-native {
119
+ pointer-events: auto;
120
+ }
121
+
122
+ .preview-container {
123
+ position: absolute;
124
+ inset: 0;
125
+ background: var(--bg-surface);
126
+ z-index: 10;
127
+ display: flex;
128
+ flex-direction: column;
129
+ align-items: center;
130
+ justify-content: center;
131
+ padding: 1.25rem;
132
+ }
133
+
134
+ .preview-container.hidden {
135
+ display: none;
136
+ }
137
+
138
+ #b64-image-preview {
139
+ max-width: 100%;
140
+ max-height: 250px;
141
+ object-fit: contain;
142
+ border-radius: 8px;
143
+ }
144
+
145
+ .clear-btn {
146
+ position: absolute;
147
+ top: 1rem;
148
+ right: 1rem;
149
+ background: var(--bg-surface);
150
+ border: 1px solid var(--border-base);
151
+ border-radius: 50%;
152
+ width: 32px;
153
+ height: 32px;
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ color: var(--text-muted);
158
+ cursor: pointer;
159
+ }
160
+
161
+ .image-info {
162
+ position: absolute;
163
+ bottom: 1rem;
164
+ display: flex;
165
+ background: var(--bg-muted);
166
+ padding: 0.4rem 0.8rem;
167
+ border-radius: 8px;
168
+ border: 1px solid var(--border-base);
169
+ }
170
+
171
+ .info-pill {
172
+ font-size: 0.7rem;
173
+ font-weight: 600;
174
+ color: var(--text-muted);
175
+ }
176
+
177
+ .name-pill {
178
+ max-width: 120px;
179
+ overflow: hidden;
180
+ text-overflow: ellipsis;
181
+ white-space: nowrap;
182
+ padding-right: 0.5rem;
183
+ }
184
+
185
+ .size-pill {
186
+ color: var(--accent);
187
+ padding-left: 0.5rem;
188
+ border-left: 1px solid var(--border-base);
189
+ }
190
+
191
+ .output-group {
192
+ display: flex;
193
+ flex-direction: column;
194
+ gap: 0.5rem;
195
+ }
196
+
197
+ .output-header {
198
+ display: flex;
199
+ justify-content: space-between;
200
+ align-items: center;
201
+ }
202
+
203
+ .output-header label {
204
+ font-weight: 700;
205
+ color: var(--text-base);
206
+ font-size: 0.95rem;
207
+ }
208
+
209
+ .copy-btn {
210
+ background: var(--bg-surface);
211
+ border: 1px solid var(--border-base);
212
+ color: var(--text-base);
213
+ padding: 0.35rem 0.75rem;
214
+ border-radius: 6px;
215
+ font-size: 0.8rem;
216
+ font-weight: 700;
217
+ cursor: pointer;
218
+ transition: all 0.2s;
219
+ }
220
+
221
+ .copy-btn:hover {
222
+ background: var(--accent);
223
+ color: var(--text-on-primary);
224
+ }
225
+
226
+ .code-wrapper {
227
+ background: var(--bg-muted);
228
+ border: 1px solid var(--border-base);
229
+ border-radius: 12px;
230
+ padding: 1rem;
231
+ }
232
+
233
+ textarea {
234
+ width: 100%;
235
+ min-height: 120px;
236
+ background: transparent;
237
+ border: none;
238
+ color: var(--text-base);
239
+ font-size: 0.85rem;
240
+ line-height: 1.5;
241
+ resize: none;
242
+ outline: none;
243
+ word-break: break-all;
244
+ }
245
+
246
+ .toast {
247
+ position: fixed;
248
+ bottom: 2rem;
249
+ left: 50%;
250
+ transform: translateX(-50%) translateY(20px);
251
+ background: var(--text-base);
252
+ color: var(--bg-surface);
253
+ padding: 0.75rem 1.5rem;
254
+ border-radius: 50px;
255
+ font-weight: 700;
256
+ font-size: 0.9rem;
257
+ display: flex;
258
+ align-items: center;
259
+ gap: 0.5rem;
260
+
261
+ --toast-shadow: rgba(0, 0, 0, 0.2);
262
+ --toast-shadow-y: 10px;
263
+ --toast-shadow-blur: 30px;
264
+
265
+ box-shadow: 0 var(--toast-shadow-y) var(--toast-shadow-blur) var(--toast-shadow);
266
+ opacity: 0;
267
+ pointer-events: none;
268
+ transition: all 0.2s;
269
+ z-index: 100;
270
+ }
271
+
272
+ .toast.show {
273
+ opacity: 1;
274
+ transform: translateX(-50%) translateY(0);
275
+ }
276
+
277
+ @media (max-width: 600px) {
278
+ .card-section {
279
+ padding: 1rem;
280
+ }
281
+
282
+ .drop-zone {
283
+ min-height: 280px;
284
+ }
285
+ }
286
+
287
+ @media (max-width: 768px) {
288
+ .card-section {
289
+ padding: 1.5rem;
290
+ }
291
+
292
+ .drop-zone {
293
+ min-height: 250px;
294
+ }
295
+
296
+ textarea {
297
+ height: 120px;
298
+ }
299
+ }