[
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release binaries\n\non:\n  release:\n    types: [published]\n\njobs:\n\n  build:\n\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [windows-latest, ubuntu-latest, macOS-latest]\n        include:\n          - os: windows-latest\n            zip_name: zonote-win\n          - os: ubuntu-latest\n            zip_name: zonote-linux\n          - os: macOS-latest\n            zip_name: zonote-mac\n\n    steps:\n      - uses: actions/checkout@v1\n\n      - uses: actions/setup-node@v1\n        with:\n          node-version: 12\n\n      - name: Install dependencies\n        run: npm i\n\n      - name: Build Windows\n        if: matrix.os == 'windows-latest'\n        shell: bash\n        run: npm run build:windows\n      \n      - name: Build Linux\n        if: matrix.os == 'ubuntu-latest'\n        shell: bash\n        run: npm run build:linux\n      \n      - name: Build Mac\n        if: matrix.os == 'macOS-latest'\n        shell: bash\n        run: npm run build:mac\n\n      - name: Zip\n        shell: bash\n        env:\n          ZIP_NAME: ${{ matrix.zip_name }}\n        run: |\n          cd dist\n          7z a ../${ZIP_NAME}.zip .\n\n      - name: Upload release asset\n        if: github.event.action == 'published'\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ github.event.release.upload_url }}\n          asset_path: ./${{ matrix.zip_name }}.zip\n          asset_name: ${{ matrix.zip_name }}.zip\n          asset_content_type: application/zip\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\ndist\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2020 Osvaldo Zonetti\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  Cross-platform desktop note-taking app\n  <br>\n  Sticky notes + Markdown + Tabs\n  <br>\n  All in one .txt file\n  <br>\n  <br>\n  <img src=\"https://github.com/zonetti/zonote/raw/master/preview.gif\" width=\"95%\">\n</p>\n\n### Download\n\nYou can find the latest version to download on the [release page](https://github.com/zonetti/zonote/releases/latest).\n\n### Development\n\n```\ngit clone git@github.com:zonetti/zonote.git\ncd zonote\nnpm install && npm start\n```\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"zonote\",\n  \"version\": \"0.4.4\",\n  \"main\": \"src/main/main.js\",\n  \"scripts\": {\n    \"start\": \"electron . --dev\",\n    \"build:windows\": \"electron-packager . --out=dist --asar --overwrite --icon=build/icon.ico --version-string.CompanyName=zonetti --version-string.FileDescription=zonetti --version-string.ProductName=zonote\",\n    \"build:linux\": \"electron-packager . --out=dist --asar --overwrite --icon=build/icon.png\",\n    \"build:mac\": \"electron-packager . --out=dist --asar --overwrite --icon=build/icon.icns\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/zonetti/zonote.git\"\n  },\n  \"dependencies\": {\n    \"dompurify\": \"^2.2.6\",\n    \"electron-store\": \"^5.2.0\",\n    \"events\": \"^3.2.0\",\n    \"hotkeys-js\": \"^3.8.1\",\n    \"marked\": \"^1.1.0\",\n    \"shortid\": \"^2.2.15\"\n  },\n  \"devDependencies\": {\n    \"electron\": \"^9.0.0\",\n    \"electron-packager\": \"^15.0.0\"\n  }\n}\n"
  },
  {
    "path": "src/main/formatter.js",
    "content": "const NOTE_MIN_WIDTH = 100\nconst NOTE_MIN_HEIGHT = 100\nconst NOTE_MAX_WIDTH = 5000\nconst NOTE_MAX_HEIGHT = 5000\n\nconst isTabs = /^znt-tabs\\|.+/\nconst isNote = /^znt-note\\|\\d+,\\d+,\\d+,\\d+,\\d+/\n\nconst colors = ['default', 'white', 'black', 'primary', 'warning', 'danger']\n\nfunction newNote ({ howManyTabs, t = 0, x = 0, y = 0, w = 500, h = 300, c = 0 }) {\n  const note = {\n    t: t >= 0 ? t : 0,\n    x: x >= 0 ? x : 0,\n    y: y >= 0 ? y : 0,\n    w: w >= NOTE_MIN_WIDTH && w <= NOTE_MAX_WIDTH ? w : NOTE_MIN_WIDTH,\n    h: h >= NOTE_MIN_HEIGHT && h <= NOTE_MAX_HEIGHT ? h : NOTE_MIN_HEIGHT,\n    text: '',\n    color: (c < 0 || c > colors.length - 1) ? colors[0] : colors[c]\n  }\n  if (t > 0 && (!howManyTabs || t > howManyTabs - 1)) note.t = 0\n  return note\n}\n\nfunction fromText (text) {\n  text = text.replace(/\\r/g, '')\n\n  if (typeof text !== 'string') {\n    throw new Error('\"text\" must be a string')\n  }\n\n  let tabs = []\n  const notes = []\n  const lines = text.split('\\n')\n\n  let currentNote = null\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i]\n\n    if (!line && !currentNote) continue\n\n    if (i === 0 && isTabs.test(line)) {\n      tabs = line.split('|').pop().split(',').map(tab => {\n        return tab.length <= 100 ? tab : tab.substring(0, 100)\n      })\n      continue\n    }\n\n    if (isNote.test(line)) {\n      if (currentNote) {\n        notes.push(currentNote)\n        currentNote = null\n      }\n      const [t, x, y, w, h, c] = line.split('|').pop().split(',').map(n => parseInt(n, 10))\n      currentNote = newNote({ howManyTabs: tabs.length, t, x, y, w, h, c })\n      continue\n    }\n\n    if (!currentNote) {\n      currentNote = newNote({ howManyTabs: tabs.length })\n    }\n\n    currentNote.text += line + '\\n'\n  }\n\n  if (currentNote) notes.push(currentNote)\n\n  return { tabs, notes }\n}\n\nfunction toText ({ tabs, notes }) {\n  let text = ''\n\n  if (tabs.length) {\n    text += 'znt-tabs|'\n    tabs.forEach(tab => {\n      text += `${tab},`\n    })\n    text = text.substring(0, text.length - 1) + '\\r\\n'\n  }\n\n  for (let i = 0; i < notes.length; i++) {\n    const note = notes[i]\n    const color = note.color || 'default'\n    note.c = colors.indexOf(color)\n    text += `znt-note|${note.t},${note.x},${note.y},${note.w},${note.h},${note.c}\\r\\n`\n    text += note.text.replace(/\\r/g, '').replace(/\\n/g, '\\r\\n') + '\\r\\n'\n  }\n\n  return text\n}\n\nif (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {\n  module.exports = { fromText, toText }\n} else {\n  window.fromText = fromText\n  window.toText = toText\n}\n"
  },
  {
    "path": "src/main/main.js",
    "content": "const path = require('path')\nconst { app, BrowserWindow, ipcMain, dialog } = require('electron')\nconst stateManager = require('./state_manager')\n\nlet state = stateManager.getInitialState()\n\nfunction close () {\n  if (process.platform === 'darwin') return\n  app.quit()\n}\n\nfunction start () {\n  const minWidth = 800\n  const minHeight = 600\n\n  const browserWindowOptions = {\n    minWidth,\n    minHeight,\n    width: minWidth,\n    height: minHeight,\n    autoHideMenuBar: true,\n    frame: false,\n    webPreferences: {\n      nodeIntegration: true,\n      enableRemoteModule: true\n    }\n  }\n\n  const previousWindowState = stateManager.getWindowState()\n\n  if (previousWindowState) {\n    browserWindowOptions.x = previousWindowState.x\n    browserWindowOptions.y = previousWindowState.y\n    browserWindowOptions.width = previousWindowState.w\n    browserWindowOptions.height = previousWindowState.h\n  }\n\n  const win = new BrowserWindow(browserWindowOptions)\n\n  if (previousWindowState && previousWindowState.isMaximized) {\n    win.maximize()\n  }\n\n  win.on('close', () => {\n    stateManager.saveWindowState({\n      isMaximized: win.isMaximized(),\n      x: win.getPosition()[0],\n      y: win.getPosition()[1],\n      w: win.getSize()[0],\n      h: win.getSize()[1]\n    })\n  })\n\n  ipcMain.on('minimize', () => win.isMinimized() ? win.restore() : win.minimize())\n  ipcMain.on('maximize', () => win.isMaximized() ? win.restore() : win.maximize())\n  ipcMain.on('get state', event => event.reply('update state', state))\n  ipcMain.on('close', close)\n\n  ipcMain.on('update state', (event, guiState) => {\n    state = guiState\n    stateManager.saveState(state)\n  })\n\n  ipcMain.on('new file', event => {\n    state = stateManager.getNewState()\n    event.reply('update state', state)\n  })\n\n  ipcMain.on('open file', (event, filePath) => {\n    try {\n      state = stateManager.getStateFromFile(filePath)\n      event.reply('update state', state)\n    } catch (err) {\n      console.log(err)\n      dialog.showMessageBoxSync({ message: 'Oh no... something is not right' })\n      event.reply('done')\n    }\n  })\n\n  ipcMain.on('save file', (event, savePath) => {\n    try {\n      const filePath = savePath || state.path\n      stateManager.saveStateToFile(filePath, state)\n      event.reply('done', filePath, path.basename(filePath))\n    } catch (err) {\n      if (err.message && err.message.includes('ENOENT')) {\n        delete state.path\n        return event.reply('done', 'save as')\n      }\n      console.log(err)\n      dialog.showMessageBoxSync({ message: 'Oh no... something is not right' })\n      event.reply('done')\n    }\n  })\n\n  win.setMenu(null)\n  win.loadFile(path.join(__dirname, '..', '..', 'static', 'index.html'))\n  if (process.argv.includes('--dev')) {\n    win.webContents.openDevTools()\n  }\n}\n\napp.on('window-all-closed', close)\n\napp.on('activate', () => {\n  if (BrowserWindow.getAllWindows().length !== 0) return\n  start()\n})\n\napp.whenReady().then(start)\n"
  },
  {
    "path": "src/main/state_manager.js",
    "content": "const fs = require('fs')\nconst path = require('path')\nconst Store = require('electron-store')\nconst formatter = require('./formatter')\n\nconst store = new Store()\n\nconst NEW_STATE = {\n  file: 'new file',\n  path: null,\n  isDirty: false,\n  activeTab: 0,\n  tabs: [],\n  notes: []\n}\n\nfunction saveState (state) {\n  store.set('state', state)\n}\n\nfunction getNewState () {\n  return JSON.parse(JSON.stringify(NEW_STATE))\n}\n\nfunction getInitialState () {\n  const state = store.get('state')\n\n  if (!state) return getNewState()\n\n  state.notes = state.notes.map(note => {\n    delete note.id\n    return note\n  })\n\n  if (state.path) {\n    try {\n      fs.readFileSync(state.path)\n    } catch (err) {\n      state.isDirty = true\n    }\n  }\n\n  return state\n}\n\nfunction saveStateToFile (filePath, state) {\n  fs.writeFileSync(filePath, formatter.toText(state))\n}\n\nfunction getStateFromFile (filePath) {\n  const parsed = formatter.fromText(fs.readFileSync(filePath).toString())\n  return {\n    file: path.basename(filePath),\n    path: filePath,\n    isDirty: false,\n    activeTab: 0,\n    tabs: parsed.tabs,\n    notes: parsed.notes\n  }\n}\n\nfunction saveWindowState (data) {\n  store.set('win-state', data)\n}\n\nfunction getWindowState () {\n  return store.get('win-state')\n}\n\nmodule.exports = {\n  saveState,\n  getNewState,\n  getInitialState,\n  saveStateToFile,\n  getStateFromFile,\n  saveWindowState,\n  getWindowState\n}\n"
  },
  {
    "path": "src/renderer/notes.js",
    "content": "const shortid = require('shortid')\nconst marked = require('marked')\nconst DOMPurify = require('dompurify')\n\nmarked.setOptions({\n  breaks: true,\n  gfm: true,\n  smartypants: true\n})\n\nconst contentElm = document.getElementById('content')\nconst editMessageElm = document.getElementById('edit-message')\nconst backMessageElm = document.getElementById('back-message')\nconst removeNoteElm = document.getElementById('remove-note')\nconst colorPickerElm = document.getElementById('color-picker')\n\nfunction generateId () {\n  const id = shortid().replace(/\\W/g, '') + new Date().getTime()\n  return id.split('').sort(() => 0.5 - Math.random()).join('')\n}\n\nlet editMessageTimeout = null\n\nEVENTS.on('render', () => {\n  editMessageElm.style.display = 'none'\n\n  const visibleNotes = STATE.notes.filter(note => note.t === STATE.activeTab)\n  backMessageElm.style.display = visibleNotes.length ? 'none' : 'block'\n  contentElm.style.top = TOP_OFFSET() + 'px'\n\n  document.querySelectorAll('.note').forEach(e => {\n    if (e.isSameNode(GUI_STATE.elementBeingDraggedElm)) return\n    e.remove()\n  })\n\n  STATE.notes.forEach(note => {\n    EVENTS.emit('render note', note)\n  })\n})\n\nfunction enableResizing (noteElm) {\n  let resizeStartX, resizeStartY, resizeStartWidth, resizeStartHeight\n\n  function resize (resizerClass) {\n    return function (event) {\n      if (['resizer-right', 'resizer-both'].includes(resizerClass)) {\n        let resizeAmount = resizeStartWidth + event.pageX - resizeStartX\n        if (resizeAmount < NOTE_MIN_WIDTH) {\n          resizeAmount = NOTE_MIN_WIDTH\n        }\n        GET_NOTE_BY_ID(GUI_STATE.elementBeingResizedElm.dataset.id).w = resizeAmount\n        GUI_STATE.elementBeingResizedElm.style.width = resizeAmount + 'px'\n      }\n      if (['resizer-bottom', 'resizer-both'].includes(resizerClass)) {\n        let resizeAmount = resizeStartHeight + event.pageY - resizeStartY\n        if (resizeAmount < NOTE_MIN_HEIGHT) {\n          resizeAmount = NOTE_MIN_HEIGHT\n        }\n        GET_NOTE_BY_ID(GUI_STATE.elementBeingResizedElm.dataset.id).h = resizeAmount\n        GUI_STATE.elementBeingResizedElm.style.height = resizeAmount + 'px'\n      }\n    }\n  }\n  \n  function stopResizing () {\n    GUI_STATE.isResizing = false\n    UTIL.removeClass(GUI_STATE.elementBeingResizedElm, 'selected')\n    GUI_STATE.elementBeingResizedElm = null\n    document.documentElement.onmousemove = null\n    document.documentElement.onmouseup = null\n    EVENTS.emit('touch state')\n    EVENTS.emit('render')\n  }\n  \n  function startResizing (event) {\n    EVENTS.emit('hide menu')\n    event.stopPropagation()\n    GUI_STATE.isResizing = true\n    GUI_STATE.elementBeingResizedElm = this.parentNote\n    resizeStartX = event.pageX\n    resizeStartWidth = parseInt(document.defaultView.getComputedStyle(GUI_STATE.elementBeingResizedElm).width, 10)\n    resizeStartY = event.pageY\n    resizeStartHeight = parseInt(document.defaultView.getComputedStyle(GUI_STATE.elementBeingResizedElm).height, 10)\n    UTIL.addClass(GUI_STATE.elementBeingResizedElm, 'selected')\n    document.documentElement.onmousemove = resize(this.className)\n    document.documentElement.onmouseup = stopResizing\n  }\n\n  const rightElm = document.createElement('div')\n  rightElm.className = 'resizer-right'\n  noteElm.appendChild(rightElm)\n  rightElm.onmousedown = startResizing\n  rightElm.parentNote = noteElm\n\n  const bottomElm = document.createElement('div')\n  bottomElm.className = 'resizer-bottom'\n  noteElm.appendChild(bottomElm)\n  bottomElm.onmousedown = startResizing\n  bottomElm.parentNote = noteElm\n  \n  const bothElm = document.createElement('div')\n  bothElm.className = 'resizer-both'\n  noteElm.appendChild(bothElm)\n  bothElm.onmousedown = startResizing\n  bothElm.parentNote = noteElm\n}\n\nfunction enableDragging (noteElm) {\n  const draggingMouseDifference = [0, 0]\n  \n  function dragElement (event) {\n    if (!GUI_STATE.elementBeingDraggedElm)  return\n    event = event || window.event\n    let noteNewPosition = [\n      event.pageX + draggingMouseDifference[0],\n      event.pageY - TOP_OFFSET() + draggingMouseDifference[1]\n    ]\n    if (noteNewPosition[0] <= CANVAS_PADDING()) {\n      noteNewPosition[0] = CANVAS_PADDING()\n    }\n    if (noteNewPosition[1] <= CANVAS_PADDING()) {\n      noteNewPosition[1] = CANVAS_PADDING()\n    }\n    const note = GET_NOTE_BY_ID(GUI_STATE.elementBeingDraggedElm.dataset.id)\n    note.x = noteNewPosition[0] - CANVAS_PADDING()\n    note.y = noteNewPosition[1] - CANVAS_PADDING()\n    GUI_STATE.elementBeingDraggedElm.style.left = noteNewPosition[0] + 'px'\n    GUI_STATE.elementBeingDraggedElm.style.top = noteNewPosition[1] + 'px'\n    UTIL.addClass(GUI_STATE.elementBeingDraggedElm, 'selected')\n    if (STATE.isInRemoveNoteArea) {\n      UTIL.addClass(GUI_STATE.elementBeingDraggedElm, 'remove')\n    } else {\n      UTIL.removeClass(GUI_STATE.elementBeingDraggedElm, 'remove')\n    }\n  }\n  \n  function stopDragging () {\n    if (STATE.isInRemoveNoteArea) {\n      const confirmationDialogResponse = DIALOG.showMessageBoxSync({\n        message: 'Are you sure you want to remove this note?',\n        buttons: ['Yes', 'No']\n      })\n      const removeConfirmed = confirmationDialogResponse === 0\n      if (removeConfirmed) {\n        const noteIdToRemove = GUI_STATE.elementBeingDraggedElm.dataset.id\n        let noteIndex = 0\n        for (let i = 0; i < STATE.notes.length; i++) {\n          if (STATE.notes[i].id !== noteIdToRemove) continue\n          noteIndex = i\n          break\n        }\n        STATE.notes.splice(noteIndex, 1)\n        GUI_STATE.elementBeingDraggedElm.remove()\n        EVENTS.emit('render')\n      }\n    }\n    UTIL.removeClasses(GUI_STATE.elementBeingDraggedElm, ['selected', 'remove'])\n    GUI_STATE.isDragging = false\n    removeNoteElm.style.opacity = 0\n    GUI_STATE.elementBeingDraggedElm = null\n    document.onmouseup = null\n    document.onmousemove = null\n    EVENTS.emit('touch state')\n    EVENTS.emit('render')\n  }\n\n  function startDragging (event) {\n    EVENTS.emit('hide menu')\n    GUI_STATE.elementBeingDraggedElm = this\n    GUI_STATE.elementBeingDraggedElm.style.zIndex = NEXT_ZINDEX()\n    removeNoteElm.style.zIndex = NEXT_ZINDEX()\n    event = event || window.event\n    draggingMouseDifference[0] = GUI_STATE.elementBeingDraggedElm.offsetLeft - event.pageX\n    draggingMouseDifference[1] = GUI_STATE.elementBeingDraggedElm.offsetTop - (event.pageY - TOP_OFFSET())\n    GUI_STATE.isDragging = true\n    UTIL.addClass(GUI_STATE.elementBeingDraggedElm, 'selected')\n    removeNoteElm.style.opacity = 1\n    removeNoteElm.style.top = (TOP_OFFSET() + removeNoteElm.offsetHeight) + 'px'\n    document.onmouseup = stopDragging\n    document.onmousemove = dragElement\n  }\n\n  noteElm.onmousedown = startDragging\n}\n\nEVENTS.on('render note text', (note, noteElm) => {\n  const noteTextElm = document.createElement('div')\n  noteTextElm.className = 'text'\n  noteTextElm.innerHTML = DOMPurify.sanitize(marked(note.text))\n  noteTextElm.onclick = event => {\n    EVENTS.emit('hide menu')\n    event.stopPropagation()\n  }\n  noteTextElm.ondblclick = () => {\n    EVENTS.emit('hide menu')\n    note.isEditing = true\n    editMessageElm.style.display = 'none'\n    EVENTS.emit('render note', note)\n  }\n  noteTextElm.onmouseenter = function () {\n    this.style.userSelect = 'text'\n    editMessageElm.style.display = 'block'\n    editMessageElm.style.top = (noteElm.offsetTop + noteElm.offsetHeight - 15) + 'px'\n    editMessageElm.style.left = (noteElm.offsetLeft + (noteElm.offsetWidth / 2) - (editMessageElm.offsetWidth / 2)) + 'px'\n    editMessageElm.style.zIndex = '' + (parseInt(noteElm.style.zIndex, 10) + 5)\n    clearTimeout(editMessageTimeout)\n    editMessageTimeout = setTimeout(() => {\n      editMessageElm.style.display = 'none'\n    }, 1000)\n  }\n  noteTextElm.onmouseleave = function () {\n    this.style.userSelect = 'none'\n    editMessageElm.style.display = 'none'\n  }\n  noteTextElm.onmousedown = event => event.stopPropagation()\n  noteTextElm.onmouseout = event => event.stopPropagation()\n  noteTextElm.querySelectorAll('a').forEach(linkElm => {\n    linkElm.onclick = event => {\n      event.preventDefault()\n      require('electron').shell.openExternal(event.target.href)\n    }\n  })\n  noteElm.appendChild(noteTextElm)\n})\n\nEVENTS.on('render note', note => {\n  if (!note.id) {\n    note.id = generateId()\n  }\n\n  let noteElm = document.querySelector(`.note[data-id=\"${note.id}\"]`)\n\n  if (noteElm && !noteElm.isSameNode(GUI_STATE.elementBeingDraggedElm)) {\n    noteElm.remove()\n    noteElm = null\n  }\n\n  if (note.t !== STATE.activeTab) return\n\n  if (!noteElm) {\n    noteElm = document.createElement('div')\n  }\n\n  noteElm.dataset.id = note.id\n  noteElm.dataset.tab = note.t\n  noteElm.className = 'note ' + (note.color || 'default')\n  noteElm.style.display = note.t === STATE.activeTab ? 'block' : 'none'\n  noteElm.style.top = note.y + CANVAS_PADDING() + 'px'\n  noteElm.style.left = note.x + CANVAS_PADDING() + 'px'\n  noteElm.style.width = note.w + 'px'\n  noteElm.style.height = note.h + 'px'\n  noteElm.style.zIndex = NEXT_ZINDEX()\n  \n  enableResizing(noteElm)\n  enableDragging(noteElm)\n\n  noteElm.ondblclick = event => event.stopPropagation()\n  noteElm.onmouseenter = function (event) {\n    if (GUI_STATE.isDragging || GUI_STATE.isResizing) return\n    this.style.zIndex = NEXT_ZINDEX()\n  }\n\n  if (note.isEditing) {\n    const noteTextareaElm = document.createElement('textarea')\n    noteTextareaElm.onblur = function () {\n      delete note.isEditing\n      this.remove()\n      EVENTS.emit('render note text', note, noteElm)\n    }\n    noteTextareaElm.onclick = event => event.stopPropagation()\n    noteTextareaElm.onmousedown = event => event.stopPropagation()\n    noteTextareaElm.onkeydown = function (event) {\n      if (event.keyCode === KEY_ESC) return EVENTS.emit('render note', note)\n      if (event.keyCode === KEY_TAB) {\n        event.preventDefault()\n        const selectionStart = this.selectionStart\n        this.value = this.value.substring(0, selectionStart) + '  ' + this.value.substring(this.selectionEnd)\n        this.selectionEnd = selectionStart + 2\n      }\n      note.text = this.value\n      EVENTS.emit('touch state')\n    }\n    noteTextareaElm.oninput = function (event) {\n      note.text = this.value\n      EVENTS.emit('touch state')\n    }\n    noteElm.appendChild(noteTextareaElm)\n    setTimeout(() => {\n      noteTextareaElm.value = note.text\n      noteTextareaElm.focus()\n    }, 0)\n  } else if (!noteElm.isSameNode(GUI_STATE.elementBeingDraggedElm)) {\n    EVENTS.emit('render note text', note, noteElm)\n  }\n\n  noteElm.addEventListener('contextmenu', e => {\n    e.preventDefault()\n    colorPickerElm.style.display = 'block'\n    colorPickerElm.style.top = (e.pageY - 15) + 'px'\n    colorPickerElm.style.left = (e.pageX - 15) + 'px'\n    colorPickerElm.style.zIndex = NEXT_ZINDEX()\n    GUI_STATE.noteRightClicked = note\n  })\n\n  contentElm.appendChild(noteElm)\n})\n\nremoveNoteElm.onmouseenter = () => { STATE.isInRemoveNoteArea = true }\nremoveNoteElm.onmouseleave = () => { STATE.isInRemoveNoteArea = false }\n\nfunction hideColorPicker () {\n  colorPickerElm.style.display = 'none'\n  GUI_STATE.noteRightClicked = null\n}\n\ncolorPickerElm.onmouseleave = hideColorPicker\n\ncolorPickerElm.querySelectorAll('span').forEach(spanElm => {\n  spanElm.onclick = e => {\n    e.preventDefault()\n    GUI_STATE.noteRightClicked.color = spanElm.className.split(' ')[0]\n    EVENTS.emit('touch state')\n    hideColorPicker()\n  }\n})\n\ncontentElm.ondblclick = event => {\n  const newNote = {\n    t: STATE.activeTab,\n    x: event.pageX - CANVAS_PADDING(),\n    y: event.pageY - TOP_OFFSET() - CANVAS_PADDING(),\n    w: 200,\n    h: 200,\n    text: '',\n    isEditing: true,\n    color: 'default'\n  }\n  if (newNote.x < 0) newNote.x = 0\n  if (newNote.y < 0) newNote.y = 0\n  STATE.notes.push(newNote)\n  EVENTS.emit('touch state')\n  EVENTS.emit('render')\n}\n"
  },
  {
    "path": "src/renderer/script.js",
    "content": "const EventEmitter = require('events')\nconst electron = require('electron')\n\nwindow.STATE = {}\nwindow.GUI_STATE = { zIndex: 1 }\nwindow.EVENTS = new EventEmitter()\nwindow.DIALOG = electron.remote.dialog\nwindow.IPC = electron.ipcRenderer\nwindow.NEXT_ZINDEX = () => '' + ++GUI_STATE.zIndex\n\n// REFACTOR ->\nwindow.CANVAS_PADDING = () => 20\nwindow.TOP_OFFSET = () => {\n  const topBarElm = document.getElementById('top')\n  const tabsElm = document.getElementById('tabs')\n  return topBarElm.offsetHeight + tabsElm.offsetHeight\n}\nwindow.ALLOWED_EXTENSIONS = ['znt', 'txt']\nwindow.KEY_ESC = 27\nwindow.KEY_TAB = 9\nwindow.NOTE_MIN_HEIGHT = 100\nwindow.NOTE_MIN_WIDTH = 100\nwindow.GET_NOTE_BY_ID = id => {\n  for (let i = 0; i < STATE.notes.length; i++) {\n    if (STATE.notes[i].id === id) return STATE.notes[i]\n  }\n  return null\n}\n// <- REFACTOR\n\nrequire('../src/renderer/util')\nrequire('../src/renderer/top_menu')\nrequire('../src/renderer/tabs')\nrequire('../src/renderer/notes')\n\ndocument.body.onclick = () => EVENTS.emit('lose focus')\ndocument.body.onresize = () => EVENTS.emit('render tab scroll')\n\nEVENTS.on('render', () => IPC.send('update state', STATE))\n\nIPC.on('update state', (event, newState) => {\n  STATE = newState\n  EVENTS.emit('render')\n})\n\nIPC.send('get state')\n"
  },
  {
    "path": "src/renderer/tabs.js",
    "content": "const hotkeys = require('hotkeys-js')\n\nconst tabsElm = document.getElementById('tabs')\nconst tabScrollLeftElm = document.getElementById('tab-scroll-left')\nconst tabScrollRightElm = document.getElementById('tab-scroll-right')\nconst removeNoteElm = document.getElementById('remove-note')\n\nEVENTS.on('toggle tabs', () => {\n  EVENTS.emit('hide menu')\n\n  if (STATE.tabs.length > 0) {\n    const tabsWithNotes = STATE.notes\n      .map(note => note.t)\n      .filter((value, index, self) => self.indexOf(value) === index)\n\n    if (tabsWithNotes.length === 1) {\n      STATE.notes = STATE.notes.map(note => ({ ...note, t: 0 }))\n    } else if (tabsWithNotes.length > 1) {\n      const chosenAction = DIALOG.showMessageBoxSync({\n        message: 'All your existing notes will be merged into one canvas.\\nAre you sure you want to proceed?',\n        buttons: ['Yes', 'No']\n      })\n      if (chosenAction === 1) return\n      STATE.notes = STATE.notes.map(note => ({ ...note, t: 0 }))\n    }\n    STATE.tabs = []\n  } else {\n    STATE.tabs = ['new tab']\n    EVENTS.emit('touch state')\n  }\n\n  STATE.activeTab = 0\n  EVENTS.emit('touch state')\n  EVENTS.emit('render')\n})\n\nEVENTS.on('render tab scroll', () => {\n  const newTabButtonElm = document.getElementById('new-tab-button')\n  const showLeft = tabsElm.scrollLeft > 0\n  const scrollRight = tabsElm.scrollWidth - tabsElm.scrollLeft - window.innerWidth > 0\n  tabScrollLeftElm.style.display = showLeft ? 'block' : 'none'\n  tabScrollRightElm.style.display = scrollRight ? 'block' : 'none'\n})\n\nEVENTS.on('render', () => {\n  tabsElm.innerText = ''\n  tabsElm.style.display = STATE.tabs.length > 0 ? 'inline-flex' : 'none'\n  tabScrollLeftElm.style.display = 'none'\n  tabScrollRightElm.style.display = 'none'\n\n  if (!STATE.tabs.length) return\n\n  let tabBeingDragged = null\n\n  STATE.tabs.forEach((tabText, tabIndex) => {\n    const isActiveTab = STATE.activeTab === tabIndex\n\n    const tabElm = document.createElement('li')\n    tabElm.dataset.index = '' + tabIndex\n    tabElm.draggable = 'true'\n\n    tabElm.ondrag = function (event) {\n      tabBeingDragged = this\n    }\n\n    tabElm.ondragover = event => event.preventDefault()\n\n    tabElm.ondragenter = function (event) {\n      if (this.isSameNode(tabBeingDragged)) return\n      this.style.backgroundColor = '#4ECDC4'\n      this.style.color = '#FFF'\n    }\n\n    tabElm.ondragleave = function (event) {\n      if (this.isSameNode(tabBeingDragged)) return\n      if (this.contains(document.elementFromPoint(event.pageX, event.pageY))) return\n      this.style.backgroundColor = ''\n      this.style.color = ''\n    }\n\n    tabElm.ondrop = function (event) {\n      event.preventDefault()\n\n      this.style.backgroundColor = ''\n      const fromIndex = parseInt(tabBeingDragged.dataset.index, 10)\n      const toIndex = parseInt(this.dataset.index, 10)\n\n      STATE.notes = STATE.notes.map(note => {\n        if (![fromIndex, toIndex].includes(note.t)) return note\n        note.t = note.t === fromIndex ? toIndex : fromIndex\n        return note\n      })\n\n      const tempTab = STATE.tabs[fromIndex]\n      STATE.tabs[fromIndex] = STATE.tabs[toIndex]\n      STATE.tabs[toIndex] = tempTab\n      tabBeingDragged = null\n\n      if (STATE.activeTab === fromIndex) {\n        STATE.activeTab = toIndex\n      }\n\n      EVENTS.emit('render')\n    }\n\n    const spanElm = document.createElement('span')\n    spanElm.ondragover = event => event.preventDefault()\n    spanElm.innerText = tabText\n\n    if (isActiveTab) {\n      spanElm.contentEditable = 'true'\n      spanElm.spellcheck = false\n    }\n\n    const removeButtonElm = document.createElement('button')\n    removeButtonElm.ondragover = event => event.preventDefault()\n    removeButtonElm.type = 'button'\n    removeButtonElm.style.opacity = isActiveTab ? 1 : 0\n    removeButtonElm.innerText = '𝗑'\n    removeButtonElm.onclick = event => {\n      event.stopPropagation()\n      EVENTS.emit('remove tab', tabIndex)\n    }\n\n    tabElm.append(spanElm)\n    tabElm.append(removeButtonElm)\n\n    if (isActiveTab) {\n      tabElm.className = 'active'\n      tabElm.onclick = event => event.stopPropagation()\n      spanElm.oninput = function () {\n        EVENTS.emit('touch state')\n        if (this.innerText.length <= 100) return\n        this.innerText = this.innerText.substring(0, 100)\n      }\n      spanElm.onblur = function () {\n        const value = this.innerText\n          .replace(/&nbsp;/g, ' ')\n          .replace(/[,|]/g, '')\n          .replace(/\\s+/g, ' ')\n          .trim()\n        this.innerText = value || 'new tab'\n        STATE.tabs[tabIndex] = this.innerText\n      }\n    } else {\n      tabElm.style.userSelect = 'none'\n      tabElm.onclick = () => {\n        STATE.activeTab = tabIndex\n        EVENTS.emit('render')\n      }\n      tabElm.onmouseenter = () => {\n        removeButtonElm.style.opacity = 1\n        if (GUI_STATE.isDragging) {\n          STATE.activeTab = tabIndex\n          GET_NOTE_BY_ID(GUI_STATE.elementBeingDraggedElm.dataset.id).t = tabIndex\n          EVENTS.emit('render')\n          GUI_STATE.elementBeingDraggedElm.style.zIndex = NEXT_ZINDEX()\n          removeNoteElm.style.zIndex = NEXT_ZINDEX()\n        }\n      }\n      tabElm.onmouseleave = () => {\n        removeButtonElm.style.opacity = 0\n      }\n    }\n    tabsElm.append(tabElm)\n  })\n\n  const newTabButtonElm = document.createElement('li')\n  newTabButtonElm.id = 'new-tab-button'\n  newTabButtonElm.innerText = '+'\n  newTabButtonElm.onmouseenter = function () { this.innerText = '+ new tab' }\n  newTabButtonElm.onmouseleave = function () { this.innerText = '+' }\n  newTabButtonElm.onclick = () => {\n    STATE.tabs.push('new tab')\n    STATE.activeTab = STATE.tabs.length - 1\n    EVENTS.emit('touch state')\n    EVENTS.emit('render')\n  }\n\n  tabsElm.append(newTabButtonElm)\n\n  EVENTS.emit('render tab scroll')\n})\n\nEVENTS.on('new tab', () => {\n  STATE.tabs.push('new tab')\n  STATE.activeTab = STATE.tabs.length - 1\n  EVENTS.emit('render')\n  EVENTS.emit('touch state')\n})\n\nEVENTS.on('remove tab', tabIndex => {\n  EVENTS.emit('touch state')\n  if (STATE.tabs.length === 1) {\n    STATE.tabs = []\n    return EVENTS.emit('render')\n  }\n  if (STATE.notes.filter(note => note.t === tabIndex).length) {\n    const confirmationDialogResponse = DIALOG.showMessageBoxSync({\n      message: 'If you remove this tab all your notes inside of it will be removed as well.\\nAre you sure you want to proceed?',\n      buttons: ['Yes', 'No']\n    })\n    const removeConfirmed = confirmationDialogResponse === 0\n    if (!removeConfirmed) return\n    document.querySelectorAll(`.note[data-tab=\"${tabIndex}\"]`).forEach(noteElm => noteElm.remove())\n    STATE.notes = STATE.notes.filter(note => note.t !== tabIndex)\n  }\n  STATE.notes.filter(note => note.t > tabIndex).forEach(note => { note.t-- })\n  STATE.tabs.splice(tabIndex, 1)\n  if (STATE.activeTab > 0 && STATE.activeTab >= tabIndex) {\n    STATE.activeTab--\n  }\n  EVENTS.emit('render')\n})\n\nEVENTS.on('switch tab', direction => {\n  const tabsLength = STATE.tabs.length\n  if (tabsLength === 0 || tabsLength === 1) return\n  let newActiveTab = STATE.activeTab\n  if (direction === 'next') {\n    newActiveTab++\n    if (newActiveTab === tabsLength) {\n      newActiveTab = 0\n    }\n  } else {\n    newActiveTab--\n    if (newActiveTab < 0) {\n      newActiveTab = tabsLength - 1\n    }\n  }\n  STATE.activeTab = newActiveTab\n  EVENTS.emit('render')\n})\n\ntabsElm.onwheel = event => tabsElm.scrollLeft += event.deltaY\ntabsElm.onscroll = () => EVENTS.emit('render tab scroll')\n\nlet scrollTabsInterval = null\n\nfunction scrollTabs (amount) {\n  tabsElm.scrollLeft += amount\n}\n\ntabScrollLeftElm.onmouseenter = () => scrollTabsInterval = setInterval(() => scrollTabs(-10), 50)\ntabScrollLeftElm.onmouseleave = () => clearInterval(scrollTabsInterval)\ntabScrollRightElm.onmouseenter = () => scrollTabsInterval = setInterval(() => scrollTabs(10), 50)\ntabScrollRightElm.onmouseleave = () => clearInterval(scrollTabsInterval)\n\nhotkeys('ctrl+t,command+t', () => EVENTS.emit('new tab'))\nhotkeys('ctrl+w,command+w', () => {\n  if (!STATE.tabs.length) return\n  EVENTS.emit('remove tab', STATE.activeTab)\n})\nhotkeys('ctrl+tab,command+tab', () => EVENTS.emit('switch tab', 'next'))\nhotkeys('ctrl+shift+tab,command+shift+tab', () => EVENTS.emit('switch tab', 'previous'))\n"
  },
  {
    "path": "src/renderer/top_menu.js",
    "content": "const hotkeys = require('hotkeys-js')\n\nconst menuElm = document.getElementById('menu')\nconst menuButtonElm = document.getElementById('menu-button')\nconst topButtonCloseAppElm = document.getElementById('close')\nconst fileElm = document.getElementById('file')\n\nfunction createMenuButton (text, key) {\n  const shortcut = process.platform === 'darwin' ? 'command' : 'ctrl'\n  const buttonElm = document.createElement('li')\n  buttonElm.innerText = text\n  const span = document.createElement('span')\n  span.innerText = `${shortcut} + ${key}`\n  buttonElm.appendChild(span)\n  return buttonElm\n}\n\nconst menuButtonNewElm = createMenuButton('new', 'n')\nconst menuButtonOpenElm = createMenuButton('open...', 'o')\nconst menuButtonSaveElm = createMenuButton('save', 's')\n\nconst menuButtonTabsElm = document.createElement('li')\nmenuButtonTabsElm.id = 'tabs-menu-button'\n\nconst menuButtonExitElm = document.createElement('li')\nmenuButtonExitElm.innerText = 'exit'\n\nmenuElm.appendChild(menuButtonNewElm)\nmenuElm.appendChild(menuButtonOpenElm)\nmenuElm.appendChild(menuButtonSaveElm)\nmenuElm.appendChild(menuButtonTabsElm)\nmenuElm.appendChild(menuButtonExitElm)\n\nEVENTS.on('render', () => {\n  document.title = STATE.path || 'zonote'\n  fileElm.innerText = `${STATE.isDirty ? '*' : ''}${STATE.file}`\n  menuButtonTabsElm.innerText = STATE.tabs.length > 0 ? 'disable tabs' : 'enable tabs'\n})\n\nEVENTS.on('hide menu', () => {\n  menuElm.style.display = 'none'\n  menuButtonElm.className = ''\n})\n\nEVENTS.on('toggle menu', () => {\n  if (menuButtonElm.className === 'open') return EVENTS.emit('hide menu')\n  menuElm.style.display = 'block'\n  menuButtonElm.className = 'open'\n  menuElm.style.zIndex = 999 + NEXT_ZINDEX()\n})\n\nEVENTS.on('touch state', () => {\n  STATE.isDirty = true\n  fileElm.innerText = `*${STATE.file}`\n})\n\nEVENTS.on('lose focus', () => {\n  EVENTS.emit('hide menu')\n  EVENTS.emit('render')\n})\n\nasync function handleCurrentState () {\n  EVENTS.emit('hide menu')\n  if (!STATE.isDirty) return 'continue'\n  const chosenAction = DIALOG.showMessageBoxSync({\n    message: 'You have unsaved changes in your current file.\\nWhat do you want to do?',\n    buttons: ['Save changes', 'Discard changes', 'Cancel']\n  })\n  if (chosenAction === 0) return EVENTS.emit('save file')\n  if (chosenAction === 1) return 'continue'\n  if (chosenAction === 2) return 'cancel'\n}\n\nEVENTS.on('new file', async () => {\n  const action = await handleCurrentState()\n  if (action === 'cancel') return\n  IPC.send('new file')\n})\n\nEVENTS.on('open file', async () => {\n  const action = await handleCurrentState()\n  if (action === 'cancel') return\n  let filePath = DIALOG.showOpenDialogSync({\n    filters: [ { name: 'zonote files', extensions: ALLOWED_EXTENSIONS } ]\n  })\n  if (!filePath) return\n  filePath = filePath[0]\n  IPC.send('open file', filePath)\n})\n\nEVENTS.on('save file', async () => {\n  EVENTS.emit('hide menu')\n\n  if (!STATE.isDirty) return\n\n  if (STATE.path) {\n    await new Promise(resolve => {\n      IPC.once('done', (event, filePath) => {\n        if (filePath === 'save as') {\n          delete STATE.path\n          EVENTS.emit('save file')\n          return resolve()\n        }\n        if (filePath) {\n          STATE.path = filePath\n          STATE.isDirty = false\n        }\n        resolve()\n      })\n      IPC.send('save file')\n    })\n\n    return EVENTS.emit('render')\n  }\n\n  const savePath = DIALOG.showSaveDialogSync({\n    filters: [ { name: 'zonote files', extensions: ALLOWED_EXTENSIONS } ],\n    properties: [ 'createDirectory' ]\n  })\n\n  if (!savePath) return\n\n  await new Promise(resolve => {\n    IPC.once('done', (event, filePath, fileName) => {\n      if (filePath) {\n        STATE.file = fileName\n        STATE.path = filePath\n        STATE.isDirty = false\n      }\n      resolve()\n    })\n    IPC.send('save file', savePath)\n  })\n\n  EVENTS.emit('render')\n})\n\nEVENTS.on('close', () => {\n  EVENTS.emit('hide menu')\n  IPC.send('close', STATE)\n})\n\nmenuElm.onclick = event => event.stopPropagation()\n\nmenuButtonElm.onclick = event => {\n  event.stopPropagation()\n  EVENTS.emit('toggle menu')\n}\n\nmenuButtonNewElm.onclick =  () => EVENTS.emit('new file')\nmenuButtonOpenElm.onclick =  () => EVENTS.emit('open file')\nmenuButtonSaveElm.onclick =  () => EVENTS.emit('save file')\nmenuButtonTabsElm.onclick = () => EVENTS.emit('toggle tabs')\nmenuButtonExitElm.onclick =  () => EVENTS.emit('close')\ntopButtonCloseAppElm.onclick =  () => EVENTS.emit('close')\n\nhotkeys('esc', () => EVENTS.emit('lose focus'))\nhotkeys('ctrl+n,command+n', () => EVENTS.emit('new file'))\nhotkeys('ctrl+o,command+o', () => EVENTS.emit('open file'))\nhotkeys('ctrl+s,command+s', () => EVENTS.emit('save file'))\n"
  },
  {
    "path": "src/renderer/util.js",
    "content": "window.UTIL = {}\n\nwindow.UTIL.addClass = (elm, value) => {\n  const classes = elm.className.split(' ')\n  if (classes.includes(value)) return\n  elm.className = classes.concat(value).join(' ')\n}\n\nwindow.UTIL.removeClass = (elm, value) => {\n  const classes = elm.className.split(' ')\n  if (!classes.includes(value)) return\n  classes.splice(classes.indexOf(value), 1)\n  elm.className = classes.join(' ')\n}\n\nwindow.UTIL.addClasses = (elm, values) => values.forEach(v => UTIL.addClass(elm, v))\nwindow.UTIL.removeClasses = (elm, values) => values.forEach(v => UTIL.removeClass(elm, v))\n"
  },
  {
    "path": "static/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <title>zonote</title>\n  <meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'self' 'unsafe-inline';\"/>\n  <link href=\"style.css\" rel=\"stylesheet\" type=\"text/css\"/>\n</head>\n<body>\n\n  <div id=\"top\">\n    <div id=\"menu-button\">\n      <span></span>\n      <span></span>\n      <span></span>\n      <span></span>\n    </div>\n    <div id=\"file\"></div>\n    <button id=\"close\" type=\"button\">𝗑</button>\n  </div>\n\n  <div id=\"remove-note\">drag here to remove</div>\n\n  <ul id=\"tabs\"></ul>\n  <button id=\"tab-scroll-left\" class=\"tab-scroll\" type=\"button\">&laquo;</button>\n  <button id=\"tab-scroll-right\" class=\"tab-scroll\" type=\"button\">&raquo;</button>\n\n  <div id=\"content\">\n    <p id=\"edit-message\">double click to edit</p>\n    <p id=\"back-message\">double click to create a new note</p>\n    <div id=\"color-picker\">\n      <span class=\"default mb-5 mr-5\"></span>\n      <span class=\"white mb-5 mr-5\"></span>\n      <span class=\"black mb-5\"></span>\n      <span class=\"primary mr-5\"></span>\n      <span class=\"warning mr-5\"></span>\n      <span class=\"danger\"></span>\n    </div>\n  </div>\n\n  <ul id=\"menu\"></ul>\n\n  <script src=\"../src/renderer/script.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "static/style.css",
    "content": "@import url('https://fonts.googleapis.com/css?family=Merriweather:300');\n\n* {\n  margin: 0;\n  padding: 0;\n}\n\n:focus {\n  outline: 0;\n}\n\nbody {\n  font-size: 16px;\n  font-family: \"Merriweather\", \"PT Serif\", Georgia, \"Times New Roman\", \"STSong\", Serif;\n}\n\n#top {\n  position: fixed;\n  top: 0;\n  left: 0;\n  width: 100%;\n  -webkit-app-region: drag;\n  height: 60px;\n  background: #556270;\n  border: 1px solid #000;\n  box-sizing: border-box;\n}\n\n#remove-note {\n  position: fixed;\n  left: 50%;\n  list-style-type: none;\n  font-size: 13px;\n  line-height: 26px;\n  z-index: 100;\n  -ms-transform: translate(-50%, -50%);\n  transform: translate(-50%, -50%);\n  border: 2px dashed #FF6B6B;\n  background: #FAFAFA;\n  color: #FF6B6B;\n  border-radius: 5px;\n  padding: 2px 10px;\n  transition: opacity .2s;\n  opacity: 0;\n}\n\n#remove-note:hover {\n  border: 2px solid #FF6B6B;\n}\n\n#tabs {\n  position: fixed;\n  box-sizing: border-box;\n  top: 60px;\n  left: 0;\n  background: #E5E5E5;\n  width: 100%;\n  border: 1px solid #000;\n  border-top: none;\n  border-bottom: none;\n  z-index: 100;\n  list-style-type: none;\n  overflow: hidden;\n  display: none;\n}\n\n#tabs li {\n  display: inline-flex;\n  padding: 10px;\n  line-height: 20px;\n  white-space: nowrap;\n  cursor: pointer;\n  border-bottom: 1px solid #000;\n}\n\n#tabs li:hover {\n  background: #EEE;\n}\n\n#tabs li span {\n  display: flex;\n}\n\n#tabs li button {\n  float: right;\n  opacity: 0;\n  transition: .1s;\n  top: 10px;\n  right: 10px;\n  background: #FF6B6B;\n  text-align: center;\n  font-size: 10px;\n  color: #FFF;\n  height: 20px;\n  width: 20px;\n  border-radius: 10px;\n  border: 1px solid #C44D58;\n  margin-left: 10px;\n  cursor: pointer;\n}\n\n#tabs li button:hover {\n  box-shadow: 1px 1px #333;\n  background: #EE5A5A;\n}\n\n#tabs li#new-tab-button {\n  min-width: 160px;\n  width: 100%;\n}\n\n#tabs li.active {\n  background: #FAFAFA;\n  border-bottom: 1px solid #FAFAFA;\n}\n\n#tabs li.active span {\n  cursor: text;\n}\n\n#tabs li:not(:first-child) {\n  border-left: 1px solid #000;\n}\n\nbutton.tab-scroll {\n  position: fixed;\n  z-index: 999;\n  top: 66px;\n  background: #4ECDC4;\n  border: none;\n  color: #FFF;\n  opacity: 1;\n  padding: 5px 0;\n  width: 30px;\n  font-size: 18px;\n  text-align: center;\n  line-height: 18px;\n  font-weight: bold;\n  text-shadow: 1px 1px #333;\n  display: none;\n}\n\nbutton.tab-scroll:hover {\n  background: #5FDED5;\n  cursor: pointer;\n  opacity: 1;\n}\n\nbutton#tab-scroll-left {\n  border-left: 1px solid #000;\n  border-top-right-radius: 10px;\n  border-bottom-right-radius: 10px;\n  left: 0;\n}\n\nbutton#tab-scroll-left:hover {\n  text-align: left;\n  padding-left: 5px;\n}\n\nbutton#tab-scroll-right {\n  border-right: 1px solid #000;\n  border-top-left-radius: 10px;\n  border-bottom-left-radius: 10px;\n  right: 0;\n}\n\nbutton#tab-scroll-right:hover {\n  text-align: right;\n  padding-right: 5px;\n}\n\n#content {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  top: 60px;\n  width: 100%;\n  background: #FAFAFA;\n  border: 1px solid #000;\n  border-top: none;\n  box-sizing: border-box;\n  padding: 20px;\n  font-size: 1.3em;\n}\n\n#menu-button,\n#menu li,\n#file,\n#close,\n#back-message,\n.note div,\n#tabs button,\n#tabs #new-tab-button,\nbutton.tab-scroll,\n#remove-note,\n#edit-message {\n  user-select: none;\n}\n\n#menu-button, #close, #back-message {\n  -webkit-app-region: no-drag;\n}\n\n#close {\n  position: fixed;\n  top: 15px;\n  right: 20px;\n  background: #FF6B6B;\n  transition: .1s;\n  color: #FFF;\n  height: 30px;\n  width: 30px;\n  border-radius: 15px;\n  font-size: 18px;\n  border: 1px solid #C44D58;\n  cursor: pointer;\n}\n\n#close:hover {\n  box-shadow: 1px 1px #333;\n  background: #EE5A5A;\n}\n\n#menu-button {\n  position: fixed;\n  top: 20px;\n  left: 20px;\n  height: 20px;\n  width: 30px;\n  cursor: pointer;\n}\n\n#file {\n  position: fixed;\n  top: 20px;\n  left: 70px;\n  font-size: 18px;\n  line-height: 20px;\n  color: #FFF;\n  text-shadow: 1px 1px #333;\n}\n\n#menu-button span {\n  display: block;\n  position: absolute;\n  height: 4px;\n  width: 100%;\n  background: #4ECDC4;\n  opacity: 1;\n  left: 0;\n  transform: rotate(0deg);\n  transition: .1s;\n}\n\n#menu-button span:nth-child(1) {\n  top: 0px;\n}\n\n#menu-button span:nth-child(2), #menu-button span:nth-child(3) {\n  top: 8px;\n}\n\n#menu-button span:nth-child(4) {\n  top: 16px;\n}\n\n#menu-button.open span:nth-child(1), #menu-button.open span:nth-child(4) {\n  top: 8px;\n  width: 0%;\n  left: 50%;\n}\n\n#menu-button.open span:nth-child(2) {\n  transform: rotate(45deg);\n}\n\n#menu-button.open span:nth-child(3) {\n  transform: rotate(-45deg);\n}\n\n#edit-message {\n  position: absolute;\n  border: 2px dashed #777;\n  border-radius: 5px;\n  background: #FAFAFA;\n  font-size: 12px;\n  line-height: 14px;\n  padding: 6px 10px;\n  display: none;\n}\n\n#back-message {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  font-size: 14px;\n  border: 2px dashed #888;\n  color: #556270;\n  border-radius: 5px;\n  padding: 6px 10px;\n}\n\n#menu {\n  position: fixed;\n  display: none;\n  top: 59px;\n  left: 0;\n  padding-top: 1px;\n  background: #556270;\n  z-index: 999;\n  border: 1px solid #000;\n  border-top: none;\n  color: #FFF;\n  text-shadow: 1px 1px #333;\n}\n\n#menu li {\n  width: 200px;\n  padding: 20px;\n  list-style-type: none;\n  font-size: 16px;\n  line-height: 16px;\n  cursor: pointer;\n}\n\n#menu li:hover {\n  background: #4ECDC4;\n  text-shadow: none;\n}\n\n#menu li span {\n  float: right;\n  margin-top: 1px;\n  font-size: 12px;\n}\n\n#content {\n  overflow: auto;\n}\n\n*::-webkit-scrollbar {\n\twidth: 10px;\n\theight: 10px;\n  background: #EEE;\n}\n\n*::-webkit-scrollbar-track, *::-webkit-scrollbar-corner {\n\tbackground: #EEE;\n}\n\n*::-webkit-scrollbar-thumb {\n\tbackground: #556270;\t\n}\n\n#color-picker {\n  display: none;\n  position: fixed;\n  background: #EEE;\n  border: 1px solid #777;\n  box-shadow: 2px 2px #BBB;\n  width: 84px;\n  height: 54px;\n  padding: 5px;\n}\n\n#color-picker span {\n  display: inline-block;\n  height: 20px;\n  width: 20px;\n  border: 1px solid #777;\n  padding: 0;\n  margin: 0;\n  cursor: pointer;\n}\n\n#color-picker span.mr-5 { margin-right: 4px; }\n#color-picker span.mb-5 { margin-bottom: 4px; }\n#color-picker span.default { background: #EEE; }\n#color-picker span.white { background: #FAFAFA; }\n#color-picker span.black { background: #222; }\n#color-picker span.primary { background: #4ECDC4; }\n#color-picker span.warning { background: #FEE074; }\n#color-picker span.danger { background: #FF6B6B; }\n\n.note {\n  position: absolute;\n  border: 1px solid #777;\n  box-shadow: 2px 2px #BBB;\n  transition: box-shadow .5s, opacity .5s, background .5s;\n  padding: 20px;\n  min-width: 100px;\n  min-height: 100px;\n  max-width: 2000px;\n  max-height: 2000px;\n  font-size: 0.8em;\n  opacity: 1;\n}\n\n.note.default { background: #EEE; color: #333; }\n.note.white { background: #FAFAFA; color: #333; }\n.note.black { background: #222; color: #FAFAFA; }\n.note.primary { background: #4ECDC4; color: #333; }\n.note.warning { background: #FEE074; color: #333; }\n.note.danger { background: #FF6B6B; color: #333; }\n\n.note.selected {\n  border: 2px dashed #4ECDC4;\n  opacity: 0.9;\n  /* background: #EFEFEF; */\n  box-shadow: none !important;\n}\n\n.note:hover {\n  cursor: move;\n  box-shadow: 4px 4px #CCC;\n}\n\n.note.selected.remove {\n  border: 2px solid #FF6B6B;\n  box-shadow: none !important;\n}\n\n.note textarea {\n  width: 100%;\n  height: 100%;\n  resize: none;\n  background: #EEE;\n  border: 2px dashed #333;\n  color: #333;\n  font-family: monospace;\n  font-size: 1.2em;\n}\n.note.white textarea { background: #FAFAFA; }\n.note.black textarea { background: #222; color: #FAFAFA; border-color: #FAFAFA; }\n.note.primary textarea { background: #4ECDC4; color: #333; border-color: #333; }\n.note.warning textarea { background: #FEE074; color: #333; border-color: #333; }\n.note.danger textarea { background: #FF6B6B; color: #333; border-color: #333; }\n\n.note .resizer-right {\n  width: 5px;\n  height: 100%;\n  background: transparent;\n  position: absolute;\n  right: 0;\n  bottom: 0;\n  cursor: e-resize;\n}\n\n.note .resizer-bottom {\n  width: 100%;\n  height: 5px;\n  background: transparent;\n  position: absolute;\n  right: 0;\n  bottom: 0;\n  cursor: n-resize;\n}\n\n.note .resizer-both {\n  width: 5px;\n  height: 5px;\n  background: transparent;\n  z-index: 10;\n  position: absolute;\n  right: 0;\n  bottom: 0;\n  cursor: nw-resize;\n}\n\n.note .text {\n  height: 100%;\n  width: 100%;\n  overflow: auto;\n}\n\n.note .text:hover {\n  cursor: text;\n}\n\n.note .text *:not(:first-child) {\n  margin-top: 15px;\n}\n\n.note .text hr {\n  border: 0;\n  border-top: 1px solid #000;\n}\n.note.black .text hr { border-color: #FAFAFA }\n\n.note .text hr:not(:first-child) {\n  margin: 20px 0 !important;\n}\n\n.note .text ul,\n.note .text ol {\n  padding-left: 10px;\n  margin-left: 10px;\n}\n\n.note .text li {\n  margin-top: 8px !important;\n}\n\n.note .text h1:not(:first-child),\n.note .text h2:not(:first-child) {\n  padding: 10px 0;\n}\n\n.note .text h3:not(:first-child),\n.note .text h4:not(:first-child) {\n  padding: 5px 0;\n}\n\n.note .text h5:not(:first-child),\n.note .text h6:not(:first-child) {\n  padding: 3px 0;\n}\n\n.note .text h1 {\n  font-size: 2.5em;\n}\n\n.note .text h2 {\n  font-size: 2em;\n}\n\n.note .text h3 {\n  font-size: 1.6em;\n}\n\n.note .text h4 {\n  font-size: 1.3em;\n}\n\n.note .text h5 {\n  font-size: 1.2em;\n}\n\n.note .text h6 {\n  font-size: 1.1em;\n}\n\n.note .text a {\n  color: #4ECDC4;\n  border-bottom: 1px solid #4ECDC4;\n  text-decoration: none;\n}\n.note.danger .text a { color: #FAFAFA; border-color: #EEE; }\n\n.note .text a:visited,\n.note .text a:active,\n.note .text a:hover {\n  color: #3DBCB3;\n  border-bottom: 1px solid #3DBCB3;\n}\n.note.danger .text a:visited,\n.note.danger .text a:active,\n.note.danger .text a:hover { color: #CCC; border-color: #CCC; }\n\n.note .text blockquote {\n  padding: 10px 20px;\n  margin: 0 0 20px;\n  border-left: 5px solid #CCC;\n  width: 90%;\n}\n.note.black .text blockquote { background: #222; color: #FAFAFA; border-color: #FAFAFA; }\n.note.primary .text blockquote { border-color: #FAFAFA; }\n.note.warning .text blockquote { border-color: #333; }\n.note.danger .text blockquote { border-color: #333; }\n\n.note .text code {\n  background: #FAFAFA;\n  border: 1px solid #333;\n  padding: 0 2px;\n  margin: 0 2px;\n}\n.note.white .text code { background: #EEE; }\n.note.black .text code { background: #FAFAFA; color: #222; border-color: #EEE; }\n.note.primary .text code { background: #FAFAFA; }\n.note.warning .text code { background: #FAFAFA; }\n.note.danger .text code { background: #FAFAFA; }\n\n.note .text pre {\n  display: block;\n  padding: 10px;\n  margin: 0 0 10px;\n  width: 95%;\n  color: #333;\n  word-break: break-all;\n  word-wrap: break-word;\n  background: #FAFAFA;\n  border: 1px solid #CCC;\n  border-radius: 4px;\n}\n.note.white .text pre { background: #EEE; }\n.note.black .text pre { background: #FAFAFA; color: #222; }\n.note.primary .text pre { background: #FAFAFA; }\n.note.warning .text pre { background: #FAFAFA; }\n.note.danger .text pre { background: #FAFAFA; }\n\n.note .text pre code {\n  background: #FAFAFA;\n  border: none;\n  padding: 0;\n}\n\n.note .text table {\n  display: table;\n  width: 90%;\n  border-spacing: 0;\n  border-collapse: collapse;\n  border: 1px solid #CCC;\n  background: #FAFAFA;\n}\n.note.white .text table { background: #EEE; }\n.note.black .text table { background: #FAFAFA; color: #333; }\n.note.primary .text table { background: #FAFAFA; color: #333; }\n.note.warning .text table { background: #FAFAFA; color: #333; }\n.note.danger .text table { background: #FAFAFA; color: #333; }\n\n.note .text thead {\n  display: table-header-group;\n  vertical-align: middle;\n  border-color: inherit;\n}\n\n.note .text tr {\n  display: table-row;\n  vertical-align: inherit;\n  border-color: inherit;\n}\n\n.note .text tr th,\n.note .text tr td {\n  vertical-align: bottom;\n  padding: 8px;\n  vertical-align: top;\n  border-top: 1px solid #CCC;\n}\n\n.note .text tr th {\n  border-bottom: 2px solid #CCC;\n}\n\n.note .text img {\n  display: inline-block;\n  max-width: 33%;\n}\n"
  }
]