Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pages/jotpad.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ <h1>JotPad</h1>
</div>
</section>

<br/>
<div class="editor-wrapper">
<div class="editor-container">
<div class="mode-toggle">
Expand Down Expand Up @@ -70,9 +71,12 @@ <h1>JotPad</h1>
<button class="delete-btn" id="clearDrawingBtn" style="display: none">
Clear Drawing
</button>
<button class="icon-btn" id="highlightToggleBtn" title="Highlight"><i class="fa-solid fa-highlighter"></i></button>
<div id="highlightPalette" class="circular-palette" title="Select Highlight Color">
</div>
</div>
</div>
</div>

<!-- Footer -->
<div id="footer-placeholder"></div>
Expand All @@ -81,5 +85,6 @@ <h1>JotPad</h1>
<script src="../scripts/script.js"></script>
<script src="../scripts/jotpad.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
</body>
</html>
300 changes: 256 additions & 44 deletions scripts/jotpad.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,17 @@ document.addEventListener('DOMContentLoaded', function () {

const theme = document.documentElement.getAttribute('data-theme')

ctx.strokeStyle = theme === 'dark' ? '#ffffff' : '#000000'
// Use selectedColor from highlight palette if chosen, otherwise fallback
if (highlightEnabled) {
ctx.strokeStyle = selectedColor;
} else {
// Default based on theme
ctx.strokeStyle =
document.documentElement.getAttribute("data-theme") === "dark"
? "#ffffff"
: "#000000";
}

ctx.lineWidth = 2
ctx.lineJoin = 'round'
ctx.lineCap = 'round'
Expand Down Expand Up @@ -187,56 +197,258 @@ document.addEventListener('DOMContentLoaded', function () {
togglePlaceholder()
})

// Download PDF
async function downloadPDF() {
const { jsPDF } = window.jspdf
const content = document.getElementById('noteArea').innerHTML
const doc = new jsPDF()
doc.setFont('helvetica', 'normal')
doc.setFontSize(14)

const tempDiv = document.createElement('div')
tempDiv.innerHTML = content

let textContent = ''
const walker = document.createTreeWalker(
tempDiv,
NodeFilter.SHOW_TEXT,
null,
false
)
let node
while ((node = walker.nextNode())) {
textContent += node.nodeValue + '\n'
const highlightToggleBtn = document.getElementById('highlightToggleBtn');
const highlightPalette = document.getElementById('highlightPalette');

// Highlight Colors Circular Palette
const highlightColors = ['yellow', 'lightgreen', 'lightblue', 'pink', 'orange'];
let selectedColor = highlightColors[0];
let highlightEnabled = false;

// --- Helper Functions ---
function insertNodeAtRange(node, range) {
const container = range.startContainer;
if (container.nodeType === 3) {
container.parentNode.insertBefore(node, container.nextSibling);
} else {
range.insertNode(node);
}
}

const images = tempDiv.getElementsByTagName('img')
let yPosition = 20
const textLines = doc.splitTextToSize(textContent, 180)
doc.text(textLines, 10, yPosition)
yPosition += textLines.length * 7
function setCaretAfterNode(node) {
const selection = window.getSelection();
const range = document.createRange();
range.setStartAfter(node);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}

for (let i = 0; i < images.length; i++) {
if (yPosition > 250) {
doc.addPage()
yPosition = 20
}
function splitSpanAtCaret() {
const selection = window.getSelection();
if (!selection.rangeCount) return;

try {
const imgData = await getImageData(images[i].src)
const imgProps = doc.getImageProperties(imgData)
const width = 180
const height = (imgProps.height * width) / imgProps.width
doc.addImage(imgData, 'PNG', 10, yPosition, width, height)
yPosition += height + 10
} catch (error) {
console.error('Error adding image to PDF:', error)
}
const range = selection.getRangeAt(0);
const container = range.startContainer;
const offset = range.startOffset;

let parentSpan = container.nodeType === 3 ? container.parentNode : container.closest('span');
if (!parentSpan || parentSpan.tagName !== 'SPAN') return;

const text = container.textContent;

// Caret at start → move before span
if (offset === 0) {
const newRange = document.createRange();
newRange.setStartBefore(parentSpan);
newRange.collapse(true);
selection.removeAllRanges();
selection.addRange(newRange);
return;
}

// Caret at end → move after span
if (offset >= text.length) {
const newRange = document.createRange();
newRange.setStartAfter(parentSpan);
newRange.collapse(true);
selection.removeAllRanges();
selection.addRange(newRange);
return;
}

// Mid-span → split into two spans
const spanStyle = parentSpan.style.cssText;

const beforeSpan = document.createElement('span');
beforeSpan.style.cssText = spanStyle;
beforeSpan.textContent = text.slice(0, offset);

const afterSpan = document.createElement('span');
afterSpan.style.cssText = spanStyle;
afterSpan.textContent = text.slice(offset);

parentSpan.parentNode.insertBefore(beforeSpan, parentSpan);
parentSpan.parentNode.insertBefore(afterSpan, parentSpan.nextSibling);
parentSpan.remove();

setCaretAfterNode(beforeSpan.nextSibling); // move caret to after split
}

// --- Create color swatches ---
highlightColors.forEach((color, i) => {
const swatch = document.createElement('div');
swatch.className = 'color-swatch';
swatch.style.backgroundColor = color;
if (i === 0) swatch.classList.add('active-selected');
highlightPalette.appendChild(swatch);

swatch.addEventListener('click', () => {
selectedColor = color;
highlightPalette.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('active-selected'));
swatch.classList.add('active-selected');
});
});

// --- Toggle button click ---
highlightToggleBtn.addEventListener('click', (e) => {
e.stopPropagation();
highlightEnabled = !highlightEnabled;
highlightToggleBtn.classList.toggle('active', highlightEnabled);

const swatches = highlightPalette.querySelectorAll('.color-swatch');

if (highlightEnabled) {
highlightPalette.classList.add('active');
highlightPalette.style.display = 'flex';
const btnRect = highlightToggleBtn.getBoundingClientRect();
const centerX = btnRect.left + btnRect.width / 2 + window.scrollX;
const centerY = btnRect.top + btnRect.height / 2 + window.scrollY;
highlightPalette.style.left = `${centerX - 60}px`;
highlightPalette.style.top = `${centerY - 60}px`;

const radius = 50;
const arcStart = Math.PI;
const arcEnd = 2 * Math.PI;
const angleStep = (arcEnd - arcStart) / (swatches.length - 1);
swatches.forEach((swatch, i) => {
const angle = arcStart + i * angleStep;
const x = 60 + radius * Math.cos(angle) - 14;
const y = 60 + radius * Math.sin(angle) - 14;
swatch.style.transition = 'all 0.3s ease';
setTimeout(() => {
swatch.style.left = `${x}px`;
swatch.style.top = `${y}px`;
swatch.style.transform = 'scale(1)';
}, i * 50);
});
} else {
swatches.forEach((swatch, i) => {
swatch.style.transition = 'all 0.2s ease';
setTimeout(() => {
swatch.style.left = '60px';
swatch.style.top = '60px';
swatch.style.transform = 'scale(0)';
}, i * 30);
});

setTimeout(() => {
highlightPalette.style.display = 'none';
highlightPalette.classList.remove('active');
}, swatches.length * 30 + 150);
}
});

// --- Apply highlight to selection ---
function applyHighlight(color) {
const selection = window.getSelection();
if (selection.isCollapsed) return;

const range = selection.getRangeAt(0);
const span = document.createElement('span');
const isDarkMode = document.body.classList.contains('dark-mode');
span.style.color = isDarkMode ? 'white' : 'black';
span.style.backgroundColor = color;
span.textContent = range.toString();
range.deleteContents();
range.insertNode(span);

setCaretAfterNode(span);
saveNoteContent();
}

// --- Mouseup listener ---
noteArea.addEventListener('mouseup', () => {
if (!highlightEnabled) return;
const selection = window.getSelection();
if (!selection.isCollapsed) applyHighlight(selectedColor);
});

// --- Handle typing and input ---
noteArea.addEventListener('beforeinput', (e) => {
const selection = window.getSelection();
if (!selection.rangeCount) return;

// Remove placeholder if present
if (noteArea.classList.contains('empty')) {
noteArea.innerHTML = '';
noteArea.classList.remove('empty');
}

const range = selection.getRangeAt(0);

// Handle Enter
if (e.inputType === "insertParagraph") {
e.preventDefault();
const br = document.createElement('br');
insertNodeAtRange(br, range);
setCaretAfterNode(br);
saveNoteContent();
return;
}

// Only handle text insertion
if (e.inputType !== "insertText") return;
e.preventDefault();
const text = e.data;

splitSpanAtCaret();

const rangeAfterSplit = selection.getRangeAt(0);
if (highlightEnabled) {
const span = document.createElement('span');
span.style.backgroundColor = selectedColor;
const isDarkMode = document.body.classList.contains('dark-mode');
span.style.color = isDarkMode ? 'white' : 'black';
span.textContent = text;
insertNodeAtRange(span, rangeAfterSplit);
setCaretAfterNode(span);
} else {
const textNode = document.createTextNode(text);
insertNodeAtRange(textNode, rangeAfterSplit);
setCaretAfterNode(textNode);
}

doc.save('My_Notes.pdf')
saveNoteContent();
});

async function downloadPDF() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF("p", "mm", "a4");

const noteArea = document.getElementById("noteArea");
const drawingCanvas = document.getElementById("drawingCanvas");

const noteCanvas = await html2canvas(noteArea, {
backgroundColor: "#ffffff",
scale: 2
});

const combinedCanvas = document.createElement("canvas");
combinedCanvas.width = noteCanvas.width;
combinedCanvas.height = noteCanvas.height;
const ctx = combinedCanvas.getContext("2d");

ctx.drawImage(noteCanvas, 0, 0);

ctx.drawImage(
drawingCanvas,
0,
0,
combinedCanvas.width,
combinedCanvas.height
);

const imgData = combinedCanvas.toDataURL("image/png");
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;

doc.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight);
doc.save("My_Notes.pdf");
}


function getImageData(url) {
return new Promise((resolve, reject) => {
const img = new Image()
Expand All @@ -261,4 +473,4 @@ function deleteAll() {

noteArea.classList.add('empty')
}
}
}
Loading