ponkotsu-md-editor 0.1.42 → 0.2.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5a8a908a696cbe6105a8819e32fb39b259be229f70e504a7d922d18a0ab0258
4
- data.tar.gz: 84047bfb4299b9f115f111f840baab12475e564b80e5df448dcb68c5ca6354e3
3
+ metadata.gz: 62a1a937b8cf271a226386c2661c8decedc24ae6b2189e92ba2358f8a9428228
4
+ data.tar.gz: 72feda410a62df3df6f05ba7fae531eb7c44f8f1fc77d73284d85a1b69258efa
5
5
  SHA512:
6
- metadata.gz: 5256725317dbdd1ac8e5bf0361bcf704e0d26b547794d54657260a49994c44e1617b8ce5615f58029d10816c0ae5c3b99b2e559658aca851c104bf42cbc4eec3
7
- data.tar.gz: 2c047a04c7e63a2ee05e5ff5de221d703c3643bfb27c309eab4ed4d2ba25e883e90803c5c410558b5e16c9613e56a78480419d0f6e6fd5c2c134c9ff9445b752
6
+ metadata.gz: 57667cc623bba7c40bb54d83dedc7eb8b7a85af87a6b4314f67a43ced501b7d068ef770b0d43b055d8ed5c9cb039c764b16800d5e28c45e19c44d2f6f495590f
7
+ data.tar.gz: e998286e7ddfead67721ed9d287ec148b770e7a1c9f0ec9b360fe02ceec263dca7e146b2608d959aef970d6b4c140f07e2622476c1006d38fbdf3107963eb06d
@@ -30,6 +30,30 @@
30
30
 
31
31
  let isPreviewMode = false;
32
32
 
33
+ const sampleHtml = "aaa bbb ccc ddd eee\n" +
34
+ " \n" +
35
+ " <div>aaa bbb ccc ddd eee</div><div><br></div><div>### aaa bbb ccc ddd eee</div><div></div><div>####</div><div><br></div><div>aaa bbb ccc ddd eee</div>";
36
+
37
+ const actual = analyzeHtml(sampleHtml);
38
+
39
+ function assertEqual(actual, expected, message) {
40
+ if (actual !== expected) {
41
+ console.error('Assertion failed:' + message + '\n' +
42
+ 'Expected:' + expected + '\n' +
43
+ 'Actual:', actual);
44
+ } else {
45
+ console.log('Assertion passed:', message);
46
+ }
47
+ }
48
+
49
+ assertEqual(actual[0], 'aaa bbb ccc ddd eee\n \n ', "Line 1");
50
+ assertEqual(actual[1], `aaa bbb ccc ddd eee`, "Line 2");
51
+ assertEqual(actual[2], "⹉", "Line 3 (empty line)");
52
+ assertEqual(actual[3], "### aaa bbb ccc ddd eee", "Line 4");
53
+ assertEqual(actual[4], "####", "Line 5 (header only)");
54
+ assertEqual(actual[5], "⹉", "Line 6 (empty line)");
55
+ assertEqual(actual[6], "aaa bbb ccc ddd eee", "Line 7");
56
+
33
57
  // Selection range utilities for contenteditable elements (precision enhanced version)
34
58
  function getContentEditableSelection(element) {
35
59
  const selection = window.getSelection();
@@ -272,6 +296,379 @@
272
296
  element.innerText = text;
273
297
  }
274
298
 
299
+ function getLineAndCharIndex(container, offset) {
300
+ const walker = document.createTreeWalker(
301
+ container,
302
+ NodeFilter.SHOW_TEXT,
303
+ null,
304
+ false
305
+ );
306
+
307
+ let currentOffset = 0;
308
+ let lineNumber = 0;
309
+ let charInLine = 0;
310
+ let node;
311
+
312
+ while (node = walker.nextNode()) {
313
+ const nodeText = node.textContent;
314
+ const nodeLength = nodeText.length;
315
+
316
+ if (currentOffset + nodeLength >= offset) {
317
+ const offsetInNode = offset - currentOffset;
318
+ const textBeforeOffset = nodeText.substring(0, offsetInNode);
319
+
320
+ const allTextBefore = container.textContent.substring(0, currentOffset + offsetInNode);
321
+ const linesBeforeOffset = allTextBefore.split('\n');
322
+
323
+ lineNumber = linesBeforeOffset.length - 1;
324
+ charInLine = linesBeforeOffset[linesBeforeOffset.length - 1].length;
325
+
326
+ break;
327
+ }
328
+
329
+ currentOffset += nodeLength;
330
+ }
331
+
332
+ return { line: lineNumber, char: charInLine };
333
+ }
334
+
335
+ function getOffsetInContainer(container, node, offset) {
336
+ // DOM構造をリニアに変換して位置を計算
337
+ function buildLinearTextMap(container) {
338
+ let textMap = [];
339
+ let currentPos = 0;
340
+
341
+ function processNode(node) {
342
+ if (node.nodeType === Node.TEXT_NODE) {
343
+ const text = node.textContent;
344
+ textMap.push({
345
+ node: node,
346
+ type: 'text',
347
+ start: currentPos,
348
+ end: currentPos + text.length,
349
+ text: text
350
+ });
351
+ currentPos += text.length;
352
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
353
+ if (node.tagName === 'DIV') {
354
+ if (node.children.length === 1 && node.children[0].tagName === 'BR') {
355
+ textMap.push({
356
+ node: node,
357
+ type: 'div-br',
358
+ start: currentPos,
359
+ end: currentPos + 1,
360
+ text: '\n'
361
+ });
362
+ currentPos += 1;
363
+ return;
364
+ } else if (node.innerHTML === '<br>' || node.innerHTML === '<br/>' || node.innerHTML === '') {
365
+ textMap.push({
366
+ node: node,
367
+ type: 'empty-div',
368
+ start: currentPos,
369
+ end: currentPos + 1,
370
+ text: '\n'
371
+ });
372
+ currentPos += 1;
373
+ return;
374
+ }
375
+ } else if (node.tagName === 'BR') {
376
+ textMap.push({
377
+ node: node,
378
+ type: 'br',
379
+ start: currentPos,
380
+ end: currentPos + 1,
381
+ text: '\n'
382
+ });
383
+ currentPos += 1;
384
+ return;
385
+ }
386
+
387
+ // 子ノードを処理
388
+ for (let child of node.childNodes) {
389
+ processNode(child);
390
+ }
391
+ }
392
+ }
393
+
394
+ // コンテナの直接の子ノードから開始
395
+ for (let child of container.childNodes) {
396
+ processNode(child);
397
+ }
398
+
399
+ return textMap;
400
+ }
401
+
402
+ const textMap = buildLinearTextMap(container);
403
+
404
+ // 対象ノードを見つけて位置を計算
405
+ for (let item of textMap) {
406
+ if (item.node === node) {
407
+ if (node.nodeType === Node.TEXT_NODE) {
408
+ return item.start + offset;
409
+ } else {
410
+ // 要素ノードの場合は内部オフセットを計算
411
+ let internalOffset = 0;
412
+ for (let i = 0; i < offset && i < node.childNodes.length; i++) {
413
+ const child = node.childNodes[i];
414
+ if (child.nodeType === Node.TEXT_NODE) {
415
+ internalOffset += child.textContent.length;
416
+ } else if (child.nodeType === Node.ELEMENT_NODE && child.tagName === 'BR') {
417
+ internalOffset += 1;
418
+ }
419
+ }
420
+ return item.start + internalOffset;
421
+ }
422
+ }
423
+ }
424
+
425
+ return 0;
426
+ }
427
+
428
+ function selectLineNumberAndCharIndex(beginEndLenStrings, beginCharIndex, endCharIndex) {
429
+ let retBeginLine = 0, retBeginCharIndex = 0;
430
+ let retEndLine = 0, retEndCharIndex = 0;
431
+ let emptyLineCount = 0;
432
+
433
+ for (let i = 0; i < beginEndLenStrings.length; i++) {
434
+ const line = beginEndLenStrings[i];
435
+
436
+ if (line.str === "⹉") {
437
+ emptyLineCount++;
438
+ }
439
+
440
+ if (beginCharIndex >= line.begin && beginCharIndex <= line.end) {
441
+ retBeginLine = i;
442
+ retBeginCharIndex = beginCharIndex - line.begin + emptyLineCount;
443
+ break;
444
+ }
445
+ }
446
+
447
+ emptyLineCount = 0;
448
+
449
+ for (let i = 0; i < beginEndLenStrings.length; i++) {
450
+ const line = beginEndLenStrings[i];
451
+
452
+ if (line.str === "⹉") {
453
+ emptyLineCount++;
454
+ }
455
+
456
+ if (endCharIndex >= line.begin && endCharIndex <= line.end) {
457
+ retEndLine = i;
458
+ retEndCharIndex = endCharIndex - line.begin + emptyLineCount;
459
+ break;
460
+ }
461
+ }
462
+
463
+ return { begin: { line: retBeginLine, char: retBeginCharIndex }, end: { line: retEndLine, char: retEndCharIndex } };
464
+ }
465
+
466
+ function replaceLineNumberAndCharIndex(beginEndLenStrings, targetTextPosition, before, after) {
467
+ let newLines = [];
468
+ for (let i = 0; i < beginEndLenStrings.length; i++) {
469
+ const line = beginEndLenStrings[i];
470
+
471
+ // 改行行は変更せずそのまま保持
472
+ if (line.str === "⹉") {
473
+ newLines.push(line.str);
474
+ continue;
475
+ }
476
+
477
+ if (i === targetTextPosition.begin.line && i === targetTextPosition.end.line) {
478
+ const first = line.str.substring(0, targetTextPosition.begin.char);
479
+ const target = line.str.substring(targetTextPosition.begin.char, targetTextPosition.end.char);
480
+ const last = line.str.substring(targetTextPosition.end.char);
481
+ newLines.push(first + before + target + after + last);
482
+ }
483
+ else if (i === targetTextPosition.begin.line) {
484
+ const first = line.str.substring(0, targetTextPosition.begin.char);
485
+ const target = line.str.substring(targetTextPosition.begin.char);
486
+ newLines.push(first + before + target + after);
487
+ }
488
+ else if (i === targetTextPosition.end.line) {
489
+ const target = line.str.substring(0, targetTextPosition.end.char);
490
+ const last = line.str.substring(targetTextPosition.end.char);
491
+ newLines.push(before + target + after + last);
492
+ }
493
+ else {
494
+ newLines.push(line.str);
495
+ }
496
+ }
497
+ return newLines;
498
+ }
499
+
500
+ function replaceLine(beginEndLenStrings, selectedLine, before, after) {
501
+
502
+ let newLines = [];
503
+ for (let i = 0; i < beginEndLenStrings.length; i++) {
504
+ const line = beginEndLenStrings[i];
505
+ if (i === selectedLine) {
506
+ let addingSpace = "";
507
+ if (!line.str.startsWith(" ")) {
508
+ addingSpace += " ";
509
+ }
510
+ newLines.push(before + addingSpace + line.str + after);
511
+ } else {
512
+ newLines.push(line.str);
513
+ }
514
+ }
515
+
516
+ return newLines;
517
+ }
518
+
519
+ function convertToInnerHtml(newLines) {
520
+ let html = "";
521
+ for (let i = 0; i < newLines.length; i++) {
522
+ if (i === 0) {
523
+ html += newLines[i].split("⹉").join("");
524
+ } else {
525
+ let insert = newLines[i];
526
+ if (insert === "⹉") {
527
+ insert = insert.split("⹉").join("<br>");
528
+ } else {
529
+ insert = insert.split("⹉").join("");
530
+ }
531
+ html += "<div>" + insert + "</div>";
532
+ }
533
+ }
534
+ return html;
535
+ }
536
+
537
+ function getCurrentLineIndex(beginEndLenStrings) {
538
+ const selection = window.getSelection();
539
+ if (!selection.rangeCount) {
540
+ return null;
541
+ }
542
+
543
+ const range = selection.getRangeAt(0);
544
+
545
+ if (!range.collapsed) {
546
+ return null;
547
+ }
548
+
549
+ const beforeCursorText = getTextBeforeCursor(range.startContainer, range.startOffset, beginEndLenStrings);
550
+ const beforeCursorTextLength = beforeCursorText.length;
551
+ let selectedLine = -1;
552
+ for (let i = 0; i < beginEndLenStrings.length; i++) {
553
+ const line = beginEndLenStrings[i];
554
+ if (line.begin <= beforeCursorTextLength && beforeCursorTextLength <= line.end) {
555
+ selectedLine = i;
556
+ break;
557
+ }
558
+ }
559
+ return {
560
+ line: selectedLine
561
+ };
562
+ }
563
+
564
+ function getTextBeforeCursor(container, offset, beginEndLenStrings) {
565
+ const selection = window.getSelection();
566
+ if (!selection.rangeCount) return '';
567
+
568
+ const range = selection.getRangeAt(0);
569
+ const textarea = document.getElementById('editor_content');
570
+
571
+ try {
572
+ // 全テキストとカーソル位置を取得
573
+ const fullText = textarea.innerText || '';
574
+ const text = getCursorPositionInInnerText(textarea, range.startContainer, range.startOffset);
575
+ return text;
576
+ } catch (error) {
577
+ console.error('Error in getTextBeforeCursor:', error);
578
+ return '';
579
+ }
580
+ }
581
+
582
+ function getCursorPositionInInnerText(container, cursorNode, cursorOffset) {
583
+ try {
584
+ const beforeRange = document.createRange();
585
+ beforeRange.setStart(container, 0);
586
+ beforeRange.setEnd(cursorNode, cursorOffset);
587
+
588
+ // cloneContentsを使用してDOM構造を保持
589
+ const fragment = beforeRange.cloneContents();
590
+ const tempDiv = document.createElement('div');
591
+ tempDiv.appendChild(fragment);
592
+
593
+ // innerTextで正確な文字数を取得(空行も含む)
594
+ const r = analyzeHtml(tempDiv.innerHTML.replace('\n \n ', ''), true).join('');
595
+ return r;
596
+
597
+ } catch (error) {
598
+ console.error('Position calculation failed:', error);
599
+ return 0;
600
+ }
601
+ }
602
+
603
+ function analyzeHtml(target, isCountEmptyDiv = false) {
604
+ let lines = [];
605
+ let remain = target;
606
+
607
+ while (remain.length > 0) {
608
+ // <div><br></div> パターン(改行)- 常に維持
609
+ if (remain.startsWith("<div><br></div>")) {
610
+ lines.push("⹉");
611
+ remain = remain.substring(15);
612
+ continue;
613
+ }
614
+
615
+ // <div></div> パターン(空のdiv)- isCountEmptyDivで制御
616
+ if (remain.startsWith("<div></div>")) {
617
+ if (isCountEmptyDiv) {
618
+ lines.push("⹉");
619
+ }
620
+ remain = remain.substring(11);
621
+ continue;
622
+ }
623
+
624
+ // <div>内容</div> パターン
625
+ const divMatch = remain.match(/^<div>([^<]*)<\/div>/);
626
+ if (divMatch) {
627
+ lines.push(divMatch[1]);
628
+ remain = remain.substring(divMatch[0].length);
629
+ continue;
630
+ }
631
+
632
+ // <br> パターン(単体の改行)
633
+ if (remain.startsWith("<br>")) {
634
+ lines.push("⹉");
635
+ remain = remain.substring(4);
636
+ continue;
637
+ }
638
+
639
+ // <div> の開始を探す
640
+ const divIndex = remain.indexOf("<div>");
641
+ if (divIndex > 0) {
642
+ lines.push(remain.substring(0, divIndex));
643
+ remain = remain.substring(divIndex);
644
+ continue;
645
+ }
646
+
647
+ // その他のタグまたは残りのテキスト
648
+ const nextTagIndex = remain.indexOf("<");
649
+ if (nextTagIndex === -1) {
650
+ if (remain.trim()) {
651
+ lines.push(remain);
652
+ }
653
+ break;
654
+ }
655
+
656
+ if (nextTagIndex > 0) {
657
+ lines.push(remain.substring(0, nextTagIndex));
658
+ remain = remain.substring(nextTagIndex);
659
+ } else {
660
+ const tagEnd = remain.indexOf(">");
661
+ if (tagEnd !== -1) {
662
+ remain = remain.substring(tagEnd + 1);
663
+ } else {
664
+ break;
665
+ }
666
+ }
667
+ }
668
+
669
+ return lines;
670
+ }
671
+
275
672
  // Markdown text insertion functionality
276
673
  window.insertMarkdown = function(before, after) {
277
674
  after = after || '';
@@ -284,62 +681,148 @@
284
681
  try {
285
682
  // Check if element is contenteditable
286
683
  const isContentEditable = textarea.contentEditable === 'true' ||
287
- textarea.getAttribute('contenteditable') === 'true';
684
+ textarea.getAttribute('contenteditable') === 'true';
288
685
 
289
- let start, end, selectedText;
686
+ let lines = analyzeHtml(textarea.innerHTML.replace('\n \n ', ''));
290
687
 
291
- if (isContentEditable) {
292
- // For contenteditable elements
293
- const selection = getContentEditableSelection(textarea);
294
- start = selection.start;
295
- end = selection.end;
296
- selectedText = selection.selectedText;
297
- } else {
298
- // For regular textarea elements
299
- start = textarea.selectionStart || 0;
300
- end = textarea.selectionEnd || 0;
301
- selectedText = textarea.value.substring(start, end);
688
+ // === 修正部分:DOM構造に基づいたテキスト表現を構築 ===
689
+ let domBasedText = '';
690
+ for (let i = 0; i < lines.length; i++) {
691
+ if (lines[i] === "⹉") {
692
+ domBasedText += '\n'; // 改行として1文字
693
+ } else {
694
+ domBasedText += lines[i];
695
+ }
302
696
  }
303
697
 
304
- // fullTextの定義をここで行う
305
- const fullText = isContentEditable ?
306
- (textarea.innerText || textarea.textContent || '') :
307
- textarea.value;
698
+ // beginEndLenStringsをDOM構造ベースで構築
699
+ let offset = 0;
700
+ let beginEndLenStrings = [];
701
+ for (let i = 0; i < lines.length; i++) {
702
+ let line = lines[i];
703
+ let actualLength;
308
704
 
309
- // Bold(**)の場合のみ、選択範囲の両端に余分な空白・改行・文末記号があれば除外
310
- if (before === '**' && after === '**' && selectedText.length > 0) {
311
- // 両端の「改行・空白・文末記号」をすべて除去(複数連続も対応)
312
- while (/^[\s\n.,;:!?]+/.test(selectedText)) {
313
- selectedText = selectedText.replace(/^[\s\n.,;:!?]+/, '');
314
- }
315
- while (/[\s\n.,;:!?]+$/.test(selectedText)) {
316
- selectedText = selectedText.replace(/[\s\n.,;:!?]+$/, '');
705
+ if (line === "⹉") {
706
+ actualLength = 1; // 改行として1文字
707
+ } else {
708
+ actualLength = line.length;
317
709
  }
710
+
711
+ beginEndLenStrings.push({
712
+ begin: offset,
713
+ end: offset + actualLength,
714
+ len: actualLength,
715
+ str: line,
716
+ });
717
+ offset += actualLength;
718
+ }
719
+ // ===================================================
720
+
721
+ // len:0のものを削除(元の処理を保持)
722
+ beginEndLenStrings = beginEndLenStrings.filter(line => line.len > 0 || line.str === "⹉");
723
+ if (beginEndLenStrings.length === 0) {
724
+ beginEndLenStrings.push({
725
+ begin: 0,
726
+ end: 1,
727
+ len: 1,
728
+ str: "⹉",
729
+ emptyLineCount: 1
730
+ });
318
731
  }
319
732
 
320
- const beforeText = fullText.substring(0, start);
321
- const afterText = fullText.substring(end);
322
- const newText = before + selectedText + after;
733
+ // 選択範囲の取得
734
+ const selection = window.getSelection();
323
735
 
324
- const newFullText = beforeText + newText + afterText;
736
+ let selectLineMode = false;
737
+ let selectedLinePos;
325
738
 
326
- if (isContentEditable) {
327
- textarea.innerText = newFullText;
328
- } else {
329
- textarea.value = newFullText;
739
+ if (!selection.rangeCount || selection.isCollapsed) {
740
+ // 行選択モード
741
+ selectLineMode = true;
742
+ selectedLinePos = getCurrentLineIndex(beginEndLenStrings);
330
743
  }
331
744
 
332
- // Adjust cursor position
333
- const newCursorPos = selectedText.length > 0 ?
334
- start + newText.length :
335
- start + before.length;
745
+ const range = selection.getRangeAt(0);
336
746
 
337
- textarea.focus();
747
+ if (!textarea.contains(range.commonAncestorContainer) &&
748
+ range.commonAncestorContainer !== textarea) {
749
+ return;
750
+ }
751
+
752
+ const startOffset = getOffsetInContainer(textarea, range.startContainer, range.startOffset);
753
+ const endOffset = getOffsetInContainer(textarea, range.endContainer, range.endOffset);
754
+
755
+ // === デバッグ出力(削除可能)===
756
+ console.log('DOM-based text:', JSON.stringify(domBasedText));
757
+ console.log('beginEndLenStrings:', beginEndLenStrings);
758
+ console.log('Selection offsets:', startOffset, endOffset);
759
+ console.log('Selected text should be:', JSON.stringify(domBasedText.substring(startOffset, endOffset)));
760
+ // =============================
761
+
762
+ const startPos = getLineAndCharIndex(textarea, startOffset);
763
+ const endPos = getLineAndCharIndex(textarea, endOffset);
764
+
765
+ const selectedTextContent = selection.toString();
766
+
767
+ const targetTextPosition = selectLineNumberAndCharIndex(beginEndLenStrings, startOffset, endOffset);
768
+
769
+ const newLines = !selectLineMode
770
+ ? replaceLineNumberAndCharIndex(beginEndLenStrings, targetTextPosition, before, after)
771
+ : replaceLine(beginEndLenStrings, selectedLinePos.line, before, after);
772
+
773
+ const newFullHTML = convertToInnerHtml(newLines);
338
774
 
339
775
  if (isContentEditable) {
340
- setContentEditableSelection(textarea, newCursorPos, newCursorPos);
341
- } else if (textarea.setSelectionRange) {
342
- textarea.setSelectionRange(newCursorPos, newCursorPos);
776
+ textarea.innerHTML = newFullHTML;
777
+ textarea.focus();
778
+
779
+ // 挿入されたテキストノードを直接探してカーソルを設定
780
+ const insertedText = selectedTextContent.length > 0 ?
781
+ (before + selectedTextContent + after) :
782
+ (before + after);
783
+
784
+ // 新しく挿入されたテキストの終端を探す
785
+ const walker = document.createTreeWalker(
786
+ textarea,
787
+ NodeFilter.SHOW_TEXT,
788
+ null,
789
+ false
790
+ );
791
+
792
+ let foundNode = null;
793
+ let foundOffset = 0;
794
+ let node;
795
+
796
+ while (node = walker.nextNode()) {
797
+ if (node.textContent.includes(insertedText)) {
798
+ // 挿入されたテキストを含むノードを発見
799
+ const textIndex = node.textContent.indexOf(insertedText);
800
+ if (textIndex !== -1) {
801
+ foundNode = node;
802
+ foundOffset = textIndex + insertedText.length;
803
+ break;
804
+ }
805
+ }
806
+ }
807
+
808
+ if (foundNode) {
809
+ // 見つかったテキストノード内にカーソルを設定
810
+ const range = document.createRange();
811
+ range.setStart(foundNode, Math.min(foundOffset, foundNode.textContent.length));
812
+ range.collapse(true);
813
+
814
+ const selection = window.getSelection();
815
+ selection.removeAllRanges();
816
+ selection.addRange(range);
817
+ } else {
818
+ // フォールバック: 従来の方法
819
+ const newDomText = buildDomBasedText(textarea);
820
+ const targetPosition = Math.min(
821
+ startOffset + insertedText.length,
822
+ newDomText.length
823
+ );
824
+ setContentEditableSelection(textarea, targetPosition, targetPosition);
825
+ }
343
826
  }
344
827
 
345
828
  // Fire input event
@@ -350,12 +833,25 @@
350
833
  }
351
834
  };
352
835
 
836
+ function buildDomBasedText(element) {
837
+ const lines = analyzeHtml(element.innerHTML);
838
+ let domText = '';
839
+ for (let line of lines) {
840
+ if (line === "⹉") {
841
+ domText += '\n';
842
+ } else {
843
+ domText += line;
844
+ }
845
+ }
846
+ return domText;
847
+ }
848
+
353
849
  window.insertCode = function() {
354
850
  const textarea = document.getElementById('editor_content');
355
851
 
356
852
  // Check if element is contenteditable
357
853
  const isContentEditable = textarea.contentEditable === 'true' ||
358
- textarea.getAttribute('contenteditable') === 'true';
854
+ textarea.getAttribute('contenteditable') === 'true';
359
855
 
360
856
  let selectedText;
361
857
  if (isContentEditable) {
@@ -388,7 +884,7 @@
388
884
  // Switch to preview mode
389
885
  // Check if element is contenteditable and get text
390
886
  const isContentEditable = textarea.contentEditable === 'true' ||
391
- textarea.getAttribute('contenteditable') === 'true';
887
+ textarea.getAttribute('contenteditable') === 'true';
392
888
 
393
889
  const markdownText = isContentEditable ?
394
890
  (textarea.innerText || textarea.textContent || '') :
@@ -731,6 +1227,11 @@
731
1227
  });
732
1228
  }
733
1229
 
1230
+ function applyTable() {
1231
+ const tableMarkdown = '| 列1 | 列2 | 列3 |<br>|-----|-----|-----|<br>| セル1 | セル2 | セル3 |<br>| セル4 | セル5 | セル6 |<br><br>';
1232
+ insertMarkdown(tableMarkdown, '');
1233
+ }
1234
+
734
1235
  // Enhanced code function with block detection
735
1236
  function applyCodeSmart() {
736
1237
  const activeElement = document.activeElement;
@@ -767,6 +1268,7 @@
767
1268
  window.applyList = applyList;
768
1269
  window.applyOrderedList = applyOrderedList;
769
1270
  window.applyBlockquote = applyBlockquote;
1271
+ window.applyTable = applyTable;
770
1272
 
771
1273
  // Allowed script tag sources (whitelist)
772
1274
  const ALLOWED_SCRIPT_SOURCES = [
@@ -1329,7 +1831,7 @@
1329
1831
 
1330
1832
  // Check if element is contenteditable
1331
1833
  const isContentEditable = textarea.contentEditable === 'true' ||
1332
- textarea.getAttribute('contenteditable') === 'true';
1834
+ textarea.getAttribute('contenteditable') === 'true';
1333
1835
 
1334
1836
  if (isContentEditable) {
1335
1837
  // For contenteditable elements
@@ -1,6 +1,5 @@
1
1
  <%
2
2
  _form = locals[:form]
3
- _content = locals[:content]
4
3
  _options = {
5
4
  lang: locals[:options][:lang] || :en,
6
5
  preview: locals[:options][:preview] || true,
@@ -60,7 +59,7 @@
60
59
 
61
60
  <div class="markdown-editor">
62
61
  <%= render "ponkotsu_md_editor/toolbar", locals: { options: _options } %>
63
- <%= render "ponkotsu_md_editor/input_area", locals: { form: _form, content: _content, options: _options } %>
62
+ <%= render "ponkotsu_md_editor/input_area", locals: { form: _form, attribute: locals[:attribute], options: _options } %>
64
63
  </div>
65
64
  <div class="form-text medium-contrast-text">
66
65
  <strong><%= case _options[:lang]
@@ -1,6 +1,5 @@
1
1
  <%
2
2
  _form = locals[:form]
3
- _content = locals[:content]
4
3
  _placeholder = locals[:options][:placeholder]
5
4
  %>
6
5
 
@@ -9,14 +8,14 @@
9
8
  contenteditable="true"
10
9
  data-field="content"
11
10
  class="form-control markdown-textarea">
12
- <%= raw _content %>
11
+ <%= raw locals[:attribute] %>
13
12
  </div>
14
13
 
15
14
  <div id="editor_content_placeholder">
16
15
  <%= _placeholder %>
17
16
  </div>
18
17
 
19
- <%= _form.hidden_field _content, id: "content_hidden_field" %>
18
+ <%= _form.hidden_field locals[:attribute], id: "content_hidden_field" %>
20
19
 
21
20
  <%= render "ponkotsu_md_editor/preview_area" %>
22
21
  </div>
@@ -22,9 +22,9 @@ module PonkotsuMdEditor
22
22
  # tools: [ :bold, :italic, :strikethrough ],
23
23
  # placeholder: "This is Placeholder."
24
24
  # }) %>
25
- def markdown_editor(form, content, options = {})
25
+ def markdown_editor(form, attribute, options = {})
26
26
  form = form[:form] if form.is_a?(Hash)
27
- render "ponkotsu_md_editor/editor", locals: { content: content, form: form, options: options }
27
+ render "ponkotsu_md_editor/editor", locals: { attribute: attribute, form: form, options: options }
28
28
  end
29
29
  end
30
30
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PonkotsuMdEditor
4
- VERSION = "0.1.42"
4
+ VERSION = "0.2.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ponkotsu-md-editor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.42
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - dhq_boiler