SenY commited on
Commit
a14f3d8
·
1 Parent(s): 4f68bf2
Files changed (1) hide show
  1. util/index.html +0 -406
util/index.html DELETED
@@ -1,406 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="ja" data-bs-theme="dark">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>テキスト編集ユーティリティ</title>
8
- <!-- Bootstrap 5 CSS -->
9
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
10
- <!-- Font Awesome -->
11
- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
12
- <style>
13
- .text-area-container {
14
- margin: 20px 0;
15
- position: relative;
16
- }
17
-
18
- .text-area-controls {
19
- position: absolute;
20
- top: 10px;
21
- right: 10px;
22
- z-index: 10;
23
- display: flex;
24
- gap: 5px;
25
- }
26
-
27
- .text-area-controls .btn {
28
- padding: 4px 8px;
29
- font-size: 12px;
30
- border-radius: 4px;
31
- }
32
-
33
- .text-area-container textarea {
34
- padding-right: 80px;
35
- }
36
-
37
- .accordion-button:not(.collapsed) {
38
- background-color: var(--bs-primary-bg-subtle);
39
- }
40
-
41
- .layout-wrapper {
42
- display: flex;
43
- flex-direction: row;
44
- height: 100vh;
45
- }
46
-
47
- .sidebar {
48
- width: 250px;
49
- min-width: 250px;
50
- transition: all 0.3s;
51
- z-index: 1000;
52
- background-color: var(--bs-body-bg);
53
- border-right: 1px solid var(--bs-border-color, #444);
54
- margin-top: 32px;
55
- }
56
-
57
- .main-content {
58
- flex: 1 1 0%;
59
- transition: all 0.3s;
60
- margin-top: 32px;
61
- display: flex;
62
- flex-direction: column;
63
- align-items: stretch;
64
- }
65
-
66
- .main-inner {
67
- width: 100%;
68
- max-width: 100%;
69
- padding: 0 8px;
70
- margin: 0;
71
- }
72
-
73
- @media (min-width: 768px) {
74
- .layout-wrapper {
75
- padding: 0 25vh;
76
- }
77
- }
78
- </style>
79
- </head>
80
-
81
- <body>
82
- <div class="layout-wrapper">
83
-
84
- <!-- メインコンテンツ -->
85
- <div class="main-content" id="mainContent">
86
- <div class="main-inner">
87
- <h2 class="mb-3">テキスト編集ユーティリティ</h2>
88
- <div class="d-grid gap-2">
89
- <button class="btn btn-primary" id="processBtn">
90
- <i class="fas fa-cog me-2"></i>Process
91
- </button>
92
- <button class="btn btn-secondary" id="deprocessBtn">
93
- <i class="fas fa-undo me-2"></i>Deprocess
94
- </button>
95
- </div>
96
- <div class="accordion" id="textEditorAccordion">
97
- <!-- 上部テキストエリア -->
98
- <div class="accordion-item">
99
- <h2 class="accordion-header">
100
- <button class="accordion-button" type="button" data-bs-toggle="collapse"
101
- data-bs-target="#collapseOne">
102
- <i class="fas fa-chevron-down me-2"></i>上部テキストエリア
103
- </button>
104
- </h2>
105
- <div id="collapseOne" class="accordion-collapse collapse show"
106
- data-bs-parent="#textEditorAccordion">
107
- <div class="accordion-body">
108
- <div class="text-area-container">
109
- <div class="text-area-controls">
110
- <button class="btn btn-outline-primary btn-sm" onclick="copyToClipboard('topText', event)" title="コピー">
111
- <i class="fas fa-copy"></i>
112
- </button>
113
- <button class="btn btn-outline-secondary btn-sm" onclick="pasteFromClipboard('topText')" title="ペースト">
114
- <i class="fas fa-paste"></i>
115
- </button>
116
- </div>
117
- <textarea id="topText" class="form-control" style="width:100%; min-height:50vh;"
118
- rows="10" placeholder="ここにテキストを入力してください"></textarea>
119
- </div>
120
- </div>
121
- </div>
122
- </div>
123
- <!-- 下部テキストエリア -->
124
- <div class="accordion-item">
125
- <h2 class="accordion-header">
126
- <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
127
- data-bs-target="#collapseTwo">
128
- <i class="fas fa-chevron-down me-2"></i>下部テキストエリア
129
- </button>
130
- </h2>
131
- <div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#textEditorAccordion">
132
- <div class="accordion-body">
133
- <div class="text-area-container">
134
- <div class="text-area-controls">
135
- <button class="btn btn-outline-primary btn-sm" onclick="copyToClipboard('bottomText', event)" title="コピー">
136
- <i class="fas fa-copy"></i>
137
- </button>
138
- <button class="btn btn-outline-secondary btn-sm" onclick="pasteFromClipboard('bottomText')" title="ペースト">
139
- <i class="fas fa-paste"></i>
140
- </button>
141
- </div>
142
- <textarea id="bottomText" class="form-control" style="width:100%; min-height:50vh;"
143
- rows="10" placeholder="ここにテキストを入力してください"></textarea>
144
- </div>
145
- </div>
146
- </div>
147
- </div>
148
-
149
- <div class="accordion-item">
150
- <h2 class="accordion-header">
151
- <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
152
- data-bs-target="#collapseThree">
153
- <i class="fas fa-chevron-down me-2"></i>メモ
154
- </button>
155
- </h2>
156
- <div id="collapseThree" class="accordion-collapse collapse"
157
- data-bs-parent="#textEditorAccordion">
158
- <div class="accordion-body">
159
- <div class="text-area-container">
160
- <div class="text-area-controls">
161
- <button class="btn btn-outline-primary btn-sm" onclick="copyToClipboard('memoArea', event)" title="コピー">
162
- <i class="fas fa-copy"></i>
163
- </button>
164
- <button class="btn btn-outline-secondary btn-sm" onclick="pasteFromClipboard('memoArea')" title="ペースト">
165
- <i class="fas fa-paste"></i>
166
- </button>
167
- </div>
168
- <textarea id="memoArea" class="form-control" style="width:100%; min-height:50vh;"
169
- rows="10" placeholder="ここにテキストを入力してください"></textarea>
170
- </div>
171
- </div>
172
- </div>
173
- </div>
174
-
175
- </div>
176
- </div>
177
- </div>
178
- </div>
179
-
180
- <!-- Bootstrap 5 JS Bundle with Popper -->
181
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
182
- <script>
183
- let lastSaveTimestamp = 0;
184
-
185
- // 0pxスペース(ゼロ幅スペース)の定数
186
- const ZERO_WIDTH_SPACE = '&#8204;';
187
-
188
- // 既知のダミー文字の候補
189
- const KNOWN_DUMMY_CHARS = [
190
- '\u200c', '\u200b', '\u200d', // ゼロ幅文字
191
- '&#8204;', '&#8203;', '&zwnj;', // HTMLエンティティ
192
- '\u200e', '\u200f', // 方向制御文字
193
- '\u2060', '\u2061', '\u2062', '\u2063', '\u2064' // その他の制御文字
194
- ];
195
-
196
- // 文字の出現頻度を分析する関数
197
- function analyzeCharFrequency(text) {
198
- const frequency = new Map();
199
- for (let i = 0; i < text.length; i++) {
200
- const char = text[i];
201
- frequency.set(char, (frequency.get(char) || 0) + 1);
202
- }
203
- return frequency;
204
- }
205
-
206
- // 文字列のパターンを分析してダミー文字の候補を探す関数
207
- function findPatternCandidate(text) {
208
- if (!text || text.length < 3) return null;
209
-
210
- // 文字の出現頻度を分析
211
- const frequency = analyzeCharFrequency(text);
212
-
213
- // 文字列を2文字ずつに分割して、各文字の間の文字を確認
214
- const patternMap = new Map();
215
- for (let i = 1; i < text.length - 1; i += 2) {
216
- const char = text[i];
217
- const prevChar = text[i - 1];
218
- const nextChar = text[i + 1];
219
-
220
- // 前後の文字が同じで、かつ現在の文字が一定の頻度で出現している場合
221
- if (prevChar === nextChar &&
222
- frequency.get(char) > text.length * 0.3) { // 30%以上の出現率
223
- patternMap.set(char, (patternMap.get(char) || 0) + 1);
224
- }
225
- }
226
-
227
- // 最も頻出するパターンを返す
228
- let maxCount = 0;
229
- let candidate = null;
230
- for (const [char, count] of patternMap) {
231
- if (count > maxCount) {
232
- maxCount = count;
233
- candidate = char;
234
- }
235
- }
236
-
237
- return candidate;
238
- }
239
-
240
- // 文字間のダミー文字を検出する関数
241
- function detectDummyChar(text) {
242
- if (!text || text.length < 3) return null;
243
-
244
- // まず既知のダミー文字をチェック
245
- for (let i = 1; i < text.length - 1; i += 2) {
246
- const char = text[i];
247
- if (KNOWN_DUMMY_CHARS.includes(char) &&
248
- text[i - 1] !== char && text[i + 1] !== char) {
249
- return char;
250
- }
251
- }
252
-
253
- // 既知のダミー文字が見つからない場合はパターン分析を実行
254
- return findPatternCandidate(text);
255
- }
256
-
257
- // 文字列の各文字の間に指定の文字列を挟む
258
- function insertBetweenChars(text, insertStr) {
259
- if (!text) return '';
260
- return text.split('').join(insertStr);
261
- }
262
-
263
- // 文字列から指定の文字列を除去
264
- function removeBetweenChars(text, removeStr) {
265
- if (!text) return '';
266
- return text.split(removeStr).join('');
267
- }
268
-
269
- // HTMLエンティティを実体参照に変換する関数
270
- function decodeHtmlEntities(str) {
271
- const textarea = document.createElement('textarea');
272
- textarea.innerHTML = str;
273
- return textarea.value;
274
- }
275
-
276
- // テキストエリアの値を取得・設定する関数
277
- function getUpperText() {
278
- return document.querySelectorAll('.text-area-container textarea')[0].value;
279
- }
280
- function setUpperText(val) {
281
- document.querySelectorAll('.text-area-container textarea')[0].value = val;
282
- }
283
- function getLowerText() {
284
- return document.querySelectorAll('.text-area-container textarea')[1].value;
285
- }
286
- function setLowerText(val) {
287
- document.querySelectorAll('.text-area-container textarea')[1].value = val;
288
- }
289
-
290
- // processボタンの挙動
291
- document.getElementById('processBtn').addEventListener('click', function () {
292
- const upperText = getUpperText();
293
- const processed = insertBetweenChars(upperText, ZERO_WIDTH_SPACE);
294
- setLowerText(processed);
295
- // 下部テキストエリアを表示
296
- const lowerAccordion = new bootstrap.Collapse(document.getElementById('collapseTwo'), {
297
- toggle: false
298
- });
299
- lowerAccordion.show();
300
- });
301
-
302
- // deprocessボタンの挙動
303
- document.getElementById('deprocessBtn').addEventListener('click', function () {
304
- let lowerText = getLowerText();
305
- // まずHTMLエンティティを実体参照に変換
306
- lowerText = decodeHtmlEntities(lowerText);
307
- const dummyChar = detectDummyChar(lowerText);
308
- if (!dummyChar) {
309
- alert('文字間のダミー文字を検出できませんでした。');
310
- return;
311
- }
312
- const deprocessed = removeBetweenChars(lowerText, dummyChar);
313
- setUpperText(deprocessed);
314
- // 上部テキストエリアを表示
315
- const upperAccordion = new bootstrap.Collapse(document.getElementById('collapseOne'), {
316
- toggle: false
317
- });
318
- upperAccordion.show();
319
- });
320
-
321
- function saveToUserStorage(force = false) {
322
- const currentTime = Date.now();
323
- if (currentTime - lastSaveTimestamp < 5000 && !force) {
324
- console.debug('セーブをスキップします');
325
- return;
326
- }
327
- console.debug('セーブを実行します');
328
-
329
- // 既存のデータを取得
330
- const textUtilData = JSON.parse(localStorage.getItem('textUtil') || '{}');
331
-
332
- const newData = {};
333
- Array.from(document.querySelectorAll("input[id], textarea[id], select[id]")).forEach(el => {
334
- if (el.id) {
335
- newData[el.id] = el.type === 'checkbox' ? el.checked : el.value;
336
- }
337
- });
338
- Object.assign(textUtilData, newData);
339
- console.log(textUtilData);
340
- localStorage.setItem('textUtil', JSON.stringify(textUtilData));
341
- lastSaveTimestamp = currentTime;
342
- }
343
-
344
- function loadFromUserStorage() {
345
- const textUtilData = JSON.parse(localStorage.getItem('textUtil') || '{}');
346
- document.getElementById('bottomText').value = textUtilData['bottomText'] || '';
347
- document.getElementById('topText').value = textUtilData['topText'] || '';
348
- document.getElementById('memoArea').value = textUtilData['memoArea'] || '';
349
- }
350
-
351
- document.querySelectorAll("#bottomText, #topText").forEach(el => {
352
- el.addEventListener('input', () => {
353
- saveToUserStorage(false);
354
- });
355
- });
356
- document.querySelectorAll("#memoArea").forEach(el => {
357
- el.addEventListener('input', () => {
358
- saveToUserStorage(true);
359
- });
360
- });
361
-
362
- document.addEventListener('DOMContentLoaded', function () {
363
- // ページ読み込み時にデータを復元
364
- loadFromUserStorage();
365
- });
366
-
367
- // クリップボードにコピーする関数
368
- async function copyToClipboard(textareaId, event) {
369
- const textarea = document.getElementById(textareaId);
370
- const text = textarea.value;
371
-
372
- try {
373
- await navigator.clipboard.writeText(text);
374
- // 成功時のフィードバック(オプション)
375
- const button = event.target.closest('button');
376
- const originalText = button.innerHTML;
377
- button.innerHTML = '<i class="fas fa-check"></i>';
378
- setTimeout(() => {
379
- button.innerHTML = originalText;
380
- }, 1000);
381
- } catch (err) {
382
- console.error('クリップボードへのコピーに失敗しました:', err);
383
- alert('クリップボードへのコピーに失敗しました');
384
- }
385
- }
386
-
387
- // クリップボードからペーストする関数
388
- async function pasteFromClipboard(textareaId) {
389
- const textarea = document.getElementById(textareaId);
390
-
391
- try {
392
- const text = await navigator.clipboard.readText();
393
- textarea.value = text;
394
- // ペースト後に自動保存
395
- saveToUserStorage(true);
396
- } catch (err) {
397
- console.error('クリップボードからのペーストに失敗しました:', err);
398
- alert('クリップボードからのペーストに失敗しました');
399
- }
400
- }
401
-
402
-
403
- </script>
404
- </body>
405
-
406
- </html>