<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Office Web — Word & Excel</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overflow:hidden}
body{font-family:"Segoe UI",system-ui,-apple-system,Arial,sans-serif;font-size:13px;color:#323130;background:#fff;-webkit-font-smoothing:antialiased}
.hidden{display:none!important}
button,input,select{font-family:inherit;font-size:inherit;color:inherit}
#app{display:flex;flex-direction:column;height:100vh}
#app[data-app="word"]{--accent:#2b579a;--accent-d:#1e3f73;--accent-l:#d2e0f5}
#app[data-app="excel"]{--accent:#217346;--accent-d:#17532f;--accent-l:#d3e5db}
#titlebar{height:46px;flex-shrink:0;display:flex;align-items:center;gap:10px;padding:0 10px;background:var(--accent);color:#fff}
.app-launcher{display:flex;gap:4px}
.app-btn{width:34px;height:34px;border:none;border-radius:4px;cursor:pointer;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:14px;color:#fff;transition:background .15s}
.app-btn:hover{background:rgba(255,255,255,.25)}
.app-btn.active{background:#fff;color:var(--accent)}
.doc-title-wrap{flex:1;display:flex;justify-content:center}
#docTitle{background:transparent;border:1px solid transparent;border-radius:3px;color:#fff;font-size:13px;text-align:center;padding:4px 12px;min-width:180px;max-width:400px;outline:none}
#docTitle:hover{background:rgba(255,255,255,.12)}
#docTitle:focus{background:rgba(255,255,255,.2);border-color:rgba(255,255,255,.5)}
.titlebar-actions{display:flex;gap:4px;align-items:center}
.tb-btn{border:1px solid rgba(255,255,255,.35);background:rgba(255,255,255,.08);color:#fff;border-radius:3px;padding:5px 11px;cursor:pointer;font-size:12px}
.tb-btn:hover{background:rgba(255,255,255,.22)}
.tb-btn.primary{background:#fff;color:var(--accent);font-weight:600;border-color:#fff}
.tb-btn.primary:hover{background:#f0f0f0}
#ribbon{flex-shrink:0;background:#f3f2f1;border-bottom:1px solid #e1dfdd;overflow-x:auto;overflow-y:hidden}
#ribbon::-webkit-scrollbar{height:6px}
#ribbon::-webkit-scrollbar-thumb{background:#c8c6c4;border-radius:3px}
.ribbon-content{display:flex;align-items:stretch;padding:5px 8px;min-height:88px}
.rgroup{display:flex;flex-direction:column;align-items:center;justify-content:space-between;padding:0 8px;border-right:1px solid #e1dfdd;flex-shrink:0}
.rgroup:last-child{border-right:none}
.rgroup-items{display:flex;align-items:center;gap:2px;flex:1;padding-top:2px;flex-wrap:wrap}
.rgroup-label{font-size:10px;color:#605e5c;padding-top:3px;white-space:nowrap}
.rbtn{min-width:28px;height:28px;padding:0 6px;border:1px solid transparent;background:transparent;border-radius:3px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:13px;color:#323130;user-select:none}
.rbtn:hover{background:#e1dfdd;border-color:#c8c6c4}
.rbtn.active{background:var(--accent-l);border-color:var(--accent);color:var(--accent-d)}
.rselect{height:28px;border:1px solid #c8c6c4;border-radius:3px;background:#fff;padding:0 4px;cursor:pointer;font-size:12px;outline:none}
.rselect.small{width:58px}
.rcolor{position:relative;width:28px;height:28px;border:1px solid transparent;border-radius:3px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:13px;font-weight:600;user-select:none}
.rcolor input[type=color]{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%;height:100%;border:none;padding:0}
.rcolor .bar{position:absolute;bottom:2px;left:4px;right:4px;height:3px;border-radius:2px}
.rsep{width:1px;height:24px;background:#e1dfdd;margin:0 3px;flex-shrink:0}
#formulaBar{flex-shrink:0;height:30px;display:flex;align-items:stretch;border-bottom:1px solid #e1dfdd;background:#fff}
.cellref{width:92px;flex-shrink:0;display:flex;align-items:center;justify-content:center;border-right:1px solid #e1dfdd;font-family:Consolas,monospace;font-size:12px;background:#faf9f8;user-select:none}
.fx-btn{width:34px;flex-shrink:0;display:flex;align-items:center;justify-content:center;border-right:1px solid #e1dfdd;font-style:italic;font-weight:600;color:#217346;font-size:13px;background:#faf9f8;user-select:none}
#formulaInput{flex:1;border:none;outline:none;padding:0 10px;font-family:Consolas,monospace;font-size:12px;background:#fff}
#workspace{flex:1;overflow:hidden;display:flex;background:#e6e6e6}
.view{flex:1;overflow:hidden;display:flex;flex-direction:column;min-width:0}
.word-scroll{flex:1;overflow:auto;padding:24px 16px;background:#e6e6e6;display:flex;justify-content:center;align-items:flex-start}
.word-page{width:816px;min-height:1056px;background:#fff;flex-shrink:0;box-shadow:0 2px 14px rgba(0,0,0,.18);padding:80px 88px}
#wordEditor{outline:none;font-family:Calibri,"Segoe UI",sans-serif;font-size:11pt;line-height:1.5;color:#000;min-height:880px;word-wrap:break-word}
#wordEditor p{margin:0 0 8pt 0}
#wordEditor h1{font-size:24pt;margin:12pt 0 6pt}
#wordEditor h2{font-size:18pt;margin:10pt 0 6pt}
#wordEditor h3{font-size:14pt;margin:8pt 0 4pt}
#wordEditor ul,#wordEditor ol{margin:6pt 0;padding-left:36pt}
#wordEditor blockquote{border-left:4px solid #2b579a;margin:8pt 0;padding:4pt 12pt;color:#444;background:#f7f9fc}
#wordEditor table{border-collapse:collapse;margin:8pt 0}
#wordEditor table td,#wordEditor table th{border:1px solid #999;padding:4pt 8pt}
#wordEditor hr{border:none;border-top:1px solid #999;margin:10pt 0}
.grid-container{flex:1;overflow:hidden;position:relative;background:#fff}
#gridScroll{position:absolute;inset:0;overflow:auto;outline:none;background:#fff}
#gridScroll::-webkit-scrollbar{width:14px;height:14px}
#gridScroll::-webkit-scrollbar-track{background:#f3f2f1}
#gridScroll::-webkit-scrollbar-thumb{background:#c8c6c4;border-radius:7px;border:3px solid #f3f2f1}
#grid{border-collapse:separate;border-spacing:0;table-layout:fixed;font-size:12px;font-family:Calibri,"Segoe UI",sans-serif}
#grid th,#grid td{height:24px;padding:0 5px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;border-right:1px solid #d4d4d4;border-bottom:1px solid #d4d4d4;vertical-align:middle}
#grid thead th{position:sticky;top:0;z-index:2;background:#f5f5f5;font-weight:400;text-align:center;color:#444;font-size:11px;user-select:none;cursor:pointer;border-bottom:1px solid #b0b0b0;width:72px;min-width:72px;max-width:72px}
#grid thead th.sel{background:var(--accent-l);color:var(--accent-d);font-weight:600}
#grid .rowhead{position:sticky;left:0;z-index:1;background:#f5f5f5;font-weight:400;text-align:center;color:#444;font-size:11px;user-select:none;cursor:pointer;border-right:1px solid #b0b0b0;width:48px;min-width:48px;max-width:48px}
#grid .rowhead.sel{background:var(--accent-l);color:var(--accent-d);font-weight:600}
#grid .corner{position:sticky;top:0;left:0;z-index:4;background:#e8e8e8;width:48px;min-width:48px;max-width:48px;border-right:1px solid #b0b0b0}
#grid td.cell{background:#fff;cursor:cell;outline:none;user-select:none;width:72px;min-width:72px;max-width:72px}
#grid td.cell.active{outline:2px solid var(--accent);outline-offset:-2px;z-index:1;position:relative}
#grid td.cell.editing{outline:2px solid var(--accent);outline-offset:-2px;z-index:5;position:relative;background:#fff!important;user-select:text;cursor:text;white-space:pre;overflow:visible;padding:0 4px}
#grid td.cell.error{color:#c00;font-weight:600}
#grid td.cell.num{text-align:right}
.sheet-tabs{flex-shrink:0;height:30px;display:flex;align-items:stretch;gap:1px;background:#f3f2f1;border-top:1px solid #e1dfdd;padding:0 6px;overflow-x:auto}
.sheet-tab{border:none;background:transparent;padding:0 16px;cursor:pointer;font-size:12px;border-bottom:2px solid transparent;color:#444;white-space:nowrap}
.sheet-tab.active{background:#fff;border-bottom-color:var(--accent);color:var(--accent);font-weight:600}
.sheet-add{border:none;background:transparent;width:30px;cursor:pointer;font-size:16px;color:#666;border-radius:3px}
#statusbar{flex-shrink:0;height:26px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;background:var(--accent);color:#fff;font-size:11px}
.status-right{display:flex;align-items:center;gap:4px}
.zoom-btn{width:20px;height:20px;border:none;border-radius:3px;background:rgba(255,255,255,.15);color:#fff;cursor:pointer;font-size:13px;line-height:1}
#zoomLabel{min-width:38px;text-align:center}
#toast{position:fixed;bottom:46px;left:50%;transform:translateX(-50%) translateY(20px);background:#323130;color:#fff;padding:10px 20px;border-radius:4px;font-size:12px;opacity:0;pointer-events:none;transition:opacity .22s,transform .22s;z-index:1000;box-shadow:0 4px 16px rgba(0,0,0,.3)}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
@media(max-width:900px){.word-page{width:100%;padding:40px 24px;min-height:auto}}
</style>
</head>
<body>
<div id="app" data-app="word">
<header id="titlebar">
<div class="app-launcher">
<button class="app-btn active" data-goto="word" title="Word">W</button>
<button class="app-btn" data-goto="excel" title="Excel">X</button>
</div>
<div class="doc-title-wrap">
<input id="docTitle" value="Documento1" spellcheck="false" autocomplete="off">
</div>
<div class="titlebar-actions">
<button class="tb-btn" id="btnNew">Nuovo</button>
<button class="tb-btn" id="btnOpen">Apri</button>
<button class="tb-btn" id="btnDownload">Scarica</button>
<button class="tb-btn primary" id="btnSave">Salva</button>
<input type="file" id="fileInput" accept=".json,application/json" style="display:none">
</div>
</header>
<div id="ribbon">
<div class="ribbon-content" id="ribbonWord">
<div class="rgroup">
<div class="rgroup-items">
<button class="rbtn" data-wcmd="undo" title="Annulla">↶</button>
<button class="rbtn" data-wcmd="redo" title="Ripeti">↷</button>
<button class="rbtn" id="wClearFmt" title="Cancella formattazione">⌫</button>
</div>
<div class="rgroup-label">Annulla</div>
</div>
<div class="rgroup">
<div class="rgroup-items">
<select class="rselect" id="wFont" style="width:126px">
<option value="Calibri">Calibri</option>
<option value="Arial">Arial</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Georgia">Georgia</option>
<option value="Verdana">Verdana</option>
<option value="Tahoma">Tahoma</option>
<option value="Courier New">Courier New</option>
<option value="Consolas">Consolas</option>
</select>
<select class="rselect small" id="wSize">
<option>9</option><option>10</option><option selected>11</option><option>12</option>
<option>14</option><option>16</option><option>18</option><option>20</option>
<option>24</option><option>28</option><option>36</option><option>48</option>
</select>
</div>
<div class="rgroup-label">Carattere</div>
</div>
<div class="rgroup">
<div class="rgroup-items">
<button class="rbtn" data-wcmd="bold" style="font-weight:800">G</button>
<button class="rbtn" data-wcmd="italic" style="font-style:italic">C</button>
<button class="rbtn" data-wcmd="underline" style="text-decoration:underline">S</button>
<button class="rbtn" data-wcmd="strikeThrough" style="text-decoration:line-through">B</button>
<button class="rbtn" data-wcmd="superscript">x²</button>
<button class="rbtn" data-wcmd="subscript">x₂</button>
<label class="rcolor">A<span class="bar" id="wForeBar" style="background:#c00000"></span><input type="color" id="wForeColor" value="#c00000"></label>
<label class="rcolor">▨<span class="bar" id="wHiliteBar" style="background:#ffff00"></span><input type="color" id="wHilite" value="#ffff00"></label>
</div>
<div class="rgroup-label">Stili</div>
</div>
<div class="rgroup">
<div class="rgroup-items">
<button class="rbtn" data-wcmd="justifyLeft">≡</button>
<button class="rbtn" data-wcmd="justifyCenter">≡</button>
<button class="rbtn" data-wcmd="justifyRight">≡</button>
<button class="rbtn" data-wcmd="justifyFull">≡</button>
</div>
<div class="rgroup-label">Paragrafo</div>
</div>
<div class="rgroup">
<div class="rgroup-items">
<button class="rbtn" data-wcmd="insertUnorderedList">• —</button>
<button class="rbtn" data-wcmd="insertOrderedList">1. —</button>
<button class="rbtn" data-wcmd="outdent">◄</button>
<button class="rbtn" data-wcmd="indent">►</button>
</div>
<div class="rgroup-label">Elenchi</div>
</div>
<div class="rgroup">
<div class="rgroup-items">
<button class="rbtn" id="wBlock">❝</button>
<button class="rbtn" id="wLink">🔗</button>
<button class="rbtn" id="wImage">🖼</button>
<button class="rbtn" id="wTable">▦</button>
<button class="rbtn" id="wHr">—</button>
</div>
<div class="rgroup-label">Inserisci</div>
</div>
</div>
<div class="ribbon-content hidden" id="ribbonExcel">
<div class="rgroup">
<div class="rgroup-items">
<button class="rbtn" id="xUndo">↶</button>
<button class="rbtn" id="xRedo">↷</button>
<button class="rbtn" id="xClearFmt">⌫</button>
</div>
<div class="rgroup-label">Annulla</div>
</div>
<div class="rgroup">
<div class="rgroup-items">
<select class="rselect" id="xFont" style="width:112px">
<option value="Calibri">Calibri</option>
<option value="Arial">Arial</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Verdana">Verdana</option>
<option value="Consolas">Consolas</option>
</select>
<select class="rselect small" id="xSize">
<option>9</option><option>10</option><option selected>11</option><option>12</option>
<option>14</option><option>16</option><option>18</option><option>20</option><option>24</option>
</select>
</div>
<div class="rgroup-label">Carattere</div>
</div>
<div class="rgroup">
<div class="rgroup-items">
<button class="rbtn" data-xstyle="bold" style="font-weight:800">G</button>
<button class="rbtn" data-xstyle="italic" style="font-style:italic">C</button>
<button class="rbtn" data-xstyle="underline" style="text-decoration:underline">S</button>
<label class="rcolor">A<span class="bar" id="xForeBar" style="background:#c00000"></span><input type="color" id="xForeColor" value="#c00000"></label>
<label class="rcolor">▨<span class="bar" id="xFillBar" style="background:#ffe699"></span><input type="color" id="xFill" value="#ffe699"></label>
</div>
<div class="rgroup-label">Stili</div>
</div>
<div class="rgroup">
<div class="rgroup-items">
<button class="rbtn" data-xstyle="alignLeft">≡</button>
<button class="rbtn" data-xstyle="alignCenter">≡</button>
<button class="rbtn" data-xstyle="alignRight">≡</button>
<button class="rbtn" data-xstyle="wrap">↵</button>
</div>
<div class="rgroup-label">Allineamento</div>
</div>
</div>
</div>
<div id="formulaBar" class="hidden">
<div class="cellref" id="cellRef">A1</div>
<div class="fx-btn">fx</div>
<input id="formulaInput" spellcheck="false" autocomplete="off" placeholder="Valore o formula (es. =A1+B1, =SUM(A1:A5))">
</div>
<div id="workspace">
<div id="wordView" class="view">
<div class="word-scroll">
<div class="word-page">
<div id="wordEditor" contenteditable="true" spellcheck="false"></div>
</div>
</div>
</div>
<div id="excelView" class="view hidden">
<div class="grid-container">
<div id="gridScroll" tabindex="0">
<table id="grid"></table>
</div>
</div>
<div class="sheet-tabs" id="sheetTabs"></div>
</div>
</div>
<div id="statusbar">
<span id="statusInfo">Pronto</span>
<div class="status-right">
<button class="zoom-btn" id="zoomOut">−</button>
<span id="zoomLabel">100%</span>
<button class="zoom-btn" id="zoomIn">+</button>
</div>
</div>
</div>
<div id="toast"></div>
<script>
(function(){
'use strict';
function $(s){ return document.querySelector(s); }
function $$(s){ return Array.prototype.slice.call(document.querySelectorAll(s)); }
function byId(id){ return document.getElementById(id); }
/* ============ UTILS ============ */
function colLetter(i){
var s=''; i++;
while(i>0){ i--; s=String.fromCharCode(65+(i%26))+s; i=Math.floor(i/26); }
return s;
}
function colIndex(letter){
var n=0;
for(var i=0;i<letter.length;i++) n=n*26+(letter.charCodeAt(i)-64);
return n-1;
}
function parseRef(ref){
var m=/^([A-Z]+)(\d+)$/.exec(ref);
if(!m) return null;
return { col: colIndex(m[1]), row: parseInt(m[2],10) };
}
function toast(msg){
var el=byId('toast');
if(!el){ el=document.createElement('div'); el.id='toast'; document.body.appendChild(el); }
el.textContent=msg;
el.classList.add('show');
clearTimeout(toast._t);
toast._t=setTimeout(function(){ el.classList.remove('show'); },1700);
}
function escXml(s){
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
}
function rgbToHex(c){
if(!c) return null;
if(c.charAt(0)==='#') return c.replace('#','').toUpperCase();
var m=/rgba?\((\d+)[,\s]+(\d+)[,\s]+(\d+)/.exec(c);
if(m){
var h=function(n){ n=parseInt(n,10); return (n<16?'0':'')+n.toString(16); };
return (h(m[1])+h(m[2])+h(m[3])).toUpperCase();
}
return null;
}
function downloadBytes(bytes,filename,mime){
var blob=new Blob([bytes],{type:mime});
var url=URL.createObjectURL(blob);
var a=document.createElement('a');
a.href=url; a.download=filename;
document.body.appendChild(a); a.click(); document.body.removeChild(a);
setTimeout(function(){ URL.revokeObjectURL(url); },800);
}
function setHidden(el,hidden){
if(!el) return;
if(hidden) el.classList.add('hidden');
else el.classList.remove('hidden');
}
/* ============ ZIP WRITER ============ */
var crcTable=(function(){
var t=new Uint32Array(256);
for(var n=0;n<256;n++){
var c=n;
for(var k=0;k<8;k++) c=(c&1)?(0xEDB88320^(c>>>1)):(c>>>1);
t[n]=c>>>0;
}
return t;
})();
function crc32(b){
var c=0xFFFFFFFF;
for(var i=0;i<b.length;i++) c=crcTable[(c^b[i])&0xFF]^(c>>>8);
return (c^0xFFFFFFFF)>>>0;
}
function makeZip(files){
var parts=[], cd=[], offset=0;
var now=new Date();
var dTime=((now.getHours()<<11)|(now.getMinutes()<<5)|(now.getSeconds()>>1))&0xFFFF;
var dDate=(((now.getFullYear()-1980)<<9)|((now.getMonth()+1)<<5)|now.getDate())&0xFFFF;
files.forEach(function(f){
var nb=new TextEncoder().encode(f.name);
var crc=crc32(f.data);
var size=f.data.length;
var lh=new Uint8Array(30+nb.length);
var dv=new DataView(lh.buffer);
dv.setUint32(0,0x04034b50,true);
dv.setUint16(4,20,true);
dv.setUint16(6,0x0800,true);
dv.setUint16(8,0,true);
dv.setUint16(10,dTime,true);
dv.setUint16(12,dDate,true);
dv.setUint32(14,crc,true);
dv.setUint32(18,size,true);
dv.setUint32(22,size,true);
dv.setUint16(26,nb.length,true);
dv.setUint16(28,0,true);
lh.set(nb,30);
parts.push(lh,f.data);
var cdh=new Uint8Array(46+nb.length);
var dv2=new DataView(cdh.buffer);
dv2.setUint32(0,0x02014b50,true);
dv2.setUint16(4,20,true);
dv2.setUint16(6,20,true);
dv2.setUint16(8,0x0800,true);
dv2.setUint16(10,0,true);
dv2.setUint16(12,dTime,true);
dv2.setUint16(14,dDate,true);
dv2.setUint32(16,crc,true);
dv2.setUint32(20,size,true);
dv2.setUint32(24,size,true);
dv2.setUint16(28,nb.length,true);
dv2.setUint16(30,0,true);
dv2.setUint16(32,0,true);
dv2.setUint16(34,0,true);
dv2.setUint16(36,0,true);
dv2.setUint32(38,0,true);
dv2.setUint32(42,offset,true);
cdh.set(nb,46);
cd.push(cdh);
offset+=lh.length+size;
});
var cdSize=cd.reduce(function(a,b){ return a+b.length; },0);
var cdStart=offset;
var eocd=new Uint8Array(22);
var dv3=new DataView(eocd.buffer);
dv3.setUint32(0,0x06054b50,true);
dv3.setUint16(4,0,true);
dv3.setUint16(6,0,true);
dv3.setUint16(8,files.length,true);
dv3.setUint16(10,files.length,true);
dv3.setUint32(12,cdSize,true);
dv3.setUint32(16,cdStart,true);
dv3.setUint16(20,0,true);
var all=parts.concat(cd,[eocd]);
var total=all.reduce(function(a,b){ return a+b.length; },0);
var out=new Uint8Array(total);
var pos=0;
all.forEach(function(p){ out.set(p,pos); pos+=p.length; });
return out;
}
/* ============ STATE ============ */
var state={
app:'word',
title:'Documento1',
sheets:[{ name:'Foglio1', cells:{} }],
activeSheet:0,
activeRef:'A1',
zoom:100
};
var editing=false;
var COLS=18, ROWS=60;
/* ============ APP SWITCH ============ */
function switchApp(app){
state.app=app;
var root=byId('app');
if(root) root.setAttribute('data-app',app);
$$('.app-btn').forEach(function(b){ b.classList.toggle('active', b.getAttribute('data-goto')===app); });
setHidden(byId('ribbonWord'), app!=='word');
setHidden(byId('ribbonExcel'), app!=='excel');
setHidden(byId('formulaBar'), app!=='excel');
setHidden(byId('wordView'), app!=='word');
setHidden(byId('excelView'), app!=='excel');
if(app==='excel'){
setTimeout(function(){ var gs=byId('gridScroll'); if(gs&&gs.focus) gs.focus(); },0);
}
}
$$('.app-btn').forEach(function(b){
b.addEventListener('click',function(){ switchApp(b.getAttribute('data-goto')); });
});
/* ============ TITLE ============ */
var docTitle=byId('docTitle');
if(docTitle){
docTitle.addEventListener('input',function(){ state.title=docTitle.value; scheduleSave(); });
}
/* ============ WORD ============ */
var wordEditor=byId('wordEditor');
$$('[data-wcmd]').forEach(function(btn){
btn.addEventListener('mousedown',function(e){
e.preventDefault();
try{ document.execCommand(btn.getAttribute('data-wcmd'),false,null); }catch(err){}
if(wordEditor) wordEditor.focus();
syncWordToolbar();
});
});
var wFont=byId('wFont');
if(wFont) wFont.addEventListener('change',function(e){
document.execCommand('fontName',false,e.target.value);
if(wordEditor) wordEditor.focus();
});
var wSize=byId('wSize');
if(wSize) wSize.addEventListener('change',function(e){
var size=e.target.value;
document.execCommand('fontSize',false,'7');
if(!wordEditor) return;
var fonts=wordEditor.querySelectorAll('font[size="7"]');
for(var i=0;i<fonts.length;i++){
var span=document.createElement('span');
span.style.fontSize=size+'pt';
while(fonts[i].firstChild) span.appendChild(fonts[i].firstChild);
fonts[i].parentNode.replaceChild(span,fonts[i]);
}
wordEditor.focus();
});
var wForeColor=byId('wForeColor');
if(wForeColor) wForeColor.addEventListener('input',function(e){
document.execCommand('foreColor',false,e.target.value);
var b=byId('wForeBar'); if(b) b.style.background=e.target.value;
if(wordEditor) wordEditor.focus();
});
var wHilite=byId('wHilite');
if(wHilite) wHilite.addEventListener('input',function(e){
try{ document.execCommand('hiliteColor',false,e.target.value); }
catch(err){ document.execCommand('backColor',false,e.target.value); }
var b=byId('wHiliteBar'); if(b) b.style.background=e.target.value;
if(wordEditor) wordEditor.focus();
});
var wBlock=byId('wBlock');
if(wBlock) wBlock.addEventListener('click',function(){ document.execCommand('formatBlock',false,'blockquote'); if(wordEditor) wordEditor.focus(); });
var wLink=byId('wLink');
if(wLink) wLink.addEventListener('click',function(){ var u=prompt('URL del link:','https://'); if(u) document.execCommand('createLink',false,u); if(wordEditor) wordEditor.focus(); });
var wImage=byId('wImage');
if(wImage) wImage.addEventListener('click',function(){ var u=prompt('URL immagine:','https://'); if(u) document.execCommand('insertImage',false,u); if(wordEditor) wordEditor.focus(); });
var wTable=byId('wTable');
if(wTable) wTable.addEventListener('click',function(){
var r=parseInt(prompt('Righe:','3'),10)||3;
var c=parseInt(prompt('Colonne:','3'),10)||3;
var html='<table>';
for(var i=0;i<r;i++){ html+='<tr>'; for(var j=0;j<c;j++) html+='<td> </td>'; html+='</tr>'; }
html+='</table><p><br></p>';
document.execCommand('insertHTML',false,html);
if(wordEditor) wordEditor.focus();
});
var wHr=byId('wHr');
if(wHr) wHr.addEventListener('click',function(){ document.execCommand('insertHorizontalRule'); if(wordEditor) wordEditor.focus(); });
var wClearFmt=byId('wClearFmt');
if(wClearFmt) wClearFmt.addEventListener('click',function(){ document.execCommand('removeFormat'); if(wordEditor) wordEditor.focus(); });
function syncWordToolbar(){
['bold','italic','underline','strikeThrough','justifyLeft','justifyCenter','justifyRight','justifyFull','insertUnorderedList','insertOrderedList'].forEach(function(cmd){
var btn=document.querySelector('[data-wcmd="'+cmd+'"]');
if(!btn) return;
try{ btn.classList.toggle('active', document.queryCommandState(cmd)); }catch(e){}
});
}
if(wordEditor){
wordEditor.addEventListener('keyup',syncWordToolbar);
wordEditor.addEventListener('mouseup',syncWordToolbar);
wordEditor.addEventListener('input',scheduleSave);
wordEditor.addEventListener('keydown',function(e){
if(e.ctrlKey||e.metaKey){
var k=e.key.toLowerCase();
if(k==='b'){ e.preventDefault(); document.execCommand('bold'); syncWordToolbar(); }
if(k==='i'){ e.preventDefault(); document.execCommand('italic'); syncWordToolbar(); }
if(k==='u'){ e.preventDefault(); document.execCommand('underline'); syncWordToolbar(); }
}
});
}
/* ============ EXCEL GRID ============ */
var gridScroll=byId('gridScroll');
var grid=byId('grid');
function buildGrid(){
if(!grid) return;
var html='<thead><tr><th class="corner"></th>';
for(var c=0;c<COLS;c++) html+='<th data-col="'+c+'">'+colLetter(c)+'</th>';
html+='</tr></thead><tbody>';
for(var r=1;r<=ROWS;r++){
html+='<tr><th class="rowhead" data-row="'+r+'">'+r+'</th>';
for(var cc=0;cc<COLS;cc++) html+='<td class="cell" data-ref="'+colLetter(cc)+r+'" data-row="'+r+'" data-col="'+cc+'"></td>';
html+='</tr>';
}
html+='</tbody>';
grid.innerHTML=html;
}
buildGrid();
function getSheet(){ return state.sheets[state.activeSheet]; }
function getCells(){ return getSheet().cells; }
function getTd(ref){ return grid?grid.querySelector('td[data-ref="'+ref+'"]'):null; }
/* ============ FORMULA ENGINE ============ */
function evaluate(ref,seen){
seen=seen||{};
if(seen[ref]) return 0;
seen[ref]=true;
var cell=getCells()[ref];
if(!cell||cell.v==null||cell.v==='') return 0;
var v=cell.v;
if(typeof v==='string'&&v.charAt(0)==='='){
try{ return evalFormula(v.slice(1),seen); }
catch(e){ return '#ERR!'; }
}
var n=Number(v);
return isNaN(n)?v:n;
}
function getRange(a,b){
var pa=parseRef(a), pb=parseRef(b);
if(!pa||!pb) return [];
var res=[];
for(var r=Math.min(pa.row,pb.row); r<=Math.max(pa.row,pb.row); r++)
for(var c=Math.min(pa.col,pb.col); c<=Math.max(pa.col,pb.col); c++)
res.push(colLetter(c)+r);
return res;
}
function evalFormula(expr,seen){
expr=expr.trim();
if(expr==='') return '';
var single=/^([A-Z]+\d+)$/i.exec(expr);
if(single) return evaluate(single[1].toUpperCase(),seen);
expr=expr.replace(/(SUM|AVERAGE|AVG|MIN|MAX|COUNT)\(([^)]*)\)/gi,function(m,fn,args){
var nums=[];
args.split(',').forEach(function(part){
part=part.trim();
if(!part) return;
if(part.indexOf(':')>=0){
var ab=part.split(':');
getRange(ab[0].trim(),ab[1].trim()).forEach(function(r){
var v=evaluate(r,Object.assign({},seen));
if(typeof v==='number') nums.push(v);
});
} else if(/^[A-Z]+\d+$/i.test(part)){
var v2=evaluate(part.toUpperCase(),Object.assign({},seen));
if(typeof v2==='number') nums.push(v2);
} else {
var n=Number(part);
if(!isNaN(n)) nums.push(n);
}
});
fn=fn.toUpperCase();
if(fn==='SUM') return nums.reduce(function(a,b){ return a+b; },0);
if(fn==='AVERAGE'||fn==='AVG') return nums.length?nums.reduce(function(a,b){return a+b;},0)/nums.length:0;
if(fn==='MIN') return nums.length?Math.min.apply(null,nums):0;
if(fn==='MAX') return nums.length?Math.max.apply(null,nums):0;
if(fn==='COUNT') return nums.length;
return 0;
});
expr=expr.replace(/\b([A-Z]+\d+)\b/gi,function(m){
var v=evaluate(m.toUpperCase(),Object.assign({},seen));
return typeof v==='number'?String(v):'0';
});
if(!/^[\d\s+\-*/().,]+$/.test(expr)) return '#ERR!';
try{
var result=Function('"use strict"; return ('+expr+');')();
return (typeof result==='number'&&isFinite(result))?result:'#ERR!';
}catch(e){ return '#ERR!'; }
}
/* ============ RENDER GRID ============ */
function renderGrid(){
if(!grid) return;
var cells=getCells();
var tds=grid.querySelectorAll('td.cell');
for(var i=0;i<tds.length;i++){
var td=tds[i];
if(td.classList.contains('editing')) continue;
var ref=td.getAttribute('data-ref');
var cell=cells[ref];
var display='', isNum=false, isErr=false;
if(cell&&cell.v!=null&&cell.v!==''){
if(typeof cell.v==='string'&&cell.v.charAt(0)==='='){
var val=evaluate(ref);
display=String(val);
isNum=(typeof val==='number');
isErr=(val==='#ERR!');
} else {
display=String(cell.v);
var n=Number(cell.v);
isNum=!isNaN(n)&&cell.v!=='';
}
}
td.textContent=display;
td.classList.toggle('num',isNum);
td.classList.toggle('error',isErr);
var s=(cell&&cell.s)||{};
td.style.fontWeight=s.bold?'bold':'';
td.style.fontStyle=s.italic?'italic':'';
td.style.textDecoration=s.underline?'underline':'';
td.style.color=s.color||'';
td.style.background=s.bg||'';
td.style.textAlign=s.align||(isNum?'right':'');
td.style.fontFamily=s.font||'';
td.style.fontSize=s.size?(parseInt(s.size,10)*(state.zoom/100))+'px':'';
td.style.whiteSpace=s.wrap?'normal':'nowrap';
}
updateSelection();
}
function updateSelection(){
if(grid){
var prev=grid.querySelector('td.active');
if(prev) prev.classList.remove('active');
var td=getTd(state.activeRef);
if(td) td.classList.add('active');
var p=parseRef(state.activeRef);
if(p){
var old=grid.querySelectorAll('thead th.sel, .rowhead.sel');
for(var i=0;i<old.length;i++) old[i].classList.remove('sel');
var cth=grid.querySelector('thead th[data-col="'+p.col+'"]');
if(cth) cth.classList.add('sel');
var rth=grid.querySelector('.rowhead[data-row="'+p.row+'"]');
if(rth) rth.classList.add('sel');
}
}
/* Accesso difensivo: l'elemento potrebbe essere stato rimosso dal sanitizer */
var el=byId('cellRef');
if(el) el.textContent=state.activeRef;
if(!editing){
var fi=byId('formulaInput');
if(fi){
var cell=getCells()[state.activeRef];
fi.value=(cell&&cell.v!=null)?cell.v:'';
}
}
}
function setActive(ref){
if(!parseRef(ref)) return;
state.activeRef=ref;
updateSelection();
var td=getTd(ref);
if(td&&gridScroll){
var r=td.getBoundingClientRect(), c=gridScroll.getBoundingClientRect();
if(r.top<c.top+26) gridScroll.scrollTop+=r.top-c.top-26;
else if(r.bottom>c.bottom) gridScroll.scrollTop+=r.bottom-c.bottom;
if(r.left<c.left+50) gridScroll.scrollLeft+=r.left-c.left-50;
else if(r.right>c.right) gridScroll.scrollLeft+=r.right-c.right;
}
}
function moveBy(dr,dc){
var p=parseRef(state.activeRef);
if(!p) return;
var r=Math.max(1,Math.min(ROWS,p.row+dr));
var c=Math.max(0,Math.min(COLS-1,p.col+dc));
setActive(colLetter(c)+r);
}
function startEdit(initialChar){
if(editing) return;
var td=getTd(state.activeRef);
if(!td) return;
editing=true;
td.classList.add('editing');
td.setAttribute('contenteditable','true');
var cell=getCells()[state.activeRef];
var cur=(cell&&cell.v!=null)?String(cell.v):'';
td.textContent=(initialChar!=null)?initialChar:cur;
td.focus();
var sel=window.getSelection();
var range=document.createRange();
range.selectNodeContents(td);
if(initialChar==null&&cur){ sel.removeAllRanges(); sel.addRange(range); }
else { range.collapse(false); sel.removeAllRanges(); sel.addRange(range); }
}
function commitEdit(){
if(!editing) return;
editing=false;
var td=getTd(state.activeRef);
if(!td) return;
var v=td.textContent;
td.classList.remove('editing');
td.removeAttribute('contenteditable');
var cells=getCells();
if(!cells[state.activeRef]) cells[state.activeRef]={};
cells[state.activeRef].v=v;
renderGrid();
scheduleSave();
}
function cancelEdit(){
if(!editing) return;
editing=false;
var td=getTd(state.activeRef);
if(td){ td.classList.remove('editing'); td.removeAttribute('contenteditable'); }
renderGrid();
}
if(grid){
grid.addEventListener('mousedown',function(e){
var td=e.target.closest?e.target.closest('td.cell'):null;
if(!td) return;
if(editing) commitEdit();
setActive(td.getAttribute('data-ref'));
if(gridScroll) gridScroll.focus();
e.preventDefault();
});
grid.addEventListener('dblclick',function(e){
var td=e.target.closest?e.target.closest('td.cell'):null;
if(!td) return;
setActive(td.getAttribute('data-ref'));
startEdit();
});
grid.addEventListener('click',function(e){
var th=e.target.closest?e.target.closest('thead th[data-col]'):null;
if(th){ setActive(colLetter(parseInt(th.getAttribute('data-col'),10))+'1'); if(gridScroll) gridScroll.focus(); return; }
var rh=e.target.closest?e.target.closest('.rowhead'):null;
if(rh){ setActive('A'+rh.getAttribute('data-row')); if(gridScroll) gridScroll.focus(); }
});
}
if(gridScroll){
gridScroll.addEventListener('keydown',function(e){
if(editing){
if(e.key==='Enter'){ e.preventDefault(); commitEdit(); moveBy(1,0); gridScroll.focus(); }
else if(e.key==='Tab'){ e.preventDefault(); commitEdit(); moveBy(0,e.shiftKey?-1:1); gridScroll.focus(); }
else if(e.key==='Escape'){ e.preventDefault(); cancelEdit(); gridScroll.focus(); }
return;
}
switch(e.key){
case 'ArrowUp': e.preventDefault(); moveBy(-1,0); break;
case 'ArrowDown': e.preventDefault(); moveBy(1,0); break;
case 'ArrowLeft': e.preventDefault(); moveBy(0,-1); break;
case 'ArrowRight': e.preventDefault(); moveBy(0,1); break;
case 'Tab': e.preventDefault(); moveBy(0,e.shiftKey?-1:1); break;
case 'Enter': case 'F2': e.preventDefault(); startEdit(); break;
case 'Delete': case 'Backspace':
e.preventDefault();
var cells=getCells();
if(!cells[state.activeRef]) cells[state.activeRef]={};
cells[state.activeRef].v='';
renderGrid(); scheduleSave(); break;
default:
if(e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey){
e.preventDefault();
startEdit(e.key);
}
}
});
}
var formulaInput=byId('formulaInput');
if(formulaInput){
formulaInput.addEventListener('keydown',function(e){
if(e.key==='Enter'){
e.preventDefault();
var cells=getCells();
if(!cells[state.activeRef]) cells[state.activeRef]={};
cells[state.activeRef].v=formulaInput.value;
renderGrid(); scheduleSave();
if(gridScroll) gridScroll.focus();
moveBy(1,0);
} else if(e.key==='Escape'){ formulaInput.value=''; if(gridScroll) gridScroll.focus(); }
});
formulaInput.addEventListener('blur',function(){
var cells=getCells();
var cell=cells[state.activeRef];
var cur=cell?(cell.v||''):'';
if(formulaInput.value!==cur){
if(!cell) cell=cells[state.activeRef]={};
cell.v=formulaInput.value;
renderGrid(); scheduleSave();
}
});
}
$$('[data-xstyle]').forEach(function(btn){
btn.addEventListener('click',function(){
var st=btn.getAttribute('data-xstyle');
var cells=getCells();
if(!cells[state.activeRef]) cells[state.activeRef]={};
var cell=cells[state.activeRef];
if(!cell.s) cell.s={};
if(st==='bold') cell.s.bold=!cell.s.bold;
else if(st==='italic') cell.s.italic=!cell.s.italic;
else if(st==='underline') cell.s.underline=!cell.s.underline;
else if(st==='alignLeft') cell.s.align='left';
else if(st==='alignCenter') cell.s.align='center';
else if(st==='alignRight') cell.s.align='right';
else if(st==='wrap') cell.s.wrap=!cell.s.wrap;
btn.classList.toggle('active');
renderGrid(); scheduleSave();
if(gridScroll) gridScroll.focus();
});
});
var xFont=byId('xFont');
if(xFont) xFont.addEventListener('change',function(e){
var cells=getCells();
if(!cells[state.activeRef]) cells[state.activeRef]={};
if(!cells[state.activeRef].s) cells[state.activeRef].s={};
cells[state.activeRef].s.font=e.target.value;
renderGrid(); scheduleSave();
});
var xSize=byId('xSize');
if(xSize) xSize.addEventListener('change',function(e){
var cells=getCells();
if(!cells[state.activeRef]) cells[state.activeRef]={};
if(!cells[state.activeRef].s) cells[state.activeRef].s={};
cells[state.activeRef].s.size=e.target.value;
renderGrid(); scheduleSave();
});
var xForeColor=byId('xForeColor');
if(xForeColor) xForeColor.addEventListener('input',function(e){
var cells=getCells();
if(!cells[state.activeRef]) cells[state.activeRef]={};
if(!cells[state.activeRef].s) cells[state.activeRef].s={};
cells[state.activeRef].s.color=e.target.value;
var b=byId('xForeBar'); if(b) b.style.background=e.target.value;
renderGrid(); scheduleSave();
});
var xFill=byId('xFill');
if(xFill) xFill.addEventListener('input',function(e){
var cells=getCells();
if(!cells[state.activeRef]) cells[state.activeRef]={};
if(!cells[state.activeRef].s) cells[state.activeRef].s={};
cells[state.activeRef].s.bg=e.target.value;
var b=byId('xFillBar'); if(b) b.style.background=e.target.value;
renderGrid(); scheduleSave();
});
var xClearFmt=byId('xClearFmt');
if(xClearFmt) xClearFmt.addEventListener('click',function(){
var cells=getCells();
if(cells[state.activeRef]) cells[state.activeRef].s={};
renderGrid(); scheduleSave();
});
var xUndo=byId('xUndo'); if(xUndo) xUndo.addEventListener('click',function(){ toast('Annulla non disponibile'); });
var xRedo=byId('xRedo'); if(xRedo) xRedo.addEventListener('click',function(){ toast('Ripeti non disponibile'); });
/* ============ SHEET TABS ============ */
function renderSheetTabs(){
var cont=byId('sheetTabs');
if(!cont) return;
var html='';
state.sheets.forEach(function(s,i){
html+='<button class="sheet-tab'+(i===state.activeSheet?' active':'')+'" data-sheet="'+i+'">'+s.name+'</button>';
});
html+='<button class="sheet-add" title="Nuovo foglio">+</button>';
cont.innerHTML=html;
}
var sheetTabsEl=byId('sheetTabs');
if(sheetTabsEl){
sheetTabsEl.addEventListener('click',function(e){
var tab=e.target.closest?e.target.closest('.sheet-tab'):null;
if(tab){
state.activeSheet=parseInt(tab.getAttribute('data-sheet'),10);
state.activeRef='A1';
renderSheetTabs(); renderGrid(); setActive('A1');
return;
}
if(e.target.classList.contains('sheet-add')){
state.sheets.push({ name:'Foglio'+(state.sheets.length+1), cells:{} });
state.activeSheet=state.sheets.length-1;
state.activeRef='A1';
renderSheetTabs(); renderGrid(); setActive('A1');
scheduleSave();
}
});
}
/* ============ SAVE / LOAD ============ */
function serialize(){
return {
version:1, app:state.app, title:state.title,
wordHTML:wordEditor?wordEditor.innerHTML:'',
sheets:state.sheets, activeSheet:state.activeSheet
};
}
function deserialize(data){
if(!data||typeof data!=='object') return;
if(typeof data.title==='string'){ state.title=data.title; if(docTitle) docTitle.value=data.title; }
if(typeof data.wordHTML==='string'&&wordEditor){ wordEditor.innerHTML=data.wordHTML; }
if(Array.isArray(data.sheets)&&data.sheets.length){
state.sheets=data.sheets.map(function(s){ return { name:s.name||'Foglio1', cells:s.cells||{} }; });
state.activeSheet=Math.min(data.activeSheet||0,state.sheets.length-1);
}
state.activeRef='A1';
renderSheetTabs(); renderGrid(); setActive('A1');
if(data.app==='word'||data.app==='excel') switchApp(data.app);
}
var saveTimer;
function scheduleSave(){ clearTimeout(saveTimer); saveTimer=setTimeout(saveToStorage,600); }
function saveToStorage(){
try{
localStorage.setItem('officeweb.v1',JSON.stringify(serialize()));
toast('Salvato nel browser');
}catch(e){ toast('Errore di salvataggio'); }
}
function loadFromStorage(){
try{
var s=localStorage.getItem('officeweb.v1');
if(s){ deserialize(JSON.parse(s)); return true; }
}catch(e){}
return false;
}
/* ============ EXPORT .DOCX ============ */
function wordRunXml(text,st){
var rPr='';
if(st.b) rPr+='<w:b/>';
if(st.i) rPr+='<w:i/>';
if(st.u) rPr+='<w:u w:val="single"/>';
if(st.strike) rPr+='<w:strike/>';
if(st.vert) rPr+='<w:vertAlign w:val="'+st.vert+'"/>';
if(st.sz) rPr+='<w:sz w:val="'+st.sz+'"/><w:szCs w:val="'+st.sz+'"/>';
if(st.color) rPr+='<w:color w:val="'+st.color+'"/>';
if(st.shd) rPr+='<w:shd w:val="clear" w:color="auto" w:fill="'+st.shd+'"/>';
if(st.font) rPr+='<w:rFonts w:ascii="'+escXml(st.font)+'" w:hAnsi="'+escXml(st.font)+'"/>';
return '<w:r>'+(rPr?'<w:rPr>'+rPr+'</w:rPr>':'')+'<w:t xml:space="preserve">'+escXml(text)+'</w:t></w:r>';
}
function wordInlineToXml(node,base){
if(node.nodeType===3){
var t=node.textContent;
if(!t) return '';
return wordRunXml(t,base);
}
if(node.nodeType!==1) return '';
var tag=node.tagName.toLowerCase();
var st=Object.assign({},base);
if(tag==='b'||tag==='strong') st.b=true;
else if(tag==='i'||tag==='em') st.i=true;
else if(tag==='u') st.u=true;
else if(tag==='s'||tag==='strike'||tag==='del') st.strike=true;
else if(tag==='sup') st.vert='superscript';
else if(tag==='sub') st.vert='subscript';
else if(tag==='br') return '<w:r><w:br/></w:r>';
if(node.style){
var css=node.style;
if(css.fontWeight==='bold'||css.fontWeight==='700'||parseInt(css.fontWeight,10)>=600) st.b=true;
if(css.fontStyle==='italic') st.i=true;
if(css.textDecoration&&css.textDecoration.indexOf('underline')>=0) st.u=true;
if(css.textDecoration&&css.textDecoration.indexOf('line-through')>=0) st.strike=true;
if(css.color){ var hc=rgbToHex(css.color); if(hc) st.color=hc; }
if(css.backgroundColor){ var hb=rgbToHex(css.backgroundColor); if(hb) st.shd=hb; }
if(css.fontSize){
var pts=parseFloat(css.fontSize);
if(css.fontSize.indexOf('pt')<0) pts=pts*0.75;
if(pts>0) st.sz=Math.round(pts*2);
}
if(css.fontFamily){
var ff=css.fontFamily.replace(/["']/g,'').split(',')[0].trim();
if(ff) st.font=ff;
}
}
var out='';
Array.prototype.forEach.call(node.childNodes,function(c){ out+=wordInlineToXml(c,st); });
return out;
}
function wordRunsToXml(node,base){
var xml='';
Array.prototype.forEach.call(node.childNodes,function(c){ xml+=wordInlineToXml(c,base); });
return xml||'<w:r><w:t xml:space="preserve"> </w:t></w:r>';
}
function wordBlockToXml(node){
if(node.nodeType===3){
var t=node.textContent;
if(!t.trim()) return '';
return '<w:p>'+wordRunXml(t,{})+'</w:p>';
}
if(node.nodeType!==1) return '';
var tag=node.tagName.toLowerCase();
if(tag==='p'||tag==='div') return '<w:p>'+wordRunsToXml(node,{})+'</w:p>';
if(/^h[1-6]$/.test(tag)){
var szMap={h1:48,h2:36,h3:28,h4:24,h5:20,h6:16};
var sz=szMap[tag]||24;
return '<w:p><w:pPr><w:spacing w:before="240" w:after="120"/></w:pPr>'+wordRunsToXml(node,{b:true,sz:sz})+'</w:p>';
}
if(tag==='ul'||tag==='ol'){
var out='', idx=1;
Array.prototype.forEach.call(node.children,function(li){
if(li.tagName&&li.tagName.toLowerCase()==='li'){
var prefix=tag==='ul'?'• ':(idx+'. ');
out+='<w:p><w:pPr><w:ind w:left="720" w:hanging="360"/></w:pPr>'+
'<w:r><w:t xml:space="preserve">'+escXml(prefix)+'</w:t></w:r>'+
wordRunsToXml(li,{})+'</w:p>';
idx++;
}
});
return out;
}
if(tag==='blockquote'){
return '<w:p><w:pPr><w:ind w:left="720"/></w:pPr>'+wordRunsToXml(node,{i:true})+'</w:p>';
}
if(tag==='hr'){
return '<w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:space="1" w:color="999999"/></w:pBdr></w:pPr></w:p>';
}
if(tag==='table'){
var rowsXml='';
Array.prototype.forEach.call(node.querySelectorAll('tr'),function(tr){
var cellsXml='';
Array.prototype.forEach.call(tr.children,function(td){
cellsXml+='<w:tc><w:tcPr><w:tcW w:w="2400" w:type="dxa"/></w:tcPr><w:p>'+wordRunsToXml(td,{})+'</w:p></w:tc>';
});
rowsXml+='<w:tr>'+cellsXml+'</w:tr>';
});
return '<w:tbl><w:tblPr><w:tblBorders>'+
'<w:top w:val="single" w:sz="4" w:color="999999"/>'+
'<w:left w:val="single" w:sz="4" w:color="999999"/>'+
'<w:bottom w:val="single" w:sz="4" w:color="999999"/>'+
'<w:right w:val="single" w:sz="4" w:color="999999"/>'+
'<w:insideH w:val="single" w:sz="4" w:color="999999"/>'+
'<w:insideV w:val="single" w:sz="4" w:color="999999"/>'+
'</w:tblBorders></w:tblPr>'+rowsXml+'</w:tbl><w:p/>';
}
return '<w:p>'+wordRunsToXml(node,{})+'</w:p>';
}
function buildDocx(html){
var container=document.createElement('div');
container.innerHTML=html;
var bodyXml='';
Array.prototype.forEach.call(container.childNodes,function(n){ bodyXml+=wordBlockToXml(n); });
if(!bodyXml) bodyXml='<w:p/>';
var documentXml='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
'<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" '+
'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'+
'<w:body>'+bodyXml+
'<w:sectPr><w:pgSz w:w="11906" w:h="16838"/>'+
'<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/>'+
'</w:sectPr></w:body></w:document>';
var contentTypes='<?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>';
var rels='<?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>';
var enc=new TextEncoder();
return makeZip([
{ name:'[Content_Types].xml', data:enc.encode(contentTypes) },
{ name:'_rels/.rels', data:enc.encode(rels) },
{ name:'word/document.xml', data:enc.encode(documentXml) }
]);
}
function exportDocx(){
var title=state.title||'Documento';
var bytes=buildDocx(wordEditor?wordEditor.innerHTML:'');
downloadBytes(bytes,title+'.docx','application/vnd.openxmlformats-officedocument.wordprocessingml.document');
toast('Scaricato '+title+'.docx');
}
/* ============ EXPORT .XLSX ============ */
function buildXlsx(){
var sheet=getSheet();
var cells=sheet.cells;
var maxRow=1, maxCol=0;
Object.keys(cells).forEach(function(ref){
var p=parseRef(ref);
if(!p) return;
if(p.row>maxRow) maxRow=p.row;
if(p.col>maxCol) maxCol=p.col;
});
var rowsXml='';
for(var r=1;r<=maxRow;r++){
var cellsXml='';
for(var c=0;c<=maxCol;c++){
var ref=colLetter(c)+r;
var cell=cells[ref];
if(!cell||cell.v==null||cell.v==='') continue;
var v=String(cell.v);
var attrs=' r="'+ref+'"';
if(v.charAt(0)==='='){
var result=evaluate(ref);
var formula=v.slice(1);
if(typeof result==='number'&&isFinite(result)){
cellsXml+='<c'+attrs+'><f>'+escXml(formula)+'</f><v>'+result+'</v></c>';
} else {
cellsXml+='<c'+attrs+' t="str"><f>'+escXml(formula)+'</f><v>'+escXml(String(result))+'</v></c>';
}
} else {
var num=Number(v);
if(!isNaN(num)&&v.trim()!==''&&/^-?\d+(\.\d+)?$/.test(v.trim())){
cellsXml+='<c'+attrs+'><v>'+num+'</v></c>';
} else {
cellsXml+='<c'+attrs+' t="inlineStr"><is><t xml:space="preserve">'+escXml(v)+'</t></is></c>';
}
}
}
if(cellsXml) rowsXml+='<row r="'+r+'">'+cellsXml+'</row>';
}
if(!rowsXml) rowsXml='<row r="1"><c r="A1" t="inlineStr"><is><t></t></is></c></row>';
var sheetXml='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
'<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'+
'<sheetData>'+rowsXml+'</sheetData></worksheet>';
var workbookXml='<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
'<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" '+
'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'+
'<sheets><sheet name="'+escXml(sheet.name)+'" sheetId="1" r:id="rId1"/></sheets></workbook>';
var wbRels='<?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/worksheet" Target="worksheets/sheet1.xml"/>'+
'</Relationships>';
var contentTypes='<?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="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'+
'<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'+
'</Types>';
var rels='<?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="xl/workbook.xml"/>'+
'</Relationships>';
var enc=new TextEncoder();
return makeZip([
{ name:'[Content_Types].xml', data:enc.encode(contentTypes) },
{ name:'_rels/.rels', data:enc.encode(rels) },
{ name:'xl/workbook.xml', data:enc.encode(workbookXml) },
{ name:'xl/_rels/workbook.xml.rels', data:enc.encode(wbRels) },
{ name:'xl/worksheets/sheet1.xml', data:enc.encode(sheetXml) }
]);
}
function exportXlsx(){
var title=state.title||'Cartella';
var bytes=buildXlsx();
downloadBytes(bytes,title+'.xlsx','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
toast('Scaricato '+title+'.xlsx');
}
/* ============ TITLEBAR ACTIONS ============ */
var btnSave=byId('btnSave');
if(btnSave) btnSave.addEventListener('click',function(e){
if(e.shiftKey){
var json=JSON.stringify(serialize(),null,2);
downloadBytes(new TextEncoder().encode(json),(state.title||'backup')+'.json','application/json');
toast('Backup JSON scaricato');
return;
}
saveToStorage();
});
var btnDownload=byId('btnDownload');
if(btnDownload) btnDownload.addEventListener('click',function(){
try{
if(state.app==='word') exportDocx();
else exportXlsx();
}catch(err){
alert('Errore durante l\'esportazione: '+err.message);
console.error(err);
}
});
var btnNew=byId('btnNew');
if(btnNew) btnNew.addEventListener('click',function(){
if(!confirm('Creare un nuovo documento? Le modifiche non salvate andranno perse.')) return;
state.title='Documento1';
if(docTitle) docTitle.value=state.title;
if(wordEditor) wordEditor.innerHTML='<p><br></p>';
state.sheets=[{ name:'Foglio1', cells:{} }];
state.activeSheet=0;
state.activeRef='A1';
renderSheetTabs(); renderGrid(); setActive('A1');
toast('Nuovo documento');
});
var btnOpen=byId('btnOpen');
var fileInput=byId('fileInput');
if(btnOpen&&fileInput) btnOpen.addEventListener('click',function(){ fileInput.click(); });
if(fileInput) fileInput.addEventListener('change',function(e){
var file=e.target.files[0];
if(!file) return;
var reader=new FileReader();
reader.onload=function(ev){
try{
deserialize(JSON.parse(ev.target.result));
toast('File aperto');
}catch(err){
alert('File non valido.');
}
};
reader.readAsText(file);
e.target.value='';
});
/* ============ ZOOM ============ */
function applyZoom(){
var z=state.zoom/100;
var wp=document.querySelector('.word-page');
if(wp) wp.style.zoom=z;
if(grid) grid.style.fontSize=(12*z)+'px';
var zl=byId('zoomLabel'); if(zl) zl.textContent=state.zoom+'%';
}
var zoomIn=byId('zoomIn');
var zoomOut=byId('zoomOut');
if(zoomIn) zoomIn.addEventListener('click',function(){ state.zoom=Math.min(200,state.zoom+10); applyZoom(); });
if(zoomOut) zoomOut.addEventListener('click',function(){ state.zoom=Math.max(50,state.zoom-10); applyZoom(); });
/* ============ GLOBAL SHORTCUT ============ */
document.addEventListener('keydown',function(e){
if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==='s'){
e.preventDefault();
saveToStorage();
}
});
/* ============ INIT ============ */
try {
renderSheetTabs();
if(!loadFromStorage() && wordEditor){
wordEditor.innerHTML=
'<h1>Benvenuto in Office Web</h1>'+
'<p>Versione semplificata di Word ed Excel in un unico file HTML, CSS e JavaScript.</p>'+
'<h2>Word</h2>'+
'<ul>'+
'<li>Formattazione: grassetto, corsivo, sottolineato, barrato, apice, pedice</li>'+
'<li>Colori testo ed evidenziazione</li>'+
'<li>Allineamento, elenchi, link, immagini, tabelle</li>'+
'<li>Scorciatoie: Ctrl+B, Ctrl+I, Ctrl+U</li>'+
'</ul>'+
'<h2>Excel</h2>'+
'<ul>'+
'<li>Griglia con celle selezionabili e modificabili</li>'+
'<li>Formule: <code>=A1+B1</code>, <code>=SUM(A1:A10)</code>, <code>=AVERAGE</code>, <code>=MIN</code>, <code>=MAX</code>, <code>=COUNT</code></li>'+
'<li>Stili, colori, allineamento, testo a capo</li>'+
'<li>Barra della formula editabile</li>'+
'</ul>'+
'<p><b>Scarica</b> esporta <b>.docx</b> in Word e <b>.xlsx</b> in Excel, apribili con Microsoft Office.</p>';
}
switchApp(state.app||'word');
renderGrid();
setActive('A1');
syncWordToolbar();
applyZoom();
} catch(err){
console.error('Errore di inizializzazione:',err);
}
})();
</script>
</body>
</html>1 views