egregore-artifacts 0.10.1 → 0.10.2
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/lib/index.js +0 -10
- package/lib/render.js +1 -11
- package/lib/shell.js +0 -436
- package/package.json +1 -1
- package/lib/comments.js +0 -248
- package/lib/edit-client.js +0 -866
- package/lib/edit-shell.js +0 -64
- package/lib/edit-styles.js +0 -537
package/lib/index.js
CHANGED
|
@@ -21,12 +21,10 @@ import { boardTemplate } from './templates/board.js';
|
|
|
21
21
|
import { networkTemplate } from './templates/network.js';
|
|
22
22
|
import { openInBrowser } from './open.js';
|
|
23
23
|
import { setLinkContext, clearLinkContext } from './markdown.js';
|
|
24
|
-
import { derivedParent, loadComments } from './comments.js';
|
|
25
24
|
|
|
26
25
|
const PARSERS = { quest: parseQuest, handoff: parseHandoff, 'handoff-v1': parseHandoffV1, emissary: parseEmissary, activity: parseActivity, document: parseDocument, board: parseBoard, network: parseNetwork };
|
|
27
26
|
const TEMPLATES = { quest: questTemplate, handoff: handoffTemplate, 'handoff-v1': handoffV1Template, emissary: emissaryTemplate, activity: activityTemplate, document: documentTemplate, board: boardTemplate, network: networkTemplate };
|
|
28
27
|
|
|
29
|
-
const COMMENTABLE_TYPES = new Set(['quest', 'handoff', 'document']);
|
|
30
28
|
export async function generateArtifact(type, input, options = {}) {
|
|
31
29
|
const template = TEMPLATES[type];
|
|
32
30
|
if (!template) throw new Error(`Unknown artifact type: ${type}. Available: ${Object.keys(TEMPLATES).join(', ')}`);
|
|
@@ -40,12 +38,6 @@ export async function generateArtifact(type, input, options = {}) {
|
|
|
40
38
|
data = input;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
let parent = null;
|
|
44
|
-
let comments = [];
|
|
45
|
-
if (typeof input === 'string' && COMMENTABLE_TYPES.has(type)) {
|
|
46
|
-
parent = derivedParent(type, input);
|
|
47
|
-
if (parent) comments = loadComments(parent);
|
|
48
|
-
}
|
|
49
41
|
// Link context for inlineMarkdown — only enabled when both values are supplied.
|
|
50
42
|
// Missing either falls through to plain <code> rendering (OSS mode).
|
|
51
43
|
setLinkContext({ viewBase: options.viewBase, orgSlug: options.orgSlug });
|
|
@@ -54,8 +46,6 @@ export async function generateArtifact(type, input, options = {}) {
|
|
|
54
46
|
return renderToHtml(element, {
|
|
55
47
|
title: data.title || 'Untitled',
|
|
56
48
|
type,
|
|
57
|
-
parent,
|
|
58
|
-
comments,
|
|
59
49
|
});
|
|
60
50
|
} finally {
|
|
61
51
|
clearLinkContext();
|
package/lib/render.js
CHANGED
|
@@ -4,22 +4,12 @@ import { renderToStaticMarkup } from 'react-dom/server';
|
|
|
4
4
|
import { Renderer, JSONUIProvider, createStateStore } from '@json-render/react';
|
|
5
5
|
import { registry } from './registry.js';
|
|
6
6
|
import { htmlShell } from './shell.js';
|
|
7
|
-
import { CommentSection } from './comments.js';
|
|
8
7
|
|
|
9
8
|
const h = React.createElement;
|
|
10
9
|
|
|
11
10
|
// Render a React element tree (from direct templates).
|
|
12
|
-
// If `options.parent` is set, append a CommentSection so every commentable
|
|
13
|
-
// artifact gets the same thread + composer treatment without per-template work.
|
|
14
11
|
export function renderToHtml(element, options = {}) {
|
|
15
|
-
const
|
|
16
|
-
? h('div', null, element, h(CommentSection, {
|
|
17
|
-
parent: options.parent,
|
|
18
|
-
comments: options.comments || [],
|
|
19
|
-
}))
|
|
20
|
-
: element;
|
|
21
|
-
|
|
22
|
-
const bodyHtml = renderToStaticMarkup(finalElement);
|
|
12
|
+
const bodyHtml = renderToStaticMarkup(element);
|
|
23
13
|
return htmlShell(bodyHtml, {
|
|
24
14
|
title: options.title || 'Egregore Artifact',
|
|
25
15
|
type: options.type || 'artifact',
|
package/lib/shell.js
CHANGED
|
@@ -465,239 +465,6 @@ export function htmlShell(bodyHtml, { title = 'Egregore Artifact', type = 'artif
|
|
|
465
465
|
.eg-theme-toggle { top: 0.75rem; right: 0.75rem; width: 32px; height: 32px; font-size: 14px; }
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
/* ── Comments section ─────────────────────────────── */
|
|
469
|
-
.eg-comments-section {
|
|
470
|
-
margin-top: 3rem;
|
|
471
|
-
padding-top: 2rem;
|
|
472
|
-
border-top: 1px solid var(--hairline);
|
|
473
|
-
}
|
|
474
|
-
.eg-comments-heading {
|
|
475
|
-
display: flex;
|
|
476
|
-
align-items: baseline;
|
|
477
|
-
gap: 0.5rem;
|
|
478
|
-
font-family: var(--font-sans);
|
|
479
|
-
font-size: 18px;
|
|
480
|
-
font-weight: 600;
|
|
481
|
-
color: var(--black);
|
|
482
|
-
margin-bottom: 1.25rem;
|
|
483
|
-
}
|
|
484
|
-
.eg-comments-bullet { font-size: 16px; }
|
|
485
|
-
.eg-comments-count { color: var(--muted); font-weight: 400; font-size: 15px; }
|
|
486
|
-
.eg-comments-empty {
|
|
487
|
-
font-family: var(--font-sans);
|
|
488
|
-
color: var(--muted);
|
|
489
|
-
font-style: italic;
|
|
490
|
-
padding: 1rem 0 1.5rem;
|
|
491
|
-
}
|
|
492
|
-
.eg-comments-thread {
|
|
493
|
-
display: flex;
|
|
494
|
-
flex-direction: column;
|
|
495
|
-
gap: 1rem;
|
|
496
|
-
margin-bottom: 2rem;
|
|
497
|
-
}
|
|
498
|
-
.eg-comment {
|
|
499
|
-
background: var(--surface);
|
|
500
|
-
border: 1px solid var(--hairline);
|
|
501
|
-
border-radius: 6px;
|
|
502
|
-
padding: 0.875rem 1rem;
|
|
503
|
-
}
|
|
504
|
-
.eg-comment-meta {
|
|
505
|
-
display: flex;
|
|
506
|
-
align-items: center;
|
|
507
|
-
gap: 0.5rem;
|
|
508
|
-
font-family: var(--font-mono);
|
|
509
|
-
font-size: 12px;
|
|
510
|
-
color: var(--muted);
|
|
511
|
-
margin-bottom: 0.5rem;
|
|
512
|
-
}
|
|
513
|
-
.eg-comment-avatar {
|
|
514
|
-
display: inline-flex;
|
|
515
|
-
align-items: center;
|
|
516
|
-
justify-content: center;
|
|
517
|
-
width: 22px; height: 22px;
|
|
518
|
-
border-radius: 50%;
|
|
519
|
-
background: var(--terracotta-chip);
|
|
520
|
-
color: var(--terracotta);
|
|
521
|
-
font-family: var(--font-sans);
|
|
522
|
-
font-size: 11px;
|
|
523
|
-
font-weight: 600;
|
|
524
|
-
}
|
|
525
|
-
.eg-comment-author { color: var(--black); font-weight: 500; }
|
|
526
|
-
.eg-comment-date { font-feature-settings: "tnum"; }
|
|
527
|
-
.eg-comment-to { color: var(--blue-muted); }
|
|
528
|
-
.eg-comment-body {
|
|
529
|
-
font-family: var(--font-serif);
|
|
530
|
-
font-size: 15px;
|
|
531
|
-
line-height: 1.55;
|
|
532
|
-
color: var(--dark);
|
|
533
|
-
}
|
|
534
|
-
.eg-comment-body p { margin: 0 0 0.5rem; }
|
|
535
|
-
.eg-comment-body p:last-child { margin-bottom: 0; }
|
|
536
|
-
.eg-comment-actions {
|
|
537
|
-
margin-top: 0.5rem;
|
|
538
|
-
display: flex;
|
|
539
|
-
justify-content: flex-end;
|
|
540
|
-
}
|
|
541
|
-
.eg-comment-reply {
|
|
542
|
-
background: transparent;
|
|
543
|
-
border: none;
|
|
544
|
-
color: var(--muted);
|
|
545
|
-
font-family: var(--font-mono);
|
|
546
|
-
font-size: 11px;
|
|
547
|
-
cursor: pointer;
|
|
548
|
-
padding: 0.25rem 0.5rem;
|
|
549
|
-
border-radius: 4px;
|
|
550
|
-
}
|
|
551
|
-
.eg-comment-reply:hover { background: var(--neutral-chip); color: var(--terracotta); }
|
|
552
|
-
.eg-comment-reply.eg-comment-reply-active {
|
|
553
|
-
background: var(--terracotta-chip);
|
|
554
|
-
color: var(--terracotta);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/* Composer — a snippet builder, NOT a form that posts */
|
|
558
|
-
.eg-comments-composer {
|
|
559
|
-
background: var(--surface);
|
|
560
|
-
border: 1px solid var(--hairline);
|
|
561
|
-
border-radius: 6px;
|
|
562
|
-
padding: 1rem;
|
|
563
|
-
}
|
|
564
|
-
.eg-comments-composer .eg-card-title {
|
|
565
|
-
font-family: var(--font-sans);
|
|
566
|
-
font-size: 14px;
|
|
567
|
-
font-weight: 600;
|
|
568
|
-
color: var(--black);
|
|
569
|
-
margin-bottom: 0.625rem;
|
|
570
|
-
}
|
|
571
|
-
.eg-comment-target {
|
|
572
|
-
font-family: var(--font-mono);
|
|
573
|
-
font-size: 12px;
|
|
574
|
-
background: var(--neutral-chip);
|
|
575
|
-
color: var(--terracotta);
|
|
576
|
-
padding: 1px 6px;
|
|
577
|
-
border-radius: 3px;
|
|
578
|
-
font-weight: 500;
|
|
579
|
-
}
|
|
580
|
-
.eg-comment-textarea {
|
|
581
|
-
width: 100%;
|
|
582
|
-
box-sizing: border-box;
|
|
583
|
-
background: var(--cream);
|
|
584
|
-
border: 1px solid var(--hairline);
|
|
585
|
-
border-radius: 4px;
|
|
586
|
-
padding: 0.625rem 0.75rem;
|
|
587
|
-
font-family: var(--font-sans);
|
|
588
|
-
font-size: 14px;
|
|
589
|
-
color: var(--black);
|
|
590
|
-
resize: vertical;
|
|
591
|
-
min-height: 96px;
|
|
592
|
-
margin-bottom: 1rem;
|
|
593
|
-
}
|
|
594
|
-
.eg-comment-textarea:focus {
|
|
595
|
-
outline: none;
|
|
596
|
-
border-color: var(--terracotta);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
.eg-comment-preview-label {
|
|
600
|
-
font-family: var(--font-mono);
|
|
601
|
-
font-size: 11px;
|
|
602
|
-
color: var(--muted);
|
|
603
|
-
text-transform: uppercase;
|
|
604
|
-
letter-spacing: 0.05em;
|
|
605
|
-
margin-bottom: 0.375rem;
|
|
606
|
-
}
|
|
607
|
-
.eg-comment-preview-frame {
|
|
608
|
-
position: relative;
|
|
609
|
-
background: var(--terminal-bg);
|
|
610
|
-
border: 1px solid var(--hairline);
|
|
611
|
-
border-left: 3px solid var(--terracotta);
|
|
612
|
-
border-radius: 4px;
|
|
613
|
-
overflow: hidden;
|
|
614
|
-
}
|
|
615
|
-
.eg-comment-preview {
|
|
616
|
-
margin: 0;
|
|
617
|
-
padding: 0.75rem 4.5rem 0.75rem 0.875rem;
|
|
618
|
-
font-family: var(--font-mono);
|
|
619
|
-
font-size: 12.5px;
|
|
620
|
-
line-height: 1.55;
|
|
621
|
-
color: var(--terminal-text);
|
|
622
|
-
white-space: pre-wrap;
|
|
623
|
-
word-break: break-word;
|
|
624
|
-
overflow-x: auto;
|
|
625
|
-
min-height: 1em;
|
|
626
|
-
}
|
|
627
|
-
.eg-comment-preview-code {
|
|
628
|
-
font-family: inherit;
|
|
629
|
-
font-size: inherit;
|
|
630
|
-
color: inherit;
|
|
631
|
-
background: transparent;
|
|
632
|
-
padding: 0;
|
|
633
|
-
border: 0;
|
|
634
|
-
}
|
|
635
|
-
.eg-comment-preview-code:empty::before {
|
|
636
|
-
content: '(your snippet will appear here as you type)';
|
|
637
|
-
color: rgba(255, 255, 255, 0.4);
|
|
638
|
-
font-style: italic;
|
|
639
|
-
}
|
|
640
|
-
.eg-comment-copy-btn {
|
|
641
|
-
position: absolute;
|
|
642
|
-
top: 0.5rem;
|
|
643
|
-
right: 0.5rem;
|
|
644
|
-
background: rgba(255, 255, 255, 0.08);
|
|
645
|
-
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
646
|
-
color: var(--terminal-text);
|
|
647
|
-
border-radius: 3px;
|
|
648
|
-
padding: 0.25rem 0.625rem;
|
|
649
|
-
font-family: var(--font-mono);
|
|
650
|
-
font-size: 11px;
|
|
651
|
-
cursor: pointer;
|
|
652
|
-
}
|
|
653
|
-
.eg-comment-copy-btn:hover {
|
|
654
|
-
background: var(--terracotta);
|
|
655
|
-
border-color: var(--terracotta);
|
|
656
|
-
color: white;
|
|
657
|
-
}
|
|
658
|
-
.eg-comment-copy-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
659
|
-
|
|
660
|
-
.eg-comment-status {
|
|
661
|
-
font-family: var(--font-mono);
|
|
662
|
-
font-size: 12px;
|
|
663
|
-
color: var(--muted);
|
|
664
|
-
margin-top: 0.5rem;
|
|
665
|
-
min-height: 1.2em;
|
|
666
|
-
}
|
|
667
|
-
.eg-comment-status.eg-comment-status-ok { color: var(--green-p1); }
|
|
668
|
-
.eg-comment-status.eg-comment-status-err { color: var(--terracotta); }
|
|
669
|
-
|
|
670
|
-
.eg-comment-help {
|
|
671
|
-
margin-top: 0.875rem;
|
|
672
|
-
padding-top: 0.75rem;
|
|
673
|
-
border-top: 1px dashed var(--hairline);
|
|
674
|
-
font-family: var(--font-mono);
|
|
675
|
-
font-size: 11px;
|
|
676
|
-
color: var(--muted);
|
|
677
|
-
line-height: 1.55;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/* Dual-mode visibility: paste-back by default, live when loopback is up */
|
|
681
|
-
.eg-live-only { display: none; }
|
|
682
|
-
.eg-comments-composer.eg-live-mode .eg-live-only { display: inline; }
|
|
683
|
-
.eg-comments-composer.eg-live-mode .eg-paste-only { display: none; }
|
|
684
|
-
|
|
685
|
-
.eg-comment-post-btn {
|
|
686
|
-
display: none;
|
|
687
|
-
background: var(--terracotta);
|
|
688
|
-
color: white;
|
|
689
|
-
border: none;
|
|
690
|
-
border-radius: 4px;
|
|
691
|
-
padding: 0.625rem 1rem;
|
|
692
|
-
margin-top: 0.75rem;
|
|
693
|
-
font-family: var(--font-sans);
|
|
694
|
-
font-size: 14px;
|
|
695
|
-
font-weight: 500;
|
|
696
|
-
cursor: pointer;
|
|
697
|
-
}
|
|
698
|
-
.eg-comment-post-btn:hover { filter: brightness(1.05); }
|
|
699
|
-
.eg-comment-post-btn:disabled { opacity: 0.5; cursor: progress; }
|
|
700
|
-
.eg-comments-composer.eg-live-mode .eg-comment-post-btn { display: inline-block; }
|
|
701
468
|
</style>
|
|
702
469
|
<script>
|
|
703
470
|
// Theme engine: light / auto / dark
|
|
@@ -807,209 +574,6 @@ ${bodyHtml}
|
|
|
807
574
|
});
|
|
808
575
|
})();
|
|
809
576
|
</script>
|
|
810
|
-
<script>
|
|
811
|
-
// Comment composer — assembles a structured payload and copies it to the
|
|
812
|
-
// clipboard. The user pastes into Claude; the /comment skill recognizes
|
|
813
|
-
// the marker and runs the write/index/notify/commit flow.
|
|
814
|
-
(function() {
|
|
815
|
-
var composer = document.querySelector('.eg-comments-composer');
|
|
816
|
-
if (!composer) return;
|
|
817
|
-
|
|
818
|
-
var parentKind = composer.dataset.parentKind;
|
|
819
|
-
var parentId = composer.dataset.parentId;
|
|
820
|
-
var textarea = composer.querySelector('.eg-comment-textarea');
|
|
821
|
-
var status = composer.querySelector('.eg-comment-status');
|
|
822
|
-
var copyBtn = composer.querySelector('.eg-comment-copy-btn');
|
|
823
|
-
var postBtn = composer.querySelector('.eg-comment-post-btn');
|
|
824
|
-
var previewCode = composer.querySelector('.eg-comment-preview-code');
|
|
825
|
-
var targetChip = composer.querySelector('.eg-comment-target');
|
|
826
|
-
var placeholderDefault = textarea.placeholder;
|
|
827
|
-
var activeReplyTo = null;
|
|
828
|
-
|
|
829
|
-
var SERVER = 'http://127.0.0.1:7321';
|
|
830
|
-
|
|
831
|
-
// Probe the loopback server. If reachable, switch the composer into
|
|
832
|
-
// live mode so the user gets a real "Post comment" button instead of
|
|
833
|
-
// the clipboard handoff.
|
|
834
|
-
fetch(SERVER + '/health', { method: 'GET' })
|
|
835
|
-
.then(function(r) { if (r.ok) composer.classList.add('eg-live-mode'); })
|
|
836
|
-
.catch(function() { /* paste-back stays as the default */ });
|
|
837
|
-
|
|
838
|
-
function buildPayload() {
|
|
839
|
-
var body = textarea.value.trim();
|
|
840
|
-
if (!body) return '';
|
|
841
|
-
var parentArg = activeReplyTo
|
|
842
|
-
? ('reply ' + activeReplyTo)
|
|
843
|
-
: (parentKind + '/' + parentId);
|
|
844
|
-
return '#egregore-comment:v1 ' + parentArg + '\\n\\n' + body;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
function updatePreview() {
|
|
848
|
-
previewCode.textContent = buildPayload();
|
|
849
|
-
// Reset stale "Copied" state when the user edits
|
|
850
|
-
if (status.textContent && status.classList.contains('eg-comment-status-ok')) {
|
|
851
|
-
status.textContent = '';
|
|
852
|
-
status.className = 'eg-comment-status';
|
|
853
|
-
copyBtn.textContent = 'Copy';
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
function updateTarget() {
|
|
858
|
-
if (activeReplyTo) {
|
|
859
|
-
var btn = document.querySelector('.eg-comment-reply[data-reply-to="' + activeReplyTo + '"]');
|
|
860
|
-
var author = btn ? btn.dataset.replyAuthor : '';
|
|
861
|
-
targetChip.textContent = author
|
|
862
|
-
? ('reply → ' + author + ' · ' + activeReplyTo)
|
|
863
|
-
: ('reply → ' + activeReplyTo);
|
|
864
|
-
} else {
|
|
865
|
-
targetChip.textContent = parentKind + '/' + parentId;
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
textarea.addEventListener('input', updatePreview);
|
|
870
|
-
|
|
871
|
-
function clearReplyState() {
|
|
872
|
-
var buttons = document.querySelectorAll('.eg-comment-reply');
|
|
873
|
-
for (var i = 0; i < buttons.length; i++) {
|
|
874
|
-
buttons[i].classList.remove('eg-comment-reply-active');
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
var replyButtons = document.querySelectorAll('.eg-comment-reply');
|
|
879
|
-
for (var i = 0; i < replyButtons.length; i++) {
|
|
880
|
-
replyButtons[i].addEventListener('click', function(e) {
|
|
881
|
-
var btn = e.currentTarget;
|
|
882
|
-
var id = btn.dataset.replyTo;
|
|
883
|
-
var isActive = btn.classList.contains('eg-comment-reply-active');
|
|
884
|
-
clearReplyState();
|
|
885
|
-
if (isActive) {
|
|
886
|
-
activeReplyTo = null;
|
|
887
|
-
textarea.placeholder = placeholderDefault;
|
|
888
|
-
} else {
|
|
889
|
-
btn.classList.add('eg-comment-reply-active');
|
|
890
|
-
activeReplyTo = id;
|
|
891
|
-
textarea.placeholder = 'Replying to ' + id + ' — click ↩ again to cancel';
|
|
892
|
-
}
|
|
893
|
-
updateTarget();
|
|
894
|
-
updatePreview();
|
|
895
|
-
textarea.focus();
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
function setStatus(ok, msg) {
|
|
900
|
-
status.textContent = msg;
|
|
901
|
-
status.className = 'eg-comment-status eg-comment-status-' + (ok ? 'ok' : 'err');
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
// Select the preview frame's text so the user has a visible affordance
|
|
905
|
-
// even if the copy mechanism fails.
|
|
906
|
-
function selectPreview() {
|
|
907
|
-
var range = document.createRange();
|
|
908
|
-
range.selectNodeContents(previewCode);
|
|
909
|
-
var sel = window.getSelection();
|
|
910
|
-
sel.removeAllRanges();
|
|
911
|
-
sel.addRange(range);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
copyBtn.addEventListener('click', function() {
|
|
915
|
-
var payload = buildPayload();
|
|
916
|
-
if (!payload) {
|
|
917
|
-
setStatus(false, 'Type a comment first — there’s nothing to copy.');
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
// Primary path: select the preview frame and execCommand('copy').
|
|
922
|
-
// Synchronous, works on file://, no permission prompt.
|
|
923
|
-
var copied = false;
|
|
924
|
-
try {
|
|
925
|
-
selectPreview();
|
|
926
|
-
copied = !!(document.execCommand && document.execCommand('copy'));
|
|
927
|
-
} catch (e) { copied = false; }
|
|
928
|
-
|
|
929
|
-
if (copied) {
|
|
930
|
-
setStatus(true, '✓ Snippet copied. Paste it into your Claude terminal.');
|
|
931
|
-
copyBtn.textContent = '✓ Copied';
|
|
932
|
-
setTimeout(function() { copyBtn.textContent = 'Copy'; }, 2000);
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// Fallback: modern Clipboard API.
|
|
937
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
938
|
-
try {
|
|
939
|
-
navigator.clipboard.writeText(payload).then(function() {
|
|
940
|
-
setStatus(true, '✓ Snippet copied. Paste it into your Claude terminal.');
|
|
941
|
-
copyBtn.textContent = '✓ Copied';
|
|
942
|
-
setTimeout(function() { copyBtn.textContent = 'Copy'; }, 2000);
|
|
943
|
-
}, function() {
|
|
944
|
-
selectPreview();
|
|
945
|
-
setStatus(false, 'Auto-copy failed — snippet selected above, press ⌘C / Ctrl+C.');
|
|
946
|
-
});
|
|
947
|
-
return;
|
|
948
|
-
} catch (e) { /* fall through */ }
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// Last resort: leave the preview selected for manual copy.
|
|
952
|
-
selectPreview();
|
|
953
|
-
setStatus(false, 'Auto-copy unavailable — snippet selected above, press ⌘C / Ctrl+C.');
|
|
954
|
-
});
|
|
955
|
-
|
|
956
|
-
// Live-mode primary action: POST directly to the loopback server.
|
|
957
|
-
// If the connection is refused (server died since page-load), drop the
|
|
958
|
-
// composer back into paste-back mode rather than leaving the user stuck.
|
|
959
|
-
postBtn.addEventListener('click', function() {
|
|
960
|
-
var body = textarea.value.trim();
|
|
961
|
-
if (!body) {
|
|
962
|
-
setStatus(false, 'Type a comment first.');
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
var parentArg = activeReplyTo
|
|
966
|
-
? ('reply ' + activeReplyTo)
|
|
967
|
-
: (parentKind + '/' + parentId);
|
|
968
|
-
|
|
969
|
-
postBtn.disabled = true;
|
|
970
|
-
postBtn.textContent = 'Posting…';
|
|
971
|
-
setStatus(true, '');
|
|
972
|
-
|
|
973
|
-
// Tell the server where this rendered HTML lives so it can re-render
|
|
974
|
-
// in place. location.pathname for file:// URLs is the absolute path.
|
|
975
|
-
var refreshPath = (location.protocol === 'file:') ? decodeURIComponent(location.pathname) : '';
|
|
976
|
-
|
|
977
|
-
fetch(SERVER + '/api/comment', {
|
|
978
|
-
method: 'POST',
|
|
979
|
-
headers: { 'Content-Type': 'application/json' },
|
|
980
|
-
body: JSON.stringify({ parent: parentArg, body: body, refreshPath: refreshPath }),
|
|
981
|
-
})
|
|
982
|
-
.then(function(r) { return r.json().then(function(j) { return { ok: r.ok, json: j }; }); })
|
|
983
|
-
.then(function(res) {
|
|
984
|
-
if (res.ok && res.json && res.json.ok) {
|
|
985
|
-
var notified = (res.json.notified || []).filter(Boolean);
|
|
986
|
-
var msg = '✓ Posted on ' + res.json.parent;
|
|
987
|
-
if (notified.length) msg += ' · DMed: ' + notified.join(', ');
|
|
988
|
-
setStatus(true, msg + ' — refreshing…');
|
|
989
|
-
postBtn.textContent = '✓ Posted';
|
|
990
|
-
setTimeout(function() { window.location.reload(); }, 800);
|
|
991
|
-
} else {
|
|
992
|
-
postBtn.disabled = false;
|
|
993
|
-
postBtn.textContent = 'Post comment';
|
|
994
|
-
var err = (res.json && res.json.error) || 'unknown error';
|
|
995
|
-
setStatus(false, 'Post failed: ' + err);
|
|
996
|
-
}
|
|
997
|
-
})
|
|
998
|
-
.catch(function(err) {
|
|
999
|
-
// Network-level failure (server died, etc). Drop to paste-back so
|
|
1000
|
-
// the user has a working path — and tell them why.
|
|
1001
|
-
composer.classList.remove('eg-live-mode');
|
|
1002
|
-
postBtn.disabled = false;
|
|
1003
|
-
postBtn.textContent = 'Post comment';
|
|
1004
|
-
setStatus(false,
|
|
1005
|
-
'Local server unreachable (' + (err.message || 'network error') +
|
|
1006
|
-
'). Switched to clipboard mode — use the Copy button.'
|
|
1007
|
-
);
|
|
1008
|
-
updatePreview();
|
|
1009
|
-
});
|
|
1010
|
-
});
|
|
1011
|
-
})();
|
|
1012
|
-
</script>
|
|
1013
577
|
</body>
|
|
1014
578
|
</html>`;
|
|
1015
579
|
}
|