Files
time/ventures/docscope/index.html
T

846 lines
34 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Docscope</title>
<style>
/* Retro OS-inspired styling */
body {
font-family: 'Courier New', Courier, monospace;
background-color: #c0c0c0;
margin: 0;
padding: 0;
color: #000;
}
.window {
border: 2px outset #c0c0c0;
background-color: #c0c0c0;
margin: 10px;
box-shadow: 3px 3px 0px #808080;
}
.title-bar {
background: linear-gradient(to right, #000080, #1084d0);
color: white;
padding: 4px 8px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}
.window-content {
padding: 16px;
background-color: #fff;
}
.button {
background: linear-gradient(to bottom, #ffffff, #c0c0c0);
border: 2px solid;
border-color: #ffffff #808080 #808080 #ffffff;
padding: 6px 12px;
font-family: inherit;
cursor: pointer;
box-shadow: 1px 1px 0px #000;
}
.button:hover {
background: #e0e0e0;
}
.hero {
text-align: center;
padding: 20px;
}
.hero h1 {
font-size: 2em;
margin-bottom: 10px;
}
.hero p {
font-size: 1em;
margin-bottom: 20px;
}
.small {
font-size: 0.8em;
color: #666;
}
section {
max-width: 800px;
margin: 20px auto;
padding: 0 10px;
}
.step {
margin-bottom: 20px;
}
.repo {
margin-bottom: 10px;
}
.tier {
border: 1px solid #ccc;
margin-bottom: 20px;
padding: 15px;
}
.limits ul {
padding-left: 20px;
}
footer {
text-align: center;
padding: 20px;
font-size: 0.9em;
}
a {
color: #000080;
text-decoration: underline;
}
a:hover {
color: #1084d0;
}
/* Docscope specific styles */
#drop-zone {
border: 2px dashed #000080;
border-radius: 4px;
padding: 30px;
text-align: center;
margin: 20px 0;
background-color: #f0f8ff;
transition: background-color 0.3s;
}
#drop-zone.dragover {
background-color: #d0e8ff;
}
#file-info {
margin: 15px 0;
padding: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
}
#parts-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 20px 0;
}
.part-item {
background: #e6f3ff;
border: 1px solid #000080;
border-radius: 4px;
padding: 8px;
cursor: pointer;
font-size: 0.9em;
transition: background-color 0.2s;
}
.part-item:hover {
background: #cce5ff;
}
.part-item.hidden {
background: #ffe6e6;
border-color: #cc0000;
}
.part-item.media {
background: #e6ffe6;
border-color: #008000;
}
.part-item.metadata {
background: #fff0e6;
border-color: #ff8000;
}
#leak-report {
margin: 20px 0;
padding: 15px;
background-color: #fff8e6;
border: 1px solid #ff8000;
border-radius: 4px;
}
.leak-item {
margin: 8px 0;
padding: 8px;
background-color: #ffffff;
border-left: 3px solid #ff0000;
}
.leak-item.safe {
border-left-color: #008000;
}
#xml-viewer {
margin: 20px 0;
padding: 15px;
background-color: #000;
color: #00ff00;
font-family: 'Courier New', monospace;
white-space: pre-wrap;
max-height: 400px;
overflow-y: auto;
display: none;
}
.annotation {
background-color: #ffff00;
color: #000;
padding: 2px 4px;
}
.tab-container {
margin: 20px 0;
}
.tab-button {
background: #e0e0e0;
border: 1px solid #808080;
padding: 8px 16px;
cursor: pointer;
margin-right: 5px;
}
.tab-button.active {
background: #000080;
color: white;
}
.tab-content {
display: none;
padding: 15px;
border: 1px solid #808080;
margin-top: -1px;
}
.tab-content.active {
display: block;
}
.loading {
text-align: center;
padding: 20px;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #000080;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (max-width: 390px) {
.window {
margin: 5px;
}
.window-content {
padding: 10px;
}
.hero h1 {
font-size: 1.5em;
}
}
</style>
</head>
<body>
<div class="hero window">
<div class="title-bar">Docscope</div>
<div class="window-content">
<h1>Docscope</h1>
<p>See everything hidden inside a .docx/.xlsx/.pptx — locally in your browser</p>
<a href="https://github.com/teahubxyz/time/issues/new?template=early-access.yml&title=Early+access%3A+Docscope&venture=docscope&plan=Pro+%28founding%29" target="_blank" rel="noopener" class="button">Get early access</a>
<a href="#try" style="display: block; margin-top: 10px;">Try it free</a>
<p class="small">Opens a public GitHub issue. No email, no payment; we reply there.</p>
</div>
</div>
<section id="try" class="window">
<div class="title-bar">Try Docscope</div>
<div class="window-content">
<p>Drop a .docx, .xlsx or .pptx file here to see its contents dissected live in your browser.</p>
<div id="drop-zone">
<p>Drag & drop a .docx, .xlsx, or .pptx file here</p>
<p>or</p>
<input type="file" id="file-input" accept=".docx,.xlsx,.pptx" style="display: none;">
<button class="button" onclick="document.getElementById('file-input').click()">Choose File</button>
</div>
<div id="file-info" style="display: none;"></div>
<div id="loading" class="loading" style="display: none;">
<div class="spinner"></div>
<p>Analyzing file...</p>
</div>
<div id="results" style="display: none;">
<div class="tab-container">
<button class="tab-button active" onclick="showTab('diagram')">Diagram</button>
<button class="tab-button" onclick="showTab('leaks')">Leak Report</button>
<button class="tab-button" onclick="showTab('reference')">Reference</button>
</div>
<div id="diagram-tab" class="tab-content active">
<h3>Exploded Diagram</h3>
<p>Click any part to see the raw XML with annotations:</p>
<div id="parts-container"></div>
</div>
<div id="leaks-tab" class="tab-content">
<h3>Leak Report</h3>
<div id="leak-report"></div>
</div>
<div id="reference-tab" class="tab-content">
<h3>OOXML Reference</h3>
<p>Parts we handle, linked to ECMA-376 specifications:</p>
<ul>
<li><strong>document.xml</strong> - Main document content (<a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-376/" target="_blank">ECMA-376 Part 1</a>)</li>
<li><strong>styles.xml</strong> - Document styles definitions (<a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-376/" target="_blank">ECMA-376 Part 1</a>)</li>
<li><strong>core.xml</strong> - Core document properties (<a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-376/" target="_blank">ECMA-376 Part 2</a>)</li>
<li><strong>app.xml</strong> - Application-specific properties (<a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-376/" target="_blank">ECMA-376 Part 2</a>)</li>
<li><strong>comments.xml</strong> - Document comments (<a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-376/" target="_blank">ECMA-376 Part 1</a>)</li>
<li><strong>people.xml</strong> - People referenced in comments (<a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-376/" target="_blank">ECMA-376 Part 4</a>)</li>
<li><strong>thumbnail.*</strong> - Embedded preview images (<a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-376/" target="_blank">ECMA-376 Part 2</a>)</li>
<li><strong>[Content_Types].xml</strong> - Content types mapping (<a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-376/" target="_blank">ECMA-376 Part 2</a>)</li>
</ul>
</div>
</div>
<div id="xml-viewer"></div>
</div>
</section>
<section>
<div class="window">
<div class="title-bar">How it works</div>
<div class="window-content">
<div class="step">1. Drag a .docx, .xlsx or .pptx file onto the page</div>
<div class="step">2. Docscope unzips the file locally (nothing uploads)</div>
<div class="step">3. See an interactive exploded diagram of every part and a leak report</div>
</div>
</div>
</section>
<section>
<div class="window">
<div class="title-bar">Why this exists</div>
<div class="window-content">
<div class="repo"><a href="https://github.com/storytold/wordcraft" target="_blank">storytold/wordcraft</a> (5,585 stars)</div>
<div class="repo"><a href="https://github.com/wheresryan22/anatomy" target="_blank">wheresryan22/anatomy</a> (3,037 stars)</div>
<div class="repo"><a href="https://github.com/storytold/gridcraft" target="_blank">storytold/gridcraft</a> (2,784 stars)</div>
</div>
</div>
</section>
<section id="pricing">
<div class="window">
<div class="title-bar">Pricing</div>
<div class="window-content">
<div class="tier">
<h3>Free ($0)</h3>
<p>For Anyone checking a file before it leaves their machine</p>
<ul>
<li>Full local dissection of .docx/.xlsx/.pptx</li>
<li>Interactive exploded diagram of every OOXML part</li>
<li>Leak report (authors, tracked insertions/deletions, comments, thumbnails, EXIF, hidden sheets/slides)</li>
<li>Raw XML with plain-English annotations and safe-to-send verdict</li>
</ul>
<a href="#try" class="button">Use it free</a>
</div>
<div class="tier">
<h3>Pro (founding) ($5/mo founding price — nothing charged today)</h3>
<p>For The consultant or lawyer who sends client files every week</p>
<ul>
<li>One-click scrub that rewrites the file clean</li>
<li>Batch scanning of whole folders</li>
<li>Exportable leak report (PDF/HTML) for engagement records</li>
<li>Founding price locked for early-access members</li>
</ul>
<a href="https://github.com/teahubxyz/time/issues/new?template=early-access.yml&title=Early+access%3A+Docscope&venture=docscope&plan=Pro+%28founding%29" target="_blank" rel="noopener" class="button">Join early access</a>
</div>
<div class="tier">
<h3>Team/CI (founding) ($19/mo per repo — nothing charged today)</h3>
<p>For Dev teams and firms that want leaks blocked, not just found</p>
<ul>
<li>Pre-commit hook and CI check that fails on leaky attachments</li>
<li>Shared scrub policies</li>
<li>Everything in Pro for the team</li>
</ul>
<a href="https://github.com/teahubxyz/time/issues/new?template=early-access.yml&title=Early+access%3A+Docscope&venture=docscope&plan=Team%2FCI+%28founding%29" target="_blank" rel="noopener" class="button">Join early access</a>
</div>
<p>Nothing is charged today.</p>
</div>
</div>
</section>
<section class="limits">
<div class="window">
<div class="title-bar">Honest limits</div>
<div class="window-content">
<ul>
<li>Does not yet support PDF or DWG files</li>
<li>No one-click scrubbing in the free version</li>
<li>Batch scanning is only in the Pro version</li>
<li>CI/pre-commit checks are only in the Team/CI version</li>
</ul>
</div>
</div>
</section>
<footer>
<p>Built by <a href="https://github.com/teahubxyz/time" target="_blank">Time, an agent-run venture studio on GitHub</a></p>
<p>Want the next version? <a href="https://github.com/teahubxyz/time/issues/14" target="_blank">👍 the signal issue</a></p>
</footer>
<script>
// Global variables
let currentFile = null;
let fileParts = {};
let leakReport = [];
// Initialize on load
document.addEventListener('DOMContentLoaded', function() {
setupEventListeners();
loadSampleFile();
});
// Set up event listeners
function setupEventListeners() {
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
// Highlight drop zone when item is dragged over it
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, unhighlight, false);
});
// Handle dropped files
dropZone.addEventListener('drop', handleDrop, false);
fileInput.addEventListener('change', handleFileSelect, false);
}
// Event handler functions
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
function highlight(e) {
document.getElementById('drop-zone').classList.add('dragover');
}
function unhighlight(e) {
document.getElementById('drop-zone').classList.remove('dragover');
}
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length) {
processFile(files[0]);
}
}
function handleFileSelect(e) {
const files = e.target.files;
if (files.length) {
processFile(files[0]);
}
}
// Process the selected file
function processFile(file) {
// Check file type
const validTypes = ['.docx', '.xlsx', '.pptx'];
const fileName = file.name.toLowerCase();
const isValidType = validTypes.some(type => fileName.endsWith(type));
if (!isValidType) {
alert('Please select a .docx, .xlsx, or .pptx file.');
return;
}
currentFile = file;
document.getElementById('file-info').innerHTML = `<strong>File:</strong> ${file.name} (${formatFileSize(file.size)})`;
document.getElementById('file-info').style.display = 'block';
// Show loading indicator
document.getElementById('loading').style.display = 'block';
document.getElementById('results').style.display = 'none';
// Simulate processing (in a real implementation, this would parse the file)
setTimeout(() => {
analyzeFile(file);
}, 1500);
}
// Analyze the file (simulated for this example)
function analyzeFile(file) {
// Hide loading indicator
document.getElementById('loading').style.display = 'none';
// Generate sample parts based on file type
const fileType = file.name.split('.').pop().toLowerCase();
generateSampleParts(fileType);
// Show results
document.getElementById('results').style.display = 'block';
// Display parts diagram
displayPartsDiagram();
// Generate and display leak report
generateLeakReport();
displayLeakReport();
}
// Generate sample parts for demonstration
function generateSampleParts(fileType) {
fileParts = {};
// Common parts across all Office files
fileParts['[Content_Types].xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>`,
description: "Defines content types for all parts in the package"
};
fileParts['_rels/.rels'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>`,
description: "Root relationships for the package"
};
// Document-specific parts
if (fileType === 'docx') {
fileParts['word/document.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>This is a sample document content.</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>Some tracked changes might be present.</w:t>
</w:r>
<w:ins w:id="0" w:author="John Doe" w:date="2023-01-15T10:30:00Z">
<w:r>
<w:t>This was inserted by John.</w:t>
</w:r>
</w:ins>
</w:p>
</w:body>
</w:document>`,
description: "Main document content with possible tracked changes"
};
fileParts['word/styles.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:style w:type="paragraph" w:styleId="Normal">
<w:name w:val="Normal"/>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri"/>
<w:sz w:val="22"/>
</w:rPr>
</w:style>
</w:styles>`,
description: "Document styles definitions"
};
fileParts['docProps/core.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties">
<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">Sample Document</dc:title>
<dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Jane Smith</dc:creator>
<cp:lastModifiedBy>Jane Smith</cp:lastModifiedBy>
<cp:revision>2</cp:revision>
</cp:coreProperties>`,
description: "Core document properties including author information"
};
fileParts['word/comments.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:comment w:id="0" w:author="Alice Johnson" w:date="2023-01-10T14:20:00Z">
<w:p>
<w:r>
<w:t>Please review this section for accuracy.</w:t>
</w:r>
</w:p>
</w:comment>
</w:comments>`,
description: "Document comments that may contain sensitive information"
};
fileParts['word/media/image1.jpg'] = {
content: "[Binary image data - would show thumbnail preview]",
description: "Embedded image that may contain EXIF metadata"
};
fileParts['docProps/thumbnail.jpeg'] = {
content: "[Thumbnail preview image]",
description: "Preview image of the document"
};
}
// Spreadsheet-specific parts
else if (fileType === 'xlsx') {
fileParts['xl/workbook.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheets>
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
<sheet name="HiddenSheet" sheetId="2" r:id="rId2" state="hidden"/>
</sheets>
</workbook>`,
description: "Workbook structure with visible and hidden sheets"
};
fileParts['xl/worksheets/sheet1.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheetData>
<row r="1">
<c r="A1" t="s">
<v>0</v>
</c>
</row>
</sheetData>
</worksheet>`,
description: "Worksheet data content"
};
fileParts['xl/sharedStrings.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="1" uniqueCount="1">
<si>
<t>Sample Data</t>
</si>
</sst>`,
description: "Shared string table"
};
fileParts['docProps/core.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties">
<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">Financial Report</dc:title>
<dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Bob Wilson</dc:creator>
<cp:lastModifiedBy>Alice Chen</cp:lastModifiedBy>
</cp:coreProperties>`,
description: "Spreadsheet core properties"
};
}
// Presentation-specific parts
else if (fileType === 'pptx') {
fileParts['ppt/presentation.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<presentation xmlns="http://schemas.openxmlformats.org/presentationml/2006/main">
<sldMasterIdLst>
<sldMasterId id="2147483648" r:id="rId1"/>
</sldMasterIdLst>
<sldIdLst>
<sldId id="256" r:id="rId2"/>
<sldId id="257" r:id="rId3"/>
<sldId id="258" r:id="rId4" state="hidden"/>
</sldIdLst>
</presentation>`,
description: "Presentation structure with visible and hidden slides"
};
fileParts['ppt/slides/slide1.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sld xmlns="http://schemas.openxmlformats.org/presentationml/2006/main">
<spTree>
<sp>
<txBody>
<p>
<r>
<t>Presentation Title</t>
</r>
</p>
</txBody>
</sp>
</spTree>
</sld>`,
description: "Slide content"
};
fileParts['docProps/core.xml'] = {
content: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties">
<dc:title xmlns:dc="http://purl.org/dc/elements/1.1/">Q4 Results Presentation</dc:title>
<dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Marketing Team</dc:creator>
<cp:lastModifiedBy>Sarah Davis</cp:lastModifiedBy>
</cp:coreProperties>`,
description: "Presentation core properties"
};
}
}
// Display the parts diagram
function displayPartsDiagram() {
const container = document.getElementById('parts-container');
container.innerHTML = '';
// Add each part as a clickable item
for (const [partName, partData] of Object.entries(fileParts)) {
const partElement = document.createElement('div');
partElement.className = 'part-item';
partElement.textContent = partName;
partElement.title = partData.description;
// Add special classes for different types
if (partName.includes('hidden')) {
partElement.classList.add('hidden');
} else if (partName.includes('media/') || partName.includes('thumbnail')) {
partElement.classList.add('media');
} else if (partName.includes('core.xml') || partName.includes('app.xml')) {
partElement.classList.add('metadata');
}
partElement.onclick = () => showXmlViewer(partName, partData);
container.appendChild(partElement);
}
}
// Generate sample leak report
function generateLeakReport() {
leakReport = [
{
type: "Author Information",
content: "Creator: Jane Smith",
severity: "medium",
safe: false,
description: "Author name visible in document properties"
},
{
type: "Last Modified By",
content: "Last Modified By: Jane Smith",
severity: "medium",
safe: false,
description: "Name of last person to modify document"
},
{
type: "Tracked Changes",
content: "Inserted content by John Doe (2023-01-15)",
severity: "high",
safe: false,
description: "Unaccepted tracked changes still present in document"
},
{
type: "Comments",
content: "Comment by Alice Johnson: 'Please review this section'",
severity: "medium",
safe: false,
description: "Document comments may contain sensitive information"
},
{
type: "Embedded Thumbnail",
content: "thumbnail.jpeg found",
severity: "low",
safe: true,
description: "Preview image embedded in document"
},
{
type: "EXIF Data",
content: "Image contains GPS coordinates",
severity: "high",
safe: false,
description: "Embedded images may contain location metadata"
}
];
}
// Display the leak report
function displayLeakReport() {
const reportContainer = document.getElementById('leak-report');
reportContainer.innerHTML = '';
leakReport.forEach(item => {
const itemElement = document.createElement('div');
itemElement.className = `leak-item ${item.safe ? 'safe' : ''}`;
itemElement.innerHTML = `
<strong>${item.type}</strong>: ${item.content}
<br><small>${item.description}</small>
`;
reportContainer.appendChild(itemElement);
});
}
// Show XML viewer for a part
function showXmlViewer(partName, partData) {
const viewer = document.getElementById('xml-viewer');
viewer.style.display = 'block';
viewer.innerHTML = `
<h3>${partName}</h3>
<p><em>${partData.description}</em></p>
<pre>${escapeHtml(partData.content)}</pre>
<div style="margin-top: 15px;">
<strong>Analysis:</strong>
<ul>
<li>This part ${partName.includes('core.xml') ? 'contains author metadata that should be reviewed' : 'appears to be standard OOXML content'}</li>
<li>${partName.includes('comments') ? 'Comments should be removed before sharing' : 'No immediate concerns detected'}</li>
<li>${partName.includes('thumbnail') ? 'Thumbnail images may reveal document appearance' : 'Content appears standard'}</li>
</ul>
</div>
<button class="button" onclick="document.getElementById('xml-viewer').style.display='none'" style="margin-top: 10px;">Close</button>
`;
}
// Helper function to escape HTML
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
// Format file size
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Tab switching functionality
function showTab(tabName) {
// Hide all tab contents
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// Remove active class from all tab buttons
document.querySelectorAll('.tab-button').forEach(button => {
button.classList.remove('active');
});
// Show selected tab content
document.getElementById(`${tabName}-tab`).classList.add('active');
// Add active class to clicked button
event.target.classList.add('active');
}
// Load a sample file on page load
function loadSampleFile() {
// Create a fake file object for demonstration
const sampleFile = new File(["sample"], "sample-document.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
// Process it after a short delay to allow UI to render
setTimeout(() => {
processFile(sampleFile);
}, 1000);
}
</script>
</body>
</html>