@@ -19,6 +19,7 @@ import {
1919 getCompleteHtml ,
2020 getFilenameFromUrl ,
2121 blobToBase64 ,
22+ migrateItemToPages ,
2223} from '../utils' ;
2324import { itemService } from '../itemService' ;
2425import '../db' ;
@@ -65,8 +66,8 @@ const UNSAVED_WARNING_COUNT = 15;
6566const version = '3.6.1' ;
6667
6768export default class App extends Component {
68- constructor ( ) {
69- super ( ) ;
69+ constructor ( props ) {
70+ super ( props ) ;
7071 this . AUTO_SAVE_INTERVAL = 15000 ; // 15 seconds
7172 this . modalDefaultStates = {
7273 isModalOpen : false ,
@@ -438,20 +439,9 @@ BookLibService.Borrow(id) {
438439
439440 async setCurrentItem ( item ) {
440441 const d = deferred ( ) ;
441- // TODO: remove later
442- item . htmlMode =
443- item . htmlMode || this . state . prefs . htmlMode || HtmlModes . HTML ;
444- item . cssMode = item . cssMode || this . state . prefs . cssMode || CssModes . CSS ;
445- item . jsMode = item . jsMode || this . state . prefs . jsMode || JsModes . JS ;
446-
447- await this . setState ( { currentItem : item } , d . resolve ) ;
448-
449- this . saveItem ( ) ;
450-
451- // Reset unsaved count, in UI also.
452- await this . setState ( { unsavedEditCount : 0 } ) ;
453- currentBrowserTab . setTitle ( item . title ) ;
454-
442+ // Migrate the item to the new pages format if needed
443+ const migratedItem = migrateItemToPages ( item ) ;
444+ await this . setState ( { currentItem : migratedItem } , d . resolve ) ;
455445 return d . promise ;
456446 }
457447
@@ -675,6 +665,9 @@ BookLibService.Borrow(id) {
675665
676666 trackGaSetField ( 'page' , '/' ) ;
677667 trackPageView ( ) ;
668+
669+ // Expose app instance for testing
670+ window . _app = this ;
678671 }
679672
680673 async closeAllOverlays ( ) {
@@ -890,25 +883,36 @@ BookLibService.Borrow(id) {
890883 await this . setState ( { currentItem : item } ) ;
891884 }
892885
893- async onCodeChange ( type , code , isUserChange ) {
894- this . state . currentItem [ type ] = code ;
895- if ( isUserChange ) {
886+ async onCodeChange ( type , code , isUserChange , updatedItem ) {
887+ // If an updatedItem is provided (with updated pages), use it instead of just updating the type
888+ if ( updatedItem ) {
896889 await this . setState ( {
897- unsavedEditCount : this . state . unsavedEditCount + 1 ,
890+ currentItem : updatedItem ,
891+ unsavedEditCount : isUserChange ? this . state . unsavedEditCount + 1 : this . state . unsavedEditCount
898892 } ) ;
899-
900- if (
901- this . state . unsavedEditCount % UNSAVED_WARNING_COUNT === 0 &&
902- this . state . unsavedEditCount >= UNSAVED_WARNING_COUNT
903- ) {
904- window . saveBtn . classList . add ( 'animated' ) ;
905- window . saveBtn . classList . add ( 'wobble' ) ;
906- window . saveBtn . addEventListener ( 'animationend' , ( ) => {
907- window . saveBtn . classList . remove ( 'animated' ) ;
908- window . saveBtn . classList . remove ( 'wobble' ) ;
893+ } else {
894+ // Original behavior
895+ this . state . currentItem [ type ] = code ;
896+ if ( isUserChange ) {
897+ await this . setState ( {
898+ unsavedEditCount : this . state . unsavedEditCount + 1 ,
909899 } ) ;
910900 }
911901 }
902+
903+ if (
904+ isUserChange &&
905+ this . state . unsavedEditCount % UNSAVED_WARNING_COUNT === 0 &&
906+ this . state . unsavedEditCount >= UNSAVED_WARNING_COUNT
907+ ) {
908+ window . saveBtn . classList . add ( 'animated' ) ;
909+ window . saveBtn . classList . add ( 'wobble' ) ;
910+ window . saveBtn . addEventListener ( 'animationend' , ( ) => {
911+ window . saveBtn . classList . remove ( 'animated' ) ;
912+ window . saveBtn . classList . remove ( 'wobble' ) ;
913+ } ) ;
914+ }
915+
912916 if ( this . state . prefs . isJs13kModeOn ) {
913917 // Throttling codesize calculation
914918 if ( this . codeSizeCalculationTimeout ) {
@@ -1503,6 +1507,125 @@ BookLibService.Borrow(id) {
15031507 mixpanel . track ( { event : 'openSettingsModal' , category : 'ui' } ) ;
15041508 }
15051509
1510+ getCurrentPage ( ) {
1511+ const { currentItem } = this . state ;
1512+ if ( ! currentItem || ! currentItem . pages || ! currentItem . currentPageId ) {
1513+ return null ;
1514+ }
1515+
1516+ return currentItem . pages . find ( page => page . id === currentItem . currentPageId ) || null ;
1517+ }
1518+
1519+ addNewPage ( title = 'New Page' ) {
1520+ const { currentItem } = this . state ;
1521+ if ( ! currentItem ) return null ;
1522+
1523+ const newPage = {
1524+ id : generateRandomId ( ) ,
1525+ title,
1526+ js : '' ,
1527+ css : '' ,
1528+ isDefault : false
1529+ } ;
1530+
1531+ const updatedItem = {
1532+ ...currentItem ,
1533+ pages : [ ...currentItem . pages , newPage ]
1534+ } ;
1535+
1536+ this . setState ( { currentItem : updatedItem } ) ;
1537+
1538+ // Switch to the new page
1539+ this . switchToPage ( newPage . id ) ;
1540+
1541+ return newPage . id ;
1542+ }
1543+
1544+ switchToPage ( pageId ) {
1545+ const { currentItem } = this . state ;
1546+ if ( ! currentItem ) return ;
1547+
1548+ const pageExists = currentItem . pages . some ( page => page . id === pageId ) ;
1549+ if ( ! pageExists ) return ;
1550+
1551+ const updatedItem = {
1552+ ...currentItem ,
1553+ currentPageId : pageId
1554+ } ;
1555+
1556+ this . setState ( { currentItem : updatedItem } , ( ) => {
1557+ // Refresh the editor to show the new page content
1558+ if ( this . contentWrap ) {
1559+ this . contentWrap . refreshEditor ( ) ;
1560+ }
1561+ } ) ;
1562+ }
1563+
1564+ updatePage ( pageId , updates ) {
1565+ const { currentItem } = this . state ;
1566+ if ( ! currentItem ) return ;
1567+
1568+ const pageIndex = currentItem . pages . findIndex ( page => page . id === pageId ) ;
1569+ if ( pageIndex === - 1 ) return ;
1570+
1571+ const updatedPages = [ ...currentItem . pages ] ;
1572+ updatedPages [ pageIndex ] = {
1573+ ...updatedPages [ pageIndex ] ,
1574+ ...updates
1575+ } ;
1576+
1577+ const updatedItem = {
1578+ ...currentItem ,
1579+ pages : updatedPages
1580+ } ;
1581+
1582+ // If we're updating the current page's js or css, also update the item's js/css for backward compatibility
1583+ if ( pageId === currentItem . currentPageId ) {
1584+ if ( updates . js !== undefined ) {
1585+ updatedItem . js = updates . js ;
1586+ }
1587+ if ( updates . css !== undefined ) {
1588+ updatedItem . css = updates . css ;
1589+ }
1590+ }
1591+
1592+ this . setState ( { currentItem : updatedItem } ) ;
1593+ }
1594+
1595+ deletePage ( pageId ) {
1596+ const { currentItem } = this . state ;
1597+ if ( ! currentItem ) return ;
1598+
1599+ // Don't allow deleting the last page
1600+ if ( currentItem . pages . length <= 1 ) return ;
1601+
1602+ const pageIndex = currentItem . pages . findIndex ( page => page . id === pageId ) ;
1603+ if ( pageIndex === - 1 ) return ;
1604+
1605+ const updatedPages = currentItem . pages . filter ( page => page . id !== pageId ) ;
1606+
1607+ // If we're deleting the current page, switch to another page
1608+ let updatedCurrentPageId = currentItem . currentPageId ;
1609+ if ( pageId === currentItem . currentPageId ) {
1610+ // Find the nearest page to switch to
1611+ const newPageIndex = Math . min ( pageIndex , updatedPages . length - 1 ) ;
1612+ updatedCurrentPageId = updatedPages [ newPageIndex ] . id ;
1613+ }
1614+
1615+ const updatedItem = {
1616+ ...currentItem ,
1617+ pages : updatedPages ,
1618+ currentPageId : updatedCurrentPageId
1619+ } ;
1620+
1621+ this . setState ( { currentItem : updatedItem } , ( ) => {
1622+ // If we switched pages, refresh the editor
1623+ if ( pageId === currentItem . currentPageId && this . contentWrap ) {
1624+ this . contentWrap . refreshEditor ( ) ;
1625+ }
1626+ } ) ;
1627+ }
1628+
15061629 render ( ) {
15071630 // remove field imageBase64 from currentItem and save it to a local variable as a copy
15081631 const { imageBase64, ...currentItem } = this . state . currentItem ;
0 commit comments