-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
394 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Jekyll Post Formatter</title> | ||
<link rel="stylesheet" href="../src/style.css" /> | ||
</head> | ||
<body> | ||
<div class="container"> | ||
<h1>Jekyll Post Formatter</h1> | ||
<details id="header-config"> | ||
<summary>Post Headers</summary> | ||
<table id="header-table"> | ||
<thead> | ||
<tr> | ||
<th>Key</th> | ||
<th>Value</th> | ||
<th>Actions</th> | ||
</tr> | ||
</thead> | ||
<tbody></tbody> | ||
</table> | ||
<button id="add-text">Add Text</button> | ||
<button id="add-date">Add Date</button> | ||
<button id="add-color">Add Color</button> | ||
<button id="save-header">Save Header</button> | ||
<button id="reset-header">Reset to default</button> | ||
</details> | ||
|
||
<div> | ||
<h2>Post Content</h2> | ||
<textarea | ||
id="content-input" | ||
placeholder="Paste your content here..." | ||
rows="15" | ||
></textarea> | ||
</div> | ||
|
||
<div> | ||
<h2>Formatted Output</h2> | ||
<textarea id="markdown-output" rows="15" readonly></textarea> | ||
<label for="filename-input">Filename:</label> | ||
<input type="text" id="filename-input" value="default-filename.md"> | ||
<button id="copy-button">Copy to Clipboard</button> | ||
<button id="download-button">Download Markdown File</button> | ||
</div> | ||
|
||
<div> | ||
<h2>Preview</h2> | ||
<div id="markdown-preview"> | ||
<code id="preview-header"></code> | ||
<div id="preview-content"></div> | ||
</div> | ||
</div> | ||
</div> | ||
<script src="https://unpkg.com/turndown/dist/turndown.js"></script> | ||
<script src="https://unpkg.com/turndown-plugin-gfm/dist/turndown-plugin-gfm.js"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js"></script> | ||
<script src="../src/script.js"></script> | ||
<script src="../tools/scripts/jekyll-post-formatter.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,333 @@ | ||
const headerTable = document.getElementById('header-table').getElementsByTagName('tbody')[0]; | ||
const contentInput = document.getElementById('content-input'); | ||
const markdownOutput = document.getElementById('markdown-output'); | ||
const copyButton = document.getElementById('copy-button'); | ||
const saveHeaderButton = document.getElementById('save-header'); | ||
const resetHeaderButton = document.getElementById('reset-header'); | ||
const addTextButton = document.getElementById('add-text'); | ||
const addDateButton = document.getElementById('add-date'); | ||
const addColorButton = document.getElementById('add-color'); | ||
const downloadButton = document.getElementById('download-button'); | ||
const previewHeader = document.getElementById('preview-header'); | ||
const previewContent = document.getElementById('preview-content'); | ||
const filenameInput = document.getElementById('filename-input'); | ||
|
||
const defaultHeaders = [ | ||
{ key: 'layout', value: 'post', type: 'text' }, | ||
{ key: 'date', value: '', type: 'date' }, | ||
{ key: 'title', value: '', type: 'text' }, | ||
{ key: 'author', value: '', type: 'text' }, | ||
{ key: 'categories', value: '[]', type: 'text' }, | ||
{ key: 'tags', value: '[]', type: 'text' }, | ||
{ key: 'image', value: '', type: 'text' }, | ||
{ key: 'description', value: '', type: 'text' }, | ||
{ key: 'video_embed', value: '', type: 'text' }, | ||
{ key: 'tags_color', value: '', type: 'color' }, | ||
{ key: 'featured', value: 'false', type: 'text' }, | ||
{ key: 'topper', value: 'false', type: 'text' }, | ||
{ key: 'hidden', value: 'false', type: 'text' }, | ||
{ key: 'toc', value: 'false', type: 'text' } | ||
]; | ||
|
||
function populateHeaderTable(headers) { | ||
headerTable.innerHTML = ''; | ||
for (const header of headers) { | ||
if (header.key === 'date' && !header.value) { | ||
header.value = getCurrentDateTime(); | ||
} | ||
addHeaderRow(header.key, header.value, header.type || 'text'); // Use saved type or default to 'text' | ||
} | ||
} | ||
|
||
function addHeaderRow(key = '', value = '', type = 'text') { | ||
const row = headerTable.insertRow(); | ||
const keyCell = row.insertCell(); | ||
const valueCell = row.insertCell(); | ||
const actionsCell = row.insertCell(); | ||
|
||
keyCell.innerHTML = `<input type="text" value="${key}" placeholder="Key">`; | ||
|
||
// Create input field based on type | ||
if (type === 'date') { | ||
valueCell.innerHTML = `<input type="datetime-local" value="${value}">`; | ||
} else if (type === 'color') { | ||
valueCell.innerHTML = `<input type="color" value="${value}"> | ||
<input type="text" placeholder="Hex Code" value="${value}" style="width: 80px;">`; | ||
const colorPicker = valueCell.querySelector('input[type="color"]'); | ||
const hexInput = valueCell.querySelector('input[type="text"]'); | ||
colorPicker.addEventListener('input', () => { | ||
hexInput.value = colorPicker.value; | ||
}); | ||
hexInput.addEventListener('input', () => { | ||
if (/^#[0-9A-Fa-f]{6}$/.test(hexInput.value)) { | ||
colorPicker.value = hexInput.value; | ||
} | ||
}); | ||
} else { | ||
valueCell.innerHTML = `<input type="text" value="${value}" placeholder="Value">`; | ||
} | ||
|
||
actionsCell.innerHTML = `<button class="delete-row">Delete</button>`; | ||
actionsCell.querySelector('.delete-row').addEventListener('click', () => { | ||
headerTable.deleteRow(row.rowIndex - 1); | ||
}); | ||
} | ||
|
||
function addDateRow() { | ||
const row = headerTable.insertRow(); | ||
const keyCell = row.insertCell(); | ||
const valueCell = row.insertCell(); | ||
const actionsCell = row.insertCell(); | ||
keyCell.innerHTML = `<input type="text" value="date" placeholder="Key" readonly>`; | ||
valueCell.innerHTML = `<input type="datetime-local" value="${getCurrentDateTime()}">`; | ||
actionsCell.innerHTML = `<button class="delete-row">Delete</button>`; | ||
actionsCell.querySelector('.delete-row').addEventListener('click', () => { | ||
headerTable.deleteRow(row.rowIndex - 1); | ||
}); | ||
} | ||
|
||
function addColorRow() { | ||
const row = headerTable.insertRow(); | ||
const keyCell = row.insertCell(); | ||
const valueCell = row.insertCell(); | ||
const actionsCell = row.insertCell(); | ||
keyCell.innerHTML = `<input type="text" value="color" placeholder="Key">`; | ||
valueCell.innerHTML = `<input type="color" value="#ffffff"> | ||
<input type="text" placeholder="Hex Code" value="#ffffff" style="width: 80px;">`; | ||
actionsCell.innerHTML = `<button class="delete-row">Delete</button>`; | ||
const colorPicker = valueCell.querySelector('input[type="color"]'); | ||
const hexInput = valueCell.querySelector('input[type="text"]'); | ||
colorPicker.addEventListener('input', () => { | ||
hexInput.value = colorPicker.value; | ||
}); | ||
hexInput.addEventListener('input', () => { | ||
if (/^#[0-9A-Fa-f]{6}$/.test(hexInput.value)) { | ||
colorPicker.value = hexInput.value; | ||
} | ||
}); | ||
actionsCell.querySelector('.delete-row').addEventListener('click', () => { | ||
headerTable.deleteRow(row.rowIndex - 1); | ||
}); | ||
} | ||
|
||
function getTableHeaderData() { | ||
const rows = headerTable.rows; | ||
const headers = []; | ||
for (let i = 0; i < rows.length; i++) { | ||
const keyInput = rows[i].cells[0].querySelector('input'); | ||
const valueInput = rows[i].cells[1].querySelector('input:not([type="color"])'); | ||
if (keyInput && valueInput) { | ||
if (keyInput.value) { | ||
headers.push({ key: keyInput.value, value: valueInput.value }); | ||
} | ||
} | ||
} | ||
return headers; | ||
} | ||
|
||
function getCurrentDateTime() { | ||
const now = new Date(); | ||
const year = now.getFullYear(); | ||
const month = (now.getMonth() + 1).toString().padStart(2, '0'); | ||
const day = now.getDate().toString().padStart(2, '0'); | ||
const hours = now.getHours().toString().padStart(2, '0'); | ||
const minutes = now.getMinutes().toString().padStart(2, '0'); | ||
return `${year}-${month}-${day}T${hours}:${minutes}`; | ||
} | ||
|
||
function convertToMarkdownFormat(html) { | ||
const turndownService = new TurndownService({ | ||
headingStyle: 'atx', | ||
codeBlockStyle: 'fenced' | ||
}); | ||
turndownService.use(turndownPluginGfm.gfm); | ||
|
||
// --- Custom Rules --- | ||
// Remove surrounding asterisks added by turndown | ||
turndownService.addRule('removeDoubleAsterisks', { | ||
filter: function (node, options) { | ||
return ( | ||
node.nodeName === 'STRONG' || node.nodeName === 'EM' | ||
); | ||
}, | ||
replacement: function (content) { | ||
return content; | ||
} | ||
}); | ||
// --- End Custom Rules --- | ||
|
||
const markdown = turndownService.turndown(html); | ||
return markdown; | ||
} | ||
|
||
function updateOutputAndPreview() { | ||
const descriptionRow = Array.from(headerTable.rows).find(row => row.cells[0].querySelector('input').value === 'description'); | ||
let description = descriptionRow ? descriptionRow.cells[1].querySelector('input:not([type="color"])').value : ''; | ||
if (!description) { | ||
const plainTextContent = contentInput.value; | ||
description = plainTextContent.slice(0, 150).trim() + '...'; | ||
if (descriptionRow) { | ||
descriptionRow.cells[1].querySelector('input').value = description; | ||
} | ||
} | ||
|
||
const header = generateHeader(); | ||
const content = contentInput.value; | ||
const markdownContent = content; | ||
|
||
markdownOutput.value = `${header}\n\n${markdownContent}`; | ||
updatePreview(markdownContent); | ||
} | ||
|
||
function generateHeader() { | ||
const headers = getTableHeaderData(); | ||
let header = '---\n'; | ||
for (const h of headers) { | ||
if (h.key === 'date' && h.value) { | ||
// Convert to ISO 8601 format with timezone offset | ||
const date = new Date(h.value); | ||
const timezoneOffset = date.getTimezoneOffset() * 60000; // Get offset in milliseconds | ||
const localISOTime = (new Date(date - timezoneOffset)).toISOString().slice(0, 19).replace('T', ' '); | ||
h.value = `${localISOTime} +0300`; | ||
} | ||
header += `${h.key}: ${h.value}\n`; | ||
} | ||
header += '---\n'; | ||
return header; | ||
} | ||
|
||
async function copyToClipboard() { | ||
const markdownContent = markdownOutput.value; | ||
if (navigator.clipboard) { | ||
try { | ||
await navigator.clipboard.writeText(markdownContent); | ||
alert('Markdown copied to clipboard!'); | ||
} catch (err) { | ||
console.error('Failed to copy to clipboard:', err); | ||
alert('Failed to copy to clipboard. Please copy manually.'); | ||
} | ||
} else { | ||
markdownOutput.select(); | ||
document.execCommand('copy'); | ||
alert('Markdown copied to clipboard!'); | ||
} | ||
} | ||
|
||
function saveHeaderToLocalStorage() { | ||
const rows = headerTable.rows; | ||
const headers = []; | ||
for (let i = 0; i < rows.length; i++) { | ||
const keyInput = rows[i].cells[0].querySelector('input'); | ||
const valueInput = rows[i].cells[1].querySelector('input:not([type="color"])'); | ||
const type = rows[i].cells[1].querySelector('input').type; // Get the input type | ||
if (keyInput && valueInput) { | ||
if (keyInput.value) { | ||
headers.push({ key: keyInput.value, value: valueInput.value, type: type }); // Save the type | ||
} | ||
} | ||
} | ||
localStorage.setItem(`headers`, JSON.stringify(headers)); | ||
alert('Header saved!'); | ||
} | ||
|
||
function resetHeaderToDefault() { | ||
populateHeaderTable(defaultHeaders); | ||
} | ||
|
||
function updateFilename() { | ||
const title = getTableHeaderData().find(h => h.key === 'title')?.value || ''; | ||
const formattedTitle = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, ''); | ||
const currentDate = new Date().toISOString().slice(0, 10); | ||
const defaultFilename = `${currentDate}-${formattedTitle}.md`; | ||
filenameInput.value = defaultFilename; | ||
} | ||
|
||
function downloadMarkdownFile() { | ||
const markdownContent = markdownOutput.value; | ||
if (!markdownContent) { | ||
alert("Please generate the Markdown first."); | ||
return; | ||
} | ||
const fileName = filenameInput.value; | ||
const blob = new Blob([markdownContent], { type: 'text/markdown' }); | ||
const link = document.createElement('a'); | ||
link.href = URL.createObjectURL(blob); | ||
link.download = fileName; | ||
link.click(); | ||
URL.revokeObjectURL(link.href); | ||
} | ||
|
||
// Function to update the Markdown preview | ||
function updatePreview(content) { | ||
const converter = new showdown.Converter(); | ||
|
||
// Format headers for code block preview | ||
const headers = getTableHeaderData(); | ||
let headerBlock = ''; | ||
if (headers.length > 0) { | ||
headerBlock = "```yaml\n"; // Start YAML code block | ||
for (const h of headers) { | ||
headerBlock += `${h.key}: ${h.value}\n`; | ||
} | ||
headerBlock += "```\n\n"; // End YAML code block | ||
} | ||
previewHeader.innerHTML = converter.makeHtml(headerBlock); | ||
previewContent.innerHTML = converter.makeHtml(content); | ||
} | ||
|
||
// Event Listeners | ||
contentInput.addEventListener('input', () => { | ||
updateOutputAndPreview(); | ||
updateFilename(); | ||
}); | ||
headerTable.addEventListener('input', () => { | ||
updateOutputAndPreview(); | ||
updateFilename(); | ||
}); | ||
filenameInput.addEventListener('input', updateFilename); | ||
copyButton.addEventListener('click', copyToClipboard); | ||
saveHeaderButton.addEventListener('click', saveHeaderToLocalStorage); | ||
resetHeaderButton.addEventListener('click', resetHeaderToDefault); | ||
downloadButton.addEventListener('click', downloadMarkdownFile); | ||
addTextButton.addEventListener('click', () => addHeaderRow()); | ||
addDateButton.addEventListener('click', () => addDateRow()); | ||
addColorButton.addEventListener('click', () => addColorRow()); | ||
|
||
// Paste event listener for contentInput | ||
contentInput.addEventListener('paste', (event) => { | ||
event.preventDefault(); | ||
const clipboardData = event.clipboardData; | ||
if (clipboardData && clipboardData.types.includes('text/html')) { | ||
const pastedData = clipboardData.getData('text/html'); | ||
const markdown = convertToMarkdownFormat(pastedData); | ||
const start = contentInput.selectionStart; | ||
const end = contentInput.selectionEnd; | ||
const text = contentInput.value; | ||
contentInput.value = text.substring(0, start) + markdown + text.substring(end); | ||
} else if (clipboardData) { | ||
const pastedData = clipboardData.getData('text/plain'); | ||
insertAtCursor(contentInput, pastedData); | ||
} | ||
updateOutputAndPreview(); | ||
updateFilename(); | ||
}); | ||
|
||
// Helper function to insert text at cursor position | ||
function insertAtCursor(inputField, text) { | ||
const start = inputField.selectionStart; | ||
const end = inputField.selectionEnd; | ||
const originalText = inputField.value; | ||
inputField.value = originalText.substring(0, start) + text + originalText.substring(end); | ||
inputField.selectionStart = inputField.selectionEnd = start + text.length; | ||
inputField.focus(); | ||
} | ||
|
||
// Initial Setup | ||
const savedHeaders = localStorage.getItem('headers'); | ||
if (savedHeaders) { | ||
populateHeaderTable(JSON.parse(savedHeaders)); | ||
} else { | ||
populateHeaderTable(defaultHeaders); | ||
} | ||
updateOutputAndPreview(); | ||
updateFilename(); |