Skip to content

Commit 4311e18

Browse files
committed
added fragment loader
1 parent 75e38f2 commit 4311e18

File tree

3 files changed

+133
-15
lines changed

3 files changed

+133
-15
lines changed

fragmentLoader.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Load and save code from/to the URL fragment (#code=...)
3+
*/
4+
5+
// Decode the code from the hash
6+
export function loadFromFragment() {
7+
const hash = window.location.hash.slice(1);
8+
const params = new URLSearchParams(hash);
9+
return params.get('code') || null;
10+
}
11+
12+
// Saves the code to the URL fragment
13+
export function saveToFragment(code) {
14+
const params = new URLSearchParams();
15+
params.set('code', code);
16+
window.location.hash = params.toString();
17+
}
18+
19+
export function clearFragment() {
20+
window.location.hash = '';
21+
}
22+
23+
export function createFragmentLink(code) {
24+
const params = new URLSearchParams();
25+
params.set('code', code);
26+
return window.location.origin + window.location.pathname + '#' + params.toString();
27+
}
28+
29+
export function onFragmentChange(callback) {
30+
// listener for fragment changes
31+
window.addEventListener('hashchange', () => {
32+
const code = loadFromFragment();
33+
if (code) callback(code);
34+
});
35+
}

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<button type="button" id="exampleInsert" name="insertExample">+</button>
5858
<button type="button" x-on:click="showReport = !showReport" x-text="showReport ? '>':'<'"
5959
x-bind:title="showReport?'hide report':'show report'"></button>
60+
<button type="button" id="shareLink" name="shareLink" title="Create shareable link">🔗</button>
6061
</fieldset>
6162
</span>
6263

main.js

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ import 'github-markdown-css/github-markdown.css'
2424
import { insertExampleFunc } from "./examples.js";
2525

2626
import Alpine from 'alpinejs'
27+
28+
// En tu archivo principal (e.g., app.js o index.js)
29+
import { clearFragment, loadFromFragment, createFragmentLink, onFragmentChange } from './fragmentLoader.js';
2730

2831
window.Alpine = Alpine
29-
32+
3033
Alpine.start()
3134

3235
const md = markdownit({ html: true })
@@ -41,6 +44,7 @@ const tabsField = document.getElementById("tabs")
4144
const insertButton = document.getElementById('exampleInsert')
4245
const exampleSelect = document.getElementById('exampleSelector')
4346
const outputs = document.getElementById("OUTPUT")
47+
const shareLinkButton = document.getElementById("shareLink")
4448
const numberOfSessions = 20
4549
const listOfSessions = Array.from({ length: numberOfSessions }, (_, i) => i + 1)
4650
let workerState = initialState
@@ -50,7 +54,52 @@ let lastTab = localStorage.getItem("lastTab") === null ? 1 : localStorage.getIte
5054

5155
let sessions = []
5256
let sessionNames = []
57+
let firstEmptySession = findEmptySession()
58+
// Phase 1: cleanup, dedupe and compact sessions in localStorage
59+
const seen = new Set();
60+
const nonEmptyUnique = [];
61+
62+
// Gather, remove empties and duplicates
63+
for (let ID of listOfSessions) {
64+
const key = getSessionName(ID);
65+
const text = localStorage.getItem(key);
66+
67+
if (text === null) continue;
68+
69+
if (text.trim() === "") {
70+
localStorage.removeItem(key);
71+
continue;
72+
}
73+
74+
if (seen.has(text)) {
75+
localStorage.removeItem(key);
76+
continue;
77+
}
78+
79+
seen.add(text);
80+
nonEmptyUnique.push({text, ID, isCurrent: ID == lastTab});
81+
}
82+
83+
// Reassign compacted non-empty sessions to lowest IDs, clear the rest
84+
for (let idx = 0; idx < listOfSessions.length; idx++) {
85+
const ID = listOfSessions[idx];
86+
const key = getSessionName(ID);
87+
if (idx < nonEmptyUnique.length) {
88+
if (nonEmptyUnique[idx].isCurrent){
89+
lastTab = idx + 1;
90+
}
91+
localStorage.setItem(key, nonEmptyUnique[idx].text);
92+
} else {
93+
localStorage.removeItem(key);
94+
}
95+
}
96+
97+
// First empty session is right after the last non-empty one (if any space left)
98+
firstEmptySession = nonEmptyUnique.length < listOfSessions.length
99+
? listOfSessions[nonEmptyUnique.length]
100+
: null;
53101

102+
// Phase 2: create HTML elements and initialize session arrays
54103
for (let ID of listOfSessions) {
55104
// Create tabs
56105
const radioInput = document.createElement('input');
@@ -67,16 +116,8 @@ for (let ID of listOfSessions) {
67116
label.textContent = ID;
68117
tabsField.appendChild(label);
69118

70-
// Create sessions
71-
const thisSession = getSessionName(ID);
72-
const sessionText = localStorage.getItem(thisSession)
73-
sessions[ID] = null
74-
75-
// Remove empty sessions
76-
if (sessionText && !sessionText.trim()) {
77-
localStorage.removeItem(thisSession)
78-
}
79-
119+
// Initialize session slots; names after label exists
120+
sessions[ID] = null;
80121
sessionNames[ID] = setSessionName(ID);
81122
}
82123

@@ -85,7 +126,16 @@ tabIDs.value = lastTab
85126

86127
let timer;
87128

129+
const codeFromUrl = loadFromFragment();
130+
if(codeFromUrl){
131+
lastTab = firstEmptySession !== null ? firstEmptySession : lastTab
132+
localStorage.setItem("lastTab", lastTab)
133+
localStorage.setItem(getSessionName(lastTab), codeFromUrl)
134+
tabIDs.value = lastTab
135+
clearFragment()
136+
}
88137
sessions[lastTab] = createState(lastTab)
138+
89139
let editor = new EditorView({
90140
state: sessions[lastTab],
91141
parent: document.querySelector('#INPUT')
@@ -104,6 +154,16 @@ tabsField.addEventListener('change', () => {
104154
lastTab = ID
105155
})
106156

157+
shareLinkButton.addEventListener('click', () => {
158+
const code = editor.state.doc.toString();
159+
const shareableLink = createFragmentLink(code);
160+
navigator.clipboard.writeText(shareableLink).then(() => {
161+
alert('Shareable link copied to clipboard!');
162+
}, (err) => {
163+
alert('Failed to copy link: ', err);
164+
});
165+
})
166+
107167
insertButton.addEventListener('click', () => {
108168
const exampleToInsert = insertExampleFunc(exampleSelect.value)
109169
editor.dispatch({
@@ -200,7 +260,7 @@ function createState(ID) {
200260
if (update.docChanged) {
201261
const text = update.state.doc.toString()
202262
clearTimeout(timer);
203-
timer = setTimeout(sendWorkToMathWorker, wait, text);
263+
timer = setTimeout(sendWorkToMathWorker, wait, text);
204264
} else if (update.selectionSet) {
205265
updateSelection()
206266
}
@@ -271,12 +331,34 @@ function saveSession(sessionID) {
271331
}
272332
}
273333

274-
function sendWorkToMathWorker(mathExpressoins) {
275-
if (mathExpressoins != "") {
276-
const expressions = mathExpressoins
334+
function sendWorkToMathWorker(mathExpressions) {
335+
if (mathExpressions != "") {
336+
const expressions = mathExpressions
277337
.replace(/\r?\n/g, '\n')
278338
//.trim()
279339
const request = { expr: expressions }
280340
mathWorker.postMessage(JSON.stringify(request))
281341
}
282342
}
343+
344+
345+
onFragmentChange((code) => {
346+
firstEmptySession = findEmptySession()
347+
lastTab = firstEmptySession !== null ? firstEmptySession : lastTab
348+
localStorage.setItem("lastTab", lastTab)
349+
localStorage.setItem(getSessionName(lastTab), code)
350+
tabIDs.value = lastTab
351+
editor.setState(createState(lastTab))
352+
clearFragment()
353+
});
354+
355+
function findEmptySession() {
356+
for (let ID of listOfSessions) {
357+
const key = getSessionName(ID);
358+
const text = localStorage.getItem(key);
359+
if (text === null || text.trim() === "") {
360+
return ID;
361+
}
362+
}
363+
return null;
364+
}

0 commit comments

Comments
 (0)