Skip to content

Commit 1ba8cbc

Browse files
Updated shareable preview
1 parent dab2776 commit 1ba8cbc

3 files changed

Lines changed: 288 additions & 31 deletions

File tree

index.html

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,51 @@ <h3 class="modal-section-title">Open-source credits</h3>
481481
</div>
482482
</div>
483483

484+
<!-- Share Modal -->
485+
<div id="share-modal" class="reset-modal-overlay modal-overlay" role="dialog" aria-modal="true" aria-labelledby="share-modal-title" aria-hidden="true" style="display:none;">
486+
<div class="reset-modal-box reset-modal-box--wide modal-box">
487+
<div class="modal-header">
488+
<p id="share-modal-title" class="reset-modal-message">Share Document</p>
489+
<button type="button" class="modal-close-btn" id="share-modal-close-icon" aria-label="Close share dialog">
490+
<i class="bi bi-x-lg"></i>
491+
</button>
492+
</div>
493+
<div class="modal-body">
494+
<p class="share-modal-description">Choose how recipients can interact with this document.</p>
495+
<div class="share-mode-cards">
496+
<label class="share-mode-card" id="share-card-view" for="share-mode-view">
497+
<input type="radio" id="share-mode-view" name="share-mode" value="view" checked />
498+
<span class="share-card-icon"><i class="bi bi-eye"></i></span>
499+
<span class="share-card-body">
500+
<span class="share-card-title">View only</span>
501+
<span class="share-card-desc">Opens in preview mode. The editor is hidden.</span>
502+
</span>
503+
<span class="share-card-check"><i class="bi bi-check-lg"></i></span>
504+
</label>
505+
<label class="share-mode-card" id="share-card-edit" for="share-mode-edit">
506+
<input type="radio" id="share-mode-edit" name="share-mode" value="edit" />
507+
<span class="share-card-icon"><i class="bi bi-pencil-square"></i></span>
508+
<span class="share-card-body">
509+
<span class="share-card-title">Edit</span>
510+
<span class="share-card-desc">Opens in split editor + preview mode.</span>
511+
</span>
512+
<span class="share-card-check"><i class="bi bi-check-lg"></i></span>
513+
</label>
514+
</div>
515+
<div class="share-url-row">
516+
<input type="text" id="share-url-input" class="rename-modal-input share-url-input" readonly placeholder="Generating link…" aria-label="Share URL" />
517+
<button class="reset-modal-btn share-copy-btn" id="share-copy-btn" title="Copy link">
518+
<i class="bi bi-clipboard"></i>
519+
</button>
520+
</div>
521+
<p class="share-modal-notice"><i class="bi bi-info-circle"></i> The entire document is encoded in the URL. No data is sent to any server.</p>
522+
</div>
523+
<div class="reset-modal-actions">
524+
<button class="reset-modal-btn reset-modal-cancel" id="share-modal-close">Close</button>
525+
</div>
526+
</div>
527+
</div>
528+
484529
<!-- Rename Modal -->
485530
<div id="rename-modal" class="reset-modal-overlay" role="dialog" aria-modal="true" aria-labelledby="rename-modal-title" style="display:none;">
486531
<div class="reset-modal-box reset-modal-box--wide">
@@ -717,4 +762,4 @@ <h3 class="modal-section-title">Open-source credits</h3>
717762
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js"></script>
718763
<script src="script.js"></script>
719764
</body>
720-
</html>
765+
</html>

script.js

Lines changed: 120 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,10 @@ document.addEventListener("DOMContentLoaded", function () {
665665
return markdown;
666666
}
667667
resetExtendedMarkdownState();
668-
return applyFootnotes(extractFootnoteDefinitions(markdown));
668+
// ✅ Replace escaped dollar signs before marked.js strips the backslash.
669+
// This prevents MathJax from treating lone $ as a math delimiter.
670+
const protectedMarkdown = markdown.replace(/\\\$/g, '&#36;');
671+
return applyFootnotes(extractFootnoteDefinitions(protectedMarkdown));
669672
},
670673
},
671674
});
@@ -5009,64 +5012,151 @@ This is a fully client-side application. Your content never leaves your browser
50095012
return new TextDecoder().decode(pako.inflate(bytes));
50105013
}
50115014

5012-
function copyShareUrl(btn) {
5015+
// ============================================
5016+
// Share Modal
5017+
// ============================================
5018+
5019+
const shareModal = document.getElementById('share-modal');
5020+
const shareModalCloseX = document.getElementById('share-modal-close-icon');
5021+
const shareModalClose = document.getElementById('share-modal-close');
5022+
const shareUrlInput = document.getElementById('share-url-input');
5023+
const shareCopyBtn = document.getElementById('share-copy-btn');
5024+
const shareModeView = document.getElementById('share-mode-view');
5025+
const shareModeEdit = document.getElementById('share-mode-edit');
5026+
const shareCardView = document.getElementById('share-card-view');
5027+
const shareCardEdit = document.getElementById('share-card-edit');
5028+
5029+
function buildShareUrl(mode) {
50135030
const markdownText = markdownEditor.value;
50145031
let encoded;
50155032
try {
50165033
encoded = encodeMarkdownForShare(markdownText);
50175034
} catch (e) {
5018-
console.error("Share encoding failed:", e);
5019-
alert("Failed to encode content for sharing: " + e.message);
5035+
console.error('Share encoding failed:', e);
5036+
return null;
5037+
}
5038+
// mode=view → #share=<encoded> (opens preview-only)
5039+
// mode=edit → #share=<encoded>&edit=1
5040+
const base = window.location.origin + window.location.pathname + '#share=' + encoded;
5041+
return mode === 'edit' ? base + '&edit=1' : base;
5042+
}
5043+
5044+
function updateShareUrlField() {
5045+
const mode = shareModeView.checked ? 'view' : 'edit';
5046+
const url = buildShareUrl(mode);
5047+
if (!url) {
5048+
shareUrlInput.value = 'Error generating link.';
5049+
shareCopyBtn.disabled = true;
50205050
return;
50215051
}
5052+
const tooLarge = url.length > MAX_SHARE_URL_LENGTH;
5053+
if (tooLarge) {
5054+
shareUrlInput.value = 'Document too large to share via URL.';
5055+
shareCopyBtn.disabled = true;
5056+
} else {
5057+
shareUrlInput.value = url;
5058+
shareCopyBtn.disabled = false;
5059+
}
5060+
}
50225061

5023-
const shareUrl = window.location.origin + window.location.pathname + '#share=' + encoded;
5024-
const tooLarge = shareUrl.length > MAX_SHARE_URL_LENGTH;
5062+
function openShareModal() {
5063+
// Reset to view-only by default each time
5064+
shareModeView.checked = true;
5065+
syncShareCardStyles();
5066+
updateShareUrlField();
5067+
shareModal.style.display = '';
5068+
requestAnimationFrame(() => {
5069+
shareModal.classList.add('is-visible');
5070+
shareModal.setAttribute('aria-hidden', 'false');
5071+
});
5072+
}
50255073

5026-
const originalHTML = btn.innerHTML;
5027-
const copiedHTML = '<i class="bi bi-check-lg"></i> Copied!';
5074+
function closeShareModal() {
5075+
shareModal.classList.remove('is-visible');
5076+
shareModal.setAttribute('aria-hidden', 'true');
5077+
shareModal.addEventListener('transitionend', function handler() {
5078+
shareModal.style.display = 'none';
5079+
shareModal.removeEventListener('transitionend', handler);
5080+
});
5081+
}
5082+
5083+
function syncShareCardStyles() {
5084+
if (shareModeView.checked) {
5085+
shareCardView.classList.add('is-selected');
5086+
shareCardEdit.classList.remove('is-selected');
5087+
} else {
5088+
shareCardEdit.classList.add('is-selected');
5089+
shareCardView.classList.remove('is-selected');
5090+
}
5091+
}
5092+
5093+
shareModeView.addEventListener('change', function () {
5094+
syncShareCardStyles();
5095+
updateShareUrlField();
5096+
});
5097+
shareModeEdit.addEventListener('change', function () {
5098+
syncShareCardStyles();
5099+
updateShareUrlField();
5100+
});
5101+
5102+
shareCopyBtn.addEventListener('click', function () {
5103+
const url = shareUrlInput.value;
5104+
if (!url || shareCopyBtn.disabled) return;
50285105

50295106
function onCopied() {
5030-
if (!tooLarge) {
5031-
window.location.hash = 'share=' + encoded;
5032-
}
5033-
btn.innerHTML = copiedHTML;
5034-
setTimeout(() => { btn.innerHTML = originalHTML; }, 2000);
5107+
const orig = shareCopyBtn.innerHTML;
5108+
shareCopyBtn.innerHTML = '<i class="bi bi-check-lg"></i>';
5109+
setTimeout(() => { shareCopyBtn.innerHTML = orig; }, 2000);
50355110
}
50365111

50375112
if (navigator.clipboard && window.isSecureContext) {
5038-
navigator.clipboard.writeText(shareUrl).then(onCopied).catch(() => {
5039-
// clipboard.writeText failed; nothing further to do in secure context
5040-
});
5113+
navigator.clipboard.writeText(url).then(onCopied).catch(() => {});
50415114
} else {
50425115
try {
5043-
const tempInput = document.createElement("textarea");
5044-
tempInput.value = shareUrl;
5045-
document.body.appendChild(tempInput);
5046-
tempInput.select();
5047-
document.execCommand("copy");
5048-
document.body.removeChild(tempInput);
5116+
const tmp = document.createElement('textarea');
5117+
tmp.value = url;
5118+
document.body.appendChild(tmp);
5119+
tmp.select();
5120+
document.execCommand('copy');
5121+
document.body.removeChild(tmp);
50495122
onCopied();
5050-
} catch (_) {
5051-
// copy failed silently
5052-
}
5123+
} catch (_) {}
50535124
}
5054-
}
5125+
});
5126+
5127+
shareModalCloseX.addEventListener('click', closeShareModal);
5128+
shareModalClose.addEventListener('click', closeShareModal);
5129+
shareModal.addEventListener('click', function (e) {
5130+
if (e.target === shareModal) closeShareModal();
5131+
});
5132+
document.addEventListener('keydown', function (e) {
5133+
if (e.key === 'Escape' && shareModal.classList.contains('is-visible')) closeShareModal();
5134+
});
50555135

5056-
shareButton.addEventListener("click", function () { copyShareUrl(shareButton); });
5057-
mobileShareButton.addEventListener("click", function () { copyShareUrl(mobileShareButton); });
5136+
shareButton.addEventListener('click', openShareModal);
5137+
mobileShareButton.addEventListener('click', openShareModal);
50585138

50595139
function loadFromShareHash() {
50605140
if (typeof pako === 'undefined') return;
50615141
const hash = window.location.hash;
50625142
if (!hash.startsWith('#share=')) return;
5063-
const encoded = hash.slice('#share='.length);
5143+
5144+
// Parse encoded content and optional &edit=1 flag.
5145+
// Hash format: #share=<encoded> or #share=<encoded>&edit=1
5146+
const rest = hash.slice('#share='.length);
5147+
const ampIdx = rest.indexOf('&');
5148+
const encoded = ampIdx === -1 ? rest : rest.slice(0, ampIdx);
5149+
const params = ampIdx === -1 ? '' : rest.slice(ampIdx + 1);
5150+
const isEdit = params.split('&').includes('edit=1');
5151+
50645152
if (!encoded) return;
50655153
try {
50665154
const decoded = decodeMarkdownFromShare(encoded);
50675155
markdownEditor.value = decoded;
50685156
renderMarkdown();
50695157
saveCurrentTabState();
5158+
// Apply the correct view mode: edit=1 → split, default → preview only
5159+
setViewMode(isEdit ? 'split' : 'preview');
50705160
} catch (e) {
50715161
console.error("Failed to load shared content:", e);
50725162
alert("The shared URL could not be decoded. It may be corrupted or incomplete.");
@@ -5495,4 +5585,4 @@ This is a fully client-side application. Your content never leaves your browser
54955585
container.appendChild(toolbar);
54965586
});
54975587
}
5498-
});
5588+
});

styles.css

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2686,3 +2686,125 @@ a:focus {
26862686
border-left: 0;
26872687
border-right: 0.25em solid currentColor;
26882688
}
2689+
2690+
/* ============================================
2691+
SHARE MODAL
2692+
============================================ */
2693+
2694+
.share-modal-description {
2695+
font-size: 13px;
2696+
color: var(--text-secondary, #57606a);
2697+
margin: 0;
2698+
}
2699+
2700+
.share-mode-cards {
2701+
display: flex;
2702+
flex-direction: column;
2703+
gap: 8px;
2704+
}
2705+
2706+
.share-mode-card {
2707+
display: flex;
2708+
align-items: center;
2709+
gap: 12px;
2710+
padding: 12px 14px;
2711+
border-radius: 8px;
2712+
border: 1px solid var(--border-color);
2713+
background: var(--bg-color);
2714+
cursor: pointer;
2715+
transition: border-color 0.15s ease, background-color 0.15s ease;
2716+
user-select: none;
2717+
}
2718+
2719+
.share-mode-card:hover {
2720+
border-color: var(--accent-color);
2721+
background: var(--button-hover);
2722+
}
2723+
2724+
.share-mode-card.is-selected {
2725+
border-color: var(--accent-color);
2726+
background: color-mix(in srgb, var(--accent-color) 8%, transparent);
2727+
}
2728+
2729+
.share-mode-card input[type="radio"] {
2730+
display: none;
2731+
}
2732+
2733+
.share-card-icon {
2734+
font-size: 18px;
2735+
width: 28px;
2736+
text-align: center;
2737+
color: var(--text-secondary, #57606a);
2738+
flex-shrink: 0;
2739+
}
2740+
2741+
.share-mode-card.is-selected .share-card-icon {
2742+
color: var(--accent-color);
2743+
}
2744+
2745+
.share-card-body {
2746+
display: flex;
2747+
flex-direction: column;
2748+
gap: 2px;
2749+
flex: 1;
2750+
min-width: 0;
2751+
}
2752+
2753+
.share-card-title {
2754+
font-size: 13px;
2755+
font-weight: 600;
2756+
color: var(--text-color);
2757+
}
2758+
2759+
.share-card-desc {
2760+
font-size: 12px;
2761+
color: var(--text-secondary, #57606a);
2762+
}
2763+
2764+
.share-card-check {
2765+
width: 18px;
2766+
text-align: center;
2767+
color: var(--accent-color);
2768+
opacity: 0;
2769+
transition: opacity 0.15s ease;
2770+
flex-shrink: 0;
2771+
}
2772+
2773+
.share-mode-card.is-selected .share-card-check {
2774+
opacity: 1;
2775+
}
2776+
2777+
.share-url-row {
2778+
display: flex;
2779+
gap: 8px;
2780+
align-items: center;
2781+
}
2782+
2783+
.share-url-input {
2784+
flex: 1;
2785+
font-size: 12px;
2786+
font-family: var(--font-mono, monospace);
2787+
color: var(--text-secondary, #57606a);
2788+
overflow: hidden;
2789+
text-overflow: ellipsis;
2790+
white-space: nowrap;
2791+
}
2792+
2793+
.share-copy-btn {
2794+
flex-shrink: 0;
2795+
padding: 6px 10px;
2796+
}
2797+
2798+
.share-copy-btn:disabled {
2799+
opacity: 0.5;
2800+
cursor: not-allowed;
2801+
}
2802+
2803+
.share-modal-notice {
2804+
font-size: 11px;
2805+
color: var(--text-secondary, #57606a);
2806+
margin: 0;
2807+
display: flex;
2808+
align-items: center;
2809+
gap: 5px;
2810+
}

0 commit comments

Comments
 (0)