[
  {
    "path": ".coddx-template",
    "content": "// -------------->> {{fileName}}.tsx\n// created time: {{YYYY}}-{{MM}}-{{DD}} {{HH}}:{{mm}}\nimport * as React from 'react';\nimport './{{fileName}}.css';\n\nexport default class {{fileName}} extends React.Component {\n  render() {\n    return (\n      <div>\n        {/* ...code goes here... */}\n      </div>\n    )\n  }\n}\n\n// -------------->> {{fileName}}.css\n.main {}\n\n// -------------->> __test__/{{fileName}}.test.tsx\nimport * as React from 'react';\n\nit('{{fileName}} - render', () => {\n  // render(<{{fileName}} />);\n  // expect(...);\n});\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig: http://EditorConfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\n\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".gitignore",
    "content": "out\nnode_modules\n.vscode-test/\n*.vsix\n\nconfigViewer/configViewer.js"
  },
  {
    "path": ".prettierrc.yml",
    "content": "---\nprintWidth: 120\ntabWidth: 2\nuseTabs: false\nsemi: true\nsingleQuote: true\ntrailingComma: none\nbracketSpacing: true\njsxBracketSameLine: false\narrowParens: avoid\n"
  },
  {
    "path": ".vscodeignore",
    "content": ".vscode/**\n.vscode-test/**\nout/test/**\nsrc/**\n.gitignore\nvsc-extension-quickstart.md\n**/tsconfig.json\n**/tslint.json\n**/*.map\n**/*.ts"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\n## [0.3.0] - 2021-05-24\n\n- Task title supports markdown now for styling, hyperlinks, simple html or even img tags.\n- New Task Action: move a task to the column on the right.\n\n## [0.2.27] - 2020-03-28\n\n- Task Board - support multiple task lists defined in user's settings.json.\n- Task Board - refresh button to reload file content.\n- Task Board - checkbox is now optional (if task title doesn't have it).\n- Task Board - support sub-task (task title starts with 2-space indentation).\n- Task Board - task menu for: toggling sub-task; inserting emojis;\n- Task Board - respect theme colors.\n\n## [0.2.12] - 2020-03-21\n\n- Task Board - search box\n- Task Board - autofocus when creating a new task.\n- Task Board - checkmark to mark a task as complete.\n- Task Board Doc\n\n## [0.2.3] - 2020-03-15\n\n- Task Board - manage tasks and save them as TODO.md - a simple plain text file. The syntax is compatible with [Github Markdown](https://bit.ly/2wBp1Mk)\n\n## [0.1.4] - 2020-03-10\n\n- Output can be edited before generating files.\n\n## [0.1.3]\n\n- Initial release\n- Follow this format - Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Duc Nguyen\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 all\ncopies 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 THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Todo Kanban Board\n\nTodo Kanban Board manages tasks and save them as [TODO.md](https://bit.ly/2JdEuET) - a simple plain text file.\n\n## Features\n\n- The syntax is compatible with [Github Markdown](https://bit.ly/2wBp1Mk)\n- TODO.md file is portable and can be committed with Pull Requests (PRs) to git repositories.\n- Support custom file name, multiple task lists.\n- Checkboxes are optional (if your task titles don't have them).\n- Task title can also have markdown for styling, hyperlinks, simple html or even img tags.\n- Task menu: to insert a sub-task, emoji icons (like bug 🐞 blocked ❌ party 🎉 etc.).\n- See also: <a href=\"https://bit.ly/2SfcKaH\">Documentation / Guides</a>\n\n## Usage:\n\n- Open Coddx Task Board:\n  - Bring up the Command Palette (F1), type and select: Coddx: Task Board.\n- When interacting with the Task Board, TODO.md will be created or updated automatically.\n- Vice versa, TODO.md can be edited manually, Task Board will load it every time (click the Refresh icon).\n\n<img src=\"docs/media/task-board-demo.gif\" />\n\n<img src=\"docs/media/task-board.png\" />\n\n<hr />\n\n## Support\n\n- For Feedbacks, Bug Reports: https://github.com/coddx-hq/coddx-alpha/issues\n- Documentation: <a href=\"https://bit.ly/2SfcKaH\">Documentation / Guides</a>\n- <a href=\"https://bit.ly/2y4fgqh\">CHANGELOG</a>\n\n## Next milestone:\n\n- Feedbacks, suggestions or ideas are welcome! Thanks.\n- Check out [TODO.md](TODO.md)\n"
  },
  {
    "path": "TODO.md",
    "content": "# TODO.md Kanban Board\n\nTODO.md Kanban Board\n\n<em>[TODO.md spec & Kanban Board](https://bit.ly/3fCwKfM)</em>\n\n### Todo\n\n- [ ] Publish on open-vsx.org\n- [ ] Get Todo\\*.md files for the dropdown\n- [ ] Rename Git Repos\n  - [ ] Update Readme, Docs\n  - [ ] Update all URLs\n- [ ] Animate Search Input\n- [ ] Support Task description\n- [ ] Markdown links for File or URL\n- [ ] Press _Enter_ to add the next task\n\n### In Progress\n\n- [ ] Task menu: add hyperlinks ?\n- [ ] Smart positioning for Task Menu\n- [ ] Change icon on Marketplace\n- [ ] 🐞 Fix sub-task toggling for a new task\n- [ ] 🐞 Bug: it flashes the default data, then loads the real data\n\n### Done ✓\n\n- [x] Markdown text formats 05/21/2021\n- [x] New task should be at the top 05/20/2021\n- [x] Dropdown option to help about adding more todo files 05/19/2021\n- [x] Respect theme colors\n- [x] Task menu: Insert Emojis 😊\n- [x] Task menu: Toggle sub-task\n- [x] Rename to Todo.md Kanban Board\n- [x] Support sub-task\n- [x] Optional checkbox in task title\n- [x] Default to the 1st Task List\n- [x] Add Help link\n- [x] Multiple task lists\n- [x] Add Search Box\n- [x] Auto save for Task Re-ordering\n- [x] Add Open File link\n- [x] Split to different Doc Topics\n- [x] Auto focus on New Task 2020-03-18\n- [x] Check mark to mark as Done 2020-03-18\n- [x] Setup coddx mailbox\n"
  },
  {
    "path": "docs/documentation.md",
    "content": "# Documentation\n\n### Task Board - TODO.md\n\n[Task Board Doc](task-board.md)\n\n### Generate Files\n\n[Generate Files Doc](generate-files.md)\n"
  },
  {
    "path": "docs/generate-files.md",
    "content": "# Generate Files\n\n### Features\n\n- Work with any programming languages.\n- Templating syntax.\n- Store templates as a single file (can be committed to git, shared with others).\n- Auto-create sub-directories.\n- Built-in params (fileName, date, time, etc.).\n- Custom params.\n- Context menu to generate files/directories from anywhere.\n\n#### Usage:\n\n- Open Coddx panel:\n  - Bring up the Command Palette (F1), type and select: Coddx: Generate Files.\n  - Or: right-click on a directory, select: Generate files.\n- Verify Template to suit your needs. (See <a href=\"https://bit.ly/2WHprLW\">Docs</a> for syntax)\n- Verify the output path (relative to Project root), a new directory will be created if not existed.\n- Enter the new file name to generate file(s).\n\n<img src=\"docs/media/coddx-demo.gif\" />\n  \n<img src=\"docs/media/panel.png\" height=\"280\" />\n\n### How does it work?\n\nCoddx uses Mozilla's Templating Engine called <a href=\"https://github.com/mozilla/nunjucks\">nunjucks</a>. Nunjucks is a powerful library, supports more advanced use cases like parameters, custom filters, etc.\n\n### Templating Syntax\n\nExample:\n\n```\n// -------------->> {{fileName}}.tsx\n// created time: {{YYYY}}-{{MM}}-{{DD}} {{HH}}:{{mm}}\n// file1 content...\n\n// -------------->> __test__/{{fileName}}.test.tsx\n// file2 content... (sub-directory will be created)\n```\n\nBuilt-in Template Variables:\n\n- {{fileName}} - will be replace with the value of \"File Name\" field.\n- {{YYYY}}, {{MM}}, {{DD}}, {{HH}}, {{mm}} - year, month, day, hours (24), minutes.\n\nRead more: <a href=\"https://mozilla.github.io/nunjucks/templating.html\">Nunjucks Templating Syntax</a>\n\n### Custom Variables\n\nYou can declare custom variables (Params field) in JSON format, for example:\n\n- { \"myVar\": \"value\" } - declare `myVar`, then use it in your template like `{{myVar}}`\n\n### Custom filters\n\n(Coming soon)\n"
  },
  {
    "path": "docs/task-board.md",
    "content": "# Todo Kanban Board\n\nTodo Kanban Board manages tasks and save them as [TODO.md](https://bit.ly/2JdEuET) - a simple plain text file.\n\n## Features\n\n- The syntax is compatible with [Github Markdown](https://bit.ly/2wBp1Mk)\n- TODO.md file is portable and can be committed with Pull Requests (PRs) to git repositories.\n- Support custom file name, multiple task lists.\n- Checkboxes are optional (if your task titles don't have them).\n- Task title can also have markdown for styling, hyperlinks, simple html or even img tags.\n- Task menu: to insert a sub-task, emoji icons (like bug 🐞 blocked ❌ party 🎉 etc.).\n- See also: <a href=\"https://bit.ly/2SfcKaH\">Documentation / Guides</a>\n\n## TODO.md\n\n- Tasks are synced to the markdown file using [TODO.md format](https://bit.ly/2JdEuET).\n- Task Board is a bit strict about the format.\n\n  - Please follow the typical structure like in the example so it can work properly. If it fails to open, please revert or re-generate your TODO.md file to make it work again.\n\n- For now, after making changes to TODO.md file, please click the Refresh icon (next to the board title) to reload.\n\n- [Example of a TODO.md file](https://github.com/todomd/todo.md/blob/master/TODO.md)\n\n## Settings\n\n- Multiple TODO files:\n  - In your workspace settings.json file, add this: `\"coddx.taskBoard.fileList\": \"TODO.md, folder/TODO-name.md\"` (comma separated, use your file names)\n\n## Tips & Tricks\n\n- In the task title:\n  - You can type `#bug` or `#feat` to classify your task.\n  - You can type a name like `@john`, `@jane`.\n  - Date format can be yyyy-mm-dd\n- Use the Search Box to filter for types, names, etc.\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"coddx-alpha\",\n  \"displayName\": \"TODO.md Kanban Board\",\n  \"description\": \"Coddx - a collection of tools that help developers program efficiently. Some useful features like: Kanban Board to manage project tasks in TODO.md, generating multiple files from templates quickly.\",\n  \"version\": \"0.2.55\",\n  \"publisher\": \"coddx\",\n  \"license\": \"MIT\",\n  \"homepage\": \"https://github.com/coddx-hq\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/coddx-hq/coddx-alpha.git\"\n  },\n  \"keywords\": [\n    \"todo\",\n    \"todolist\",\n    \"kanban\",\n    \"task\",\n    \"project\",\n    \"management\",\n    \"generate\",\n    \"generator\",\n    \"template\",\n    \"webdev\",\n    \"snippet\",\n    \"file\"\n  ],\n  \"icon\": \"docs/media/logo.png\",\n  \"engines\": {\n    \"vscode\": \"^1.29.0\"\n  },\n  \"categories\": [\n    \"Extension Packs\",\n    \"Programming Languages\",\n    \"Snippets\",\n    \"Other\"\n  ],\n  \"activationEvents\": [\n    \"onCommand:extension.viewconfig\",\n    \"onCommand:extension.taskboard\"\n  ],\n  \"main\": \"./out/extension.js\",\n  \"contributes\": {\n    \"languages\": [],\n    \"commands\": [\n      {\n        \"command\": \"extension.viewconfig\",\n        \"title\": \"Generate files\",\n        \"category\": \"Coddx\"\n      },\n      {\n        \"command\": \"extension.taskboard\",\n        \"title\": \"TODO.md Kanban Task Board\",\n        \"category\": \"Coddx\"\n      }\n    ],\n    \"menus\": {\n      \"explorer/context\": [\n        {\n          \"command\": \"extension.viewconfig\"\n        }\n      ]\n    }\n  },\n  \"scripts\": {\n    \"vscode:prepublish\": \"npm run compile\",\n    \"compile\": \"npm-run-all compile:*\",\n    \"watch\": \"npm-run-all -p watch:*\",\n    \"compile:extension\": \"rm -rf out && tsc --jsx react -p ./\",\n    \"compile:views\": \"webpack --mode development\",\n    \"watch:extension\": \"tsc --jsx react -watch -p ./\",\n    \"watch:views\": \"webpack --watch --mode development\",\n    \"postinstall\": \"node ./node_modules/vscode/bin/install\",\n    \"test\": \"npm run compile && node ./node_modules/vscode/bin/test\"\n  },\n  \"devDependencies\": {\n    \"@types/marked\": \"^2.0.2\",\n    \"@types/mocha\": \"^2.2.42\",\n    \"@types/node\": \"^10.12.21\",\n    \"@types/react\": \"^16.9.0\",\n    \"@types/react-dom\": \"^16.9.0\",\n    \"css-loader\": \"^3.0.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"style-loader\": \"^0.23.1\",\n    \"ts-loader\": \"^6.0.4\",\n    \"tslint\": \"^5.12.1\",\n    \"typescript\": \"^3.3.1\",\n    \"vscode\": \"^1.1.37\",\n    \"webpack\": \"^4.35.2\",\n    \"webpack-cli\": \"^3.3.5\"\n  },\n  \"dependencies\": {\n    \"marked\": \"^2.0.4\",\n    \"nunjucks\": \"^3.2.0\",\n    \"prismjs\": \"^1.19.0\",\n    \"react\": \"^16.13.0\",\n    \"react-autosize-textarea\": \"^7.0.0\",\n    \"react-beautiful-dnd\": \"^13.0.0\",\n    \"react-dom\": \"^16.13.0\",\n    \"react-select\": \"^3.1.0\",\n    \"react-simple-code-editor\": \"^0.11.0\",\n    \"styled-components\": \"^5.0.1\"\n  }\n}\n"
  },
  {
    "path": "src/.editorconfig",
    "content": "# EditorConfig: http://EditorConfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\n\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "src/.gitignore",
    "content": "out\nnode_modules\n.vscode-test/\n*.vsix\n\nconfigViewer/configViewer.js"
  },
  {
    "path": "src/.prettierrc.yml",
    "content": "---\nprintWidth: 120\ntabWidth: 2\nuseTabs: false\nsemi: true\nsingleQuote: true\ntrailingComma: none\nbracketSpacing: true\njsxBracketSameLine: false\narrowParens: avoid\n"
  },
  {
    "path": "src/.vscodeignore",
    "content": ".vscode/**\n.vscode-test/**\nout/test/**\nsrc/**\n.gitignore\nvsc-extension-quickstart.md\n**/tsconfig.json\n**/tslint.json\n**/*.map\n**/*.ts"
  },
  {
    "path": "src/extension.ts",
    "content": "// The module 'vscode' contains the VS Code extensibility API\n// Import the module and reference it with the alias vscode in your code below\nimport * as vscode from 'vscode';\n// import { telemetry } from './view/app/Utils';\n\nimport ViewLoader from './view/ViewLoader';\nimport TaskBoardLoader from './view/TaskBoardLoader';\n\n// this method is called when your extension is activated\n// your extension is activated the very first time the command is executed\nexport function activate(context: vscode.ExtensionContext) {\n  // Use the console to output diagnostic information (console.log) and errors (console.error)\n  // This line of code will only be executed once when your extension is activated\n  // console.log('Congratulations, your extension \"vscode-react\" is now active!');\n  // context.subscriptions.push(telemetry);\n\n  // The command has been defined in the package.json file\n  // Now provide the implementation of the command with registerCommand\n  // The commandId parameter must match the command field in package.json\n  let disposable = vscode.commands.registerCommand('extension.viewconfig', (uri: vscode.Uri) => {\n    // let openDialogOptions: vscode.OpenDialogOptions = {\n    //   canSelectFiles: true,\n    //   canSelectFolders: false,\n    //   canSelectMany: false,\n    //   filters: {\n    //     Json: [\"json\"]\n    //   }\n    // };\n\n    // vscode.window\n    //   .showOpenDialog(openDialogOptions)\n    //   .then(async (uri: vscode.Uri[] | undefined) => {\n    //     if (uri && uri.length > 0) {\n    //       const view = new ViewLoader(uri[0], context.extensionPath);\n    //     } else {\n    //       vscode.window.showErrorMessage(\"No valid file selected!\");\n    //       return;\n    //     }\n    //   });\n    const view = new ViewLoader(context.extensionPath, uri);\n    // telemetry.sendTelemetryEvent('init-file-generator');\n    return view;\n  });\n  context.subscriptions.push(disposable);\n\n  let taskBoardCmd = vscode.commands.registerCommand('extension.taskboard', (uri: vscode.Uri) => {\n    const view = new TaskBoardLoader(context.extensionPath, uri);\n    // telemetry.sendTelemetryEvent('init-task-board');\n    return view;\n  });\n  context.subscriptions.push(taskBoardCmd);\n}\n\n// this method is called when your extension is deactivated\nexport function deactivate() {\n  // telemetry.dispose();\n}\n"
  },
  {
    "path": "src/test/extension.test.ts",
    "content": "//\n// Note: This example test is leveraging the Mocha test framework.\n// Please refer to their documentation on https://mochajs.org/ for help.\n//\n\n// The module 'assert' provides assertion methods from node\nimport * as assert from 'assert';\n\n// You can import and use all API from the 'vscode' module\n// as well as import your extension to test it\n// import * as vscode from 'vscode';\n// import * as myExtension from '../extension';\n\n// Defines a Mocha test suite to group tests of similar kind together\nsuite(\"Extension Tests\", function () {\n\n    // Defines a Mocha unit test\n    test(\"Something 1\", function() {\n        assert.equal(-1, [1, 2, 3].indexOf(5));\n        assert.equal(-1, [1, 2, 3].indexOf(0));\n    });\n});"
  },
  {
    "path": "src/test/index.ts",
    "content": "//\n// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING\n//\n// This file is providing the test runner to use when running extension tests.\n// By default the test runner in use is Mocha based.\n//\n// You can provide your own test runner if you want to override it by exporting\n// a function run(testsRoot: string, clb: (error: Error, failures?: number) => void): void\n// that the extension host can call to run the tests. The test runner is expected to use console.log\n// to report the results back to the caller. When the tests are finished, return\n// a possible error to the callback or null if none.\n\nimport * as testRunner from 'vscode/lib/testrunner';\n\n// You can directly control Mocha options by configuring the test runner below\n// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options\n// for more info\ntestRunner.configure({\n    ui: 'tdd', \t\t// the TDD UI is being used in extension.test.ts (suite, test, etc.)\n    useColors: true // colored output from test results\n});\n\nmodule.exports = testRunner;"
  },
  {
    "path": "src/view/TaskBoardLoader.ts",
    "content": "import * as vscode from 'vscode';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { IConfig, ICommand, CommandAction } from './app/model';\nimport { deepFind, VER } from './app/Utils';\n\nlet __panel = null;\nlet selectedFile = '';\n\nexport default class ViewLoader {\n  private readonly _panel: vscode.WebviewPanel | undefined;\n  private readonly _extensionPath: string = '';\n  private _disposables: vscode.Disposable[] = [];\n\n  constructor(extensionPath: string, uri: vscode.Uri) {\n    // load \"coddx.taskBoard.fileList\" from config (settings):\n    const configuration = vscode.workspace.getConfiguration();\n    const fileList: string = configuration.get('coddx.taskBoard.fileList') || 'TODO.md';\n    const filesArr = fileList.split(',').map(str => str.trim());\n    selectedFile = filesArr[0];\n\n    this._extensionPath = extensionPath;\n    const column = vscode.window.activeTextEditor\n      ? vscode.ViewColumn.Two // vscode.window.activeTextEditor.viewColumn  vscode.ViewColumn.Two\n      : undefined;\n\n    // let config = this.getFileContent();\n    // if (config) {\n    this._panel = vscode.window.createWebviewPanel('configView', 'Task Board', column || vscode.ViewColumn.Two, {\n      enableScripts: true,\n      localResourceRoots: [vscode.Uri.file(path.join(extensionPath, 'configViewer'))]\n    });\n\n    // get base path (from the user's workspace path):\n    const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';\n    // const templateFilePath = rootPath + '/TODO.md';\n    let basePath = ''; // relative\n\n    if (uri && uri.fsPath) {\n      // path from Context Menu\n      basePath = uri.fsPath.replace(rootPath, ''); // get relative path\n    }\n\n    // const fileUri = vscode.Uri.file(templateFilePath);\n    const todoStr = ''; // this.getFileContent(fileUri);\n\n    this._panel.webview.html = this.getWebviewContent({\n      basePath,\n      templateString: todoStr || '',\n      fileList,\n      selectedFile,\n      rootPath\n    });\n    __panel = this._panel;\n\n    this._panel.webview.onDidReceiveMessage(\n      (command: ICommand) => {\n        switch (command.action) {\n          case CommandAction.ShowMessage:\n            vscode.window.showInformationMessage(command.content.description);\n            return;\n          case CommandAction.OpenFile:\n            const rootPath2 = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';\n            const filePath2 = rootPath2 + (selectedFile || 'TODO.md');\n            vscode.window.showTextDocument(vscode.Uri.file(filePath2));\n            return;\n          case CommandAction.Save:\n            this.saveFileContent(command.content);\n            return;\n          case CommandAction.Load:\n            selectedFile = command.content.description || 'TODO.md';\n            const rootPath3 = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';\n            const filePath3 = rootPath3 + selectedFile;\n            const fileUri = vscode.Uri.file(filePath3);\n            const todoStr = this.getFileContent(fileUri);\n            this._panel.webview.html = this.getWebviewContent({\n              basePath,\n              templateString: todoStr || '',\n              fileList,\n              selectedFile,\n              rootPath\n            });\n            // __panel.webview.postMessage({ command: 'load', content }); // Doesn't work ???\n            return;\n          // case CommandAction.GetListFiles:\n          // load config (settings)\n          // vscode.window.showInformationMessage(`👍 Config:`, JSON.stringify(filesArr));\n\n          // WORKS!\n          // const workspace = vscode.workspace.workspaceFolders[0];\n          // if (workspace) {\n          //   vscode.workspace\n          //     .findFiles(new vscode.RelativePattern(workspace, '**/*/TODO.md'), '**/node_modules/**')\n          //     .then(results => {\n          //       console.log('results: ', results);\n          //     });\n          // }\n          // return;\n        }\n      },\n      undefined,\n      this._disposables\n    );\n    // }\n  }\n\n  private getWebviewContent({ basePath, templateString, fileList, selectedFile, rootPath }): string {\n    // Local path to main script run in the webview\n    const reactAppPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'configViewer', 'configViewer.js'));\n    const reactAppUri = reactAppPathOnDisk.with({ scheme: 'vscode-resource' });\n\n    // const configJson = JSON.stringify(config);\n    const fullPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + `/${selectedFile}`;\n    // rootPathBase64 = toBase64(rootPathBase64);\n\n    return `<!DOCTYPE html>\n    <html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n        <title>Task Board</title>\n\n        <meta http-equiv=\"Content-Security-Policy\"\n                    content=\"default-src 'none';\n                             img-src https:;\n                             script-src 'unsafe-eval' 'unsafe-inline' vscode-resource:;\n                             style-src vscode-resource: 'unsafe-inline';\">\n        <script>\n          window.acquireVsCodeApi = acquireVsCodeApi;\n          window.initialData = { name: 'TaskBoard', path: \\`${basePath}\\`, dataString: \\`${templateString}\\`, fileList: \\`${fileList}\\`, selectedFile: \\`${selectedFile}\\` };\n        </script>\n    </head>\n    <body>\n        <div id=\"root\"></div>\n\n        <script src=\"${reactAppUri}\"></script>\n    </body>\n    </html>`;\n  }\n\n  private getFileContent(fileUri: vscode.Uri) {\n    //: IConfig | undefined {\n    if (fs.existsSync(fileUri.fsPath)) {\n      let content = fs.readFileSync(fileUri.fsPath, 'utf8');\n      // let config: IConfig = JSON.parse(content);\n\n      // return config;\n      return content;\n    }\n    return undefined;\n  }\n\n  private saveFileContent(config: IConfig) {\n    const content = config.description;\n    const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';\n    const filePath = rootPath + (selectedFile || 'TODO.md');\n\n    const uri = vscode.Uri.file(filePath);\n    fs.writeFileSync(uri.fsPath, content);\n\n    // vscode.window.showInformationMessage(`👍 TODO.md saved!`);\n  }\n}\n"
  },
  {
    "path": "src/view/ViewLoader.ts",
    "content": "import * as vscode from 'vscode';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { IConfig, ICommand, CommandAction } from './app/model';\nimport { deepFind, VER } from './app/Utils';\n\nexport default class ViewLoader {\n  private readonly _panel: vscode.WebviewPanel | undefined;\n  private readonly _extensionPath: string = '';\n  private _disposables: vscode.Disposable[] = [];\n\n  constructor(extensionPath: string, uri: vscode.Uri) {\n    this._extensionPath = extensionPath;\n    const column = vscode.window.activeTextEditor\n      ? vscode.ViewColumn.Two // vscode.window.activeTextEditor.viewColumn\n      : undefined;\n\n    // let config = this.getFileContent();\n    // if (config) {\n    this._panel = vscode.window.createWebviewPanel(\n      'configView',\n      'Coddx - Generate Files',\n      column || vscode.ViewColumn.Two,\n      {\n        enableScripts: true,\n        localResourceRoots: [vscode.Uri.file(path.join(extensionPath, 'configViewer'))]\n      }\n    );\n\n    // get base path (from the user's workspace path):\n    const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';\n    const templateFilePath = rootPath + '/.coddx-template';\n    let basePath = ''; // relative\n\n    if (uri && uri.fsPath) {\n      // path from Context Menu\n      basePath = uri.fsPath.replace(rootPath, ''); // get relative path\n    }\n    // if (uri.fsPath) {\n    //   basePath = uri.fsPath;\n    // } else if (vscode.workspace.workspaceFolders) {\n    //   basePath = vscode.workspace.workspaceFolders[0].uri.fsPath;\n    // }\n\n    // load template file:\n    // const filePath = '/Users/duc/Documents/downloads/coddx-ext/.coddx-template';\n    const fileUri = vscode.Uri.file(templateFilePath);\n    const templateString = this.getFileContent(fileUri);\n\n    this._panel.webview.html = this.getWebviewContent(basePath, templateString || '');\n\n    this._panel.webview.onDidReceiveMessage(\n      (command: ICommand) => {\n        switch (command.action) {\n          case CommandAction.Save:\n            const fileUri = vscode.Uri.file(templateFilePath);\n\n            this.saveFileContent(fileUri, command.content);\n            return;\n          case CommandAction.GenerateFiles:\n            this.generateFiles(command.content);\n            return;\n        }\n      },\n      undefined,\n      this._disposables\n    );\n    // }\n  }\n\n  // constructor(fileUri: vscode.Uri, extensionPath: string) {\n  //   this._extensionPath = extensionPath;\n\n  //   const column = vscode.window.activeTextEditor\n  // \t\t? vscode.ViewColumn.Two // vscode.window.activeTextEditor.viewColumn\n  // \t\t: undefined;\n\n  //   let config = this.getFileContent(fileUri);\n  //   if (config) {\n  //     this._panel = vscode.window.createWebviewPanel(\n  //       \"configView\",\n  //       \"Config View\",\n  //       column || vscode.ViewColumn.Two,\n  //       {\n  //         enableScripts: true,\n\n  //         localResourceRoots: [\n  //           vscode.Uri.file(path.join(extensionPath, \"configViewer\"))\n  //         ]\n  //       }\n  //     );\n\n  //     this._panel.webview.html = this.getWebviewContent(config);\n\n  //     this._panel.webview.onDidReceiveMessage(\n  //       (command: ICommand) => {\n  //         switch (command.action) {\n  //           case CommandAction.Save:\n  //             this.saveFileContent(fileUri, command.content);\n  //             return;\n  //         }\n  //       },\n  //       undefined,\n  //       this._disposables\n  //     );\n  //   }\n  // }\n\n  private getWebviewContent(basePath: string, templateString: string): string {\n    // Local path to main script run in the webview\n    const reactAppPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'configViewer', 'configViewer.js'));\n    const reactAppUri = reactAppPathOnDisk.with({ scheme: 'vscode-resource' });\n\n    // const configJson = JSON.stringify(config);\n\n    return `<!DOCTYPE html>\n    <html lang=\"en\">\n    <head>\n        <meta charset=\"UTF-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n        <title>Coddx - Generate Files</title>\n\n        <meta http-equiv=\"Content-Security-Policy\"\n                    content=\"default-src 'none';\n                             img-src https:;\n                             script-src 'unsafe-eval' 'unsafe-inline' vscode-resource:;\n                             style-src vscode-resource: 'unsafe-inline';\">\n\n        <script>\n          window.acquireVsCodeApi = acquireVsCodeApi;\n          window.initialData = { path: \\`${basePath}\\`, templateString: \\`${templateString}\\` };\n        </script>\n    </head>\n    <body>\n        <div id=\"root\"></div>\n\n        <script src=\"${reactAppUri}\"></script>\n    </body>\n    </html>`;\n  }\n\n  private getFileContent(fileUri: vscode.Uri) {\n    //: IConfig | undefined {\n    if (fs.existsSync(fileUri.fsPath)) {\n      let content = fs.readFileSync(fileUri.fsPath, 'utf8');\n      // let config: IConfig = JSON.parse(content);\n\n      // return config;\n      return content;\n    }\n    return undefined;\n  }\n\n  private saveFileContent(fileUri: vscode.Uri, config: IConfig) {\n    // if (fs.existsSync(fileUri.fsPath)) {}\n    fs.writeFileSync(fileUri.fsPath, config.description || '');\n\n    vscode.window.showInformationMessage(`👍 Template saved to .coddx-template`);\n  }\n\n  private mkDirByPathSync(targetDir: string, { baseDir = '', isRelativeToScript = false } = {}) {\n    const sep = path.sep;\n    const initDir = path.isAbsolute(targetDir) ? sep : '';\n    // const baseDir = isRelativeToScript ? __dirname : '.';\n\n    return targetDir.split(sep).reduce((parentDir, childDir) => {\n      const curDir = path.resolve(baseDir, parentDir, childDir);\n      try {\n        fs.mkdirSync(curDir);\n      } catch (err) {\n        if (err.code === 'EEXIST') {\n          // curDir already exists!\n          return curDir;\n        }\n\n        // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.\n        if (err.code === 'ENOENT') {\n          // Throw the original parentDir error on curDir `ENOENT` failure.\n          throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`);\n        }\n\n        const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;\n        if (!caughtErr || (caughtErr && curDir === path.resolve(targetDir))) {\n          throw err; // Throw if it's just the last created dir.\n        }\n      }\n\n      return curDir;\n    }, initDir);\n  }\n\n  private generateFiles(config: IConfig) {\n    const { path, files } = JSON.parse(config.description || '');\n    let counter = 0;\n    const rootPath = deepFind(vscode, 'workspace.workspaceFolders[0].uri.fsPath', '') + '/';\n\n    if (!fs.existsSync(rootPath + path)) {\n      this.mkDirByPathSync(path, { baseDir: rootPath });\n    }\n\n    Object.keys(files).forEach(itemKey => {\n      const item = files[itemKey];\n\n      if (item.checked === true) {\n        const fileName = item.fileName; // itemKey.trim()\n        const filePath = rootPath + (path + '/' + fileName).trim();\n\n        if (fileName.indexOf('/') > 0) {\n          // create directories, e.g. __test__/mocks\n          const dirPath = fileName.substr(0, fileName.lastIndexOf('/'));\n          this.mkDirByPathSync(dirPath, { baseDir: rootPath + path });\n        }\n\n        const fileUri = vscode.Uri.file(filePath);\n        fs.writeFileSync(fileUri.fsPath, item.fileContent);\n        counter++;\n      }\n    });\n\n    vscode.window.showInformationMessage(`👍 ${counter} files generated.`);\n  }\n}\n"
  },
  {
    "path": "src/view/app/Utils.ts",
    "content": "import nunjucks from 'nunjucks';\nimport { ICommand, CommandAction } from './model';\n\nexport const VER = '0.2.55'; // TODO: get this from package.json.\n\n// const TelemetryReporter = require('vscode-extension-telemetry');\n// const extensionId = 'coddx-alpha';\n// const extensionVersion = '0.2.11';\n// const key = '';\n// export const telemetry = new TelemetryReporter(extensionId, extensionVersion, key); // TODO: this crashed!\n\nexport interface FilesInterface {\n  checked?: boolean;\n  fileMarker?: string;\n  fileeName?: string;\n  fileContent?: string;\n}\n\nexport const FILE_SEPARATOR = '--->>';\n\nexport const DefaultTemplateString = `// -------------->> {{fileName}}.tsx\n// created time: {{YYYY}}-{{MM}}-{{DD}} {{HH}}:{{mm}}\nimport * as React from 'react';\nimport './{{fileName}}.css';\n\nexport default class {{fileName}} extends React.Component {\n  render() {\n    return (\n      <div>\n        {/* ...code goes here... */}\n      </div>\n    )\n  }\n}\n\n// -------------->> {{fileName}}.css\n.main {}\n\n// -------------->> __test__/{{fileName}}.test.tsx\nimport * as React from 'react';\n\nit('{{fileName}} - render', () => {\n  // render(<{{fileName}} />);\n  // expect(...);\n});\n`;\n\nconst getBuiltInParams = () => {\n  const fd = formatDate(new Date());\n  return {\n    YYYY: fd.year,\n    MM: fd.month,\n    DD: fd.day,\n    HH: fd.hours24,\n    mm: fd.minutes,\n    ss: fd.seconds\n  };\n};\n\n// format to: yyy-mm-dd\nexport function formatDate(d: Date) {\n  let month = '' + (d.getMonth() + 1);\n  let day = '' + d.getDate();\n  const year = d.getFullYear();\n\n  if (month.length < 2) month = '0' + month;\n  if (day.length < 2) day = '0' + day;\n  return {\n    year,\n    month,\n    day,\n    hours24: d.getHours(),\n    minutes: d.getMinutes(),\n    seconds: d.getSeconds(),\n    yyyymmdd: [year, month, day].join('-')\n  };\n}\n\nexport function parseJsonString(jsonString: string) {\n  if (!jsonString || !jsonString.trim()) {\n    return {};\n  }\n  let useParams = {};\n  try {\n    useParams = JSON.parse(jsonString.trim());\n  } catch {}\n  return useParams;\n}\n\nexport function jsonClone(obj: any) {\n  if (!obj) {\n    return obj; // null or undefined\n  }\n  return JSON.parse(JSON.stringify(obj));\n}\n\nexport function deepFind(obj: any, path: string, defaultValue: any) {\n  const travel = regexp =>\n    String.prototype.split\n      .call(path, regexp)\n      .filter(Boolean)\n      .reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj);\n  const result = travel(/[,[\\]]+?/) || travel(/[,[\\].]+?/);\n  return result === undefined || result === obj ? defaultValue : result;\n}\n\nexport function nunjucksRender(str: string, data: any): string {\n  const tmpl = nunjucks.compile(str);\n  return tmpl.render({ ...getBuiltInParams(), ...data });\n}\n\n// from content string => parse to get file contents & put in \"files\" object {}\nexport function getTemplateItems(content: string, params: any = {}, existingFiles: any = {}): FilesInterface {\n  const lines = content.split('\\n');\n  const output: FilesInterface = {};\n  let fileContent = '';\n  let lastKey = '';\n\n  lines.forEach(line => {\n    if (line.indexOf(FILE_SEPARATOR) >= 0) {\n      // when seeing a new file separator => set the accumulated \"fileContent\" string for the lastKey:\n      if (lastKey) {\n        output[lastKey].fileContent = nunjucksRender(fileContent, params).trim();\n        fileContent = ''; // reset fileContent, prepare for the next file.\n      }\n\n      const arr = line.split(FILE_SEPARATOR);\n      const itemKey = arr[arr.length - 1].trim();\n      const existingItem = existingFiles[itemKey] || {};\n\n      const fileName = nunjucksRender(itemKey, params).trim();\n      output[itemKey] = {\n        checked: existingItem.checked !== undefined ? existingItem.checked : true,\n        fileMarker: line.replace(itemKey, fileName),\n        fileName,\n        fileContent\n      };\n      lastKey = itemKey;\n    } else {\n      if (output[lastKey] && output[lastKey].checked === true) {\n        fileContent += line + '\\n';\n      }\n    }\n  });\n\n  if (lastKey) {\n    output[lastKey].fileContent = nunjucksRender(fileContent, params).trim();\n  }\n  return output;\n}\n\nexport const sendCommand = (vscode: any, action: CommandAction, dataStr: string) => {\n  let command: ICommand = {\n    action: action,\n    content: {\n      name: '',\n      description: dataStr\n    }\n  };\n  if (vscode.postMessage) {\n    vscode.postMessage(command);\n  }\n};\n\nexport function getVscodeHelper(vscode: any) {\n  return {\n    showMessage: (msg: string) =>\n      vscode.postMessage({\n        action: CommandAction.ShowMessage,\n        content: {\n          name: '',\n          description: msg\n        }\n      }),\n    saveList: (dataStr: string) => {\n      let command: ICommand = {\n        action: CommandAction.Save,\n        content: {\n          name: '',\n          description: dataStr\n        }\n      };\n      if (vscode.postMessage) {\n        vscode.postMessage(command);\n      }\n    }\n  };\n}\n\n// In VSCode, \"btoa\" function is not available (?) => use this:\n// A helper that returns Base64 characters and their indices.\n// const chars = {\n//   ascii: function() {\n//     return 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';\n//   },\n//   indices: function() {\n//     if (!this.cache) {\n//       this.cache = {};\n//       var ascii = chars.ascii();\n\n//       for (var c = 0; c < ascii.length; c++) {\n//         var chr = ascii[c];\n//         this.cache[chr] = c;\n//       }\n//     }\n//     return this.cache;\n//   }\n// };\n// const toBase64 = function(data) {\n//   var ascii = chars.ascii(),\n//     len = data.length - 1,\n//     i = -1,\n//     b64 = '';\n//   while (i < len) {\n//     var code = (data.charCodeAt(++i) << 16) | (data.charCodeAt(++i) << 8) | data.charCodeAt(++i);\n//     b64 += ascii[(code >>> 18) & 63] + ascii[(code >>> 12) & 63] + ascii[(code >>> 6) & 63] + ascii[code & 63];\n//   }\n//   var pads = data.length % 3;\n//   if (pads > 0) {\n//     b64 = b64.slice(0, pads - 3);\n\n//     while (b64.length % 4 !== 0) {\n//       b64 += '=';\n//     }\n//   }\n//   return b64;\n// };\n"
  },
  {
    "path": "src/view/app/components/CodeGen/CodeEditor.css",
    "content": ".container__content {\n    width: 440px;\n    max-width: 100%;\n    padding: 10px;\n    text-align: center;\n  }\n  \n  .container_editor_area {\n    tab-size: 4ch;\n    max-height: 400px;\n    overflow: auto;\n    margin: 1.67em 0;\n  }\n  \n  .container__editor {\n    font-size: 12px;\n    font-variant-ligatures: common-ligatures;\n    background-color: #fafafa;\n    border-radius: 3px;\n  }\n  \n  .container__editor textarea {\n    outline: 0;\n  }\n  \n  .button {\n    display: inline-block;\n    padding: 0 6px;\n    text-decoration: none;\n    background: #000;\n    color: #fff;\n  }\n  \n  .button:hover {\n    background: linear-gradient(45deg, #E42B66, #E2433F);\n  }\n  \n  /* Syntax highlighting */\n  .token.comment,\n  .token.prolog,\n  .token.doctype,\n  .token.cdata {\n    color: #5a9b4b;\n  }\n  .token.punctuation {\n    color: #9e9e9e;\n  }\n  .namespace {\n    opacity: 0.7;\n  }\n  .token.property,\n  .token.tag,\n  .token.boolean,\n  .token.number,\n  .token.constant,\n  .token.symbol,\n  .token.deleted {\n    color: #e91e63;\n  }\n  .token.selector,\n  .token.attr-name,\n  .token.string,\n  .token.char,\n  .token.builtin,\n  .token.inserted {\n    color: #4caf50;\n  }\n  .token.operator,\n  .token.entity,\n  .token.url,\n  .language-css .token.string,\n  .style .token.string {\n    color: #795548;\n  }\n  .token.atrule,\n  .token.attr-value,\n  .token.keyword {\n    color: #3f51b5;\n  }\n  .token.function {\n    color: #f44336;\n  }\n  .token.regex,\n  .token.important,\n  .token.variable {\n    color: #ff9800;\n  }\n  .token.important,\n  .token.bold {\n    font-weight: bold;\n  }\n  .token.italic {\n    font-style: italic;\n  }\n  .token.entity {\n    cursor: help;\n  }"
  },
  {
    "path": "src/view/app/components/CodeGen/CodeEditor.tsx",
    "content": "import * as React from 'react';\n\nimport Editor from 'react-simple-code-editor';\nimport { highlight, languages } from 'prismjs/components/prism-core';\nimport 'prismjs/components/prism-clike';\nimport 'prismjs/components/prism-javascript';\n\nimport './CodeEditor.css';\n\nconst styles = {\n  editor: {\n    fontFamily: '\"Fira code\", \"Fira Mono\", monospace',\n    fontSize: 12\n  }\n}\n\ninterface Props {\n  code: string,\n  onValueChange?: (code: string) => void\n}\n\nexport default class CodeEditor extends React.Component<Props> {\n  state = { code: this.props.code };\n\n  componentWillReceiveProps(newProps: any) {\n    this.setState({ code: newProps.code });\n  }\n\n  render() {\n    return (\n      <>\n        <Editor\n          value={this.state.code}\n          onValueChange={code => {\n            this.setState({ code });\n            if (this.props.onValueChange) {\n              this.props.onValueChange(code);\n            }\n          }}\n          highlight={code => highlight(code || '', languages.js)}\n          padding={10}\n          style={styles.editor}\n        />\n      </>\n    );\n  }\n}\n"
  },
  {
    "path": "src/view/app/components/CodeGen/EditTemplatePanel.tsx",
    "content": "import * as React from 'react';\nimport CodeEditor from './CodeEditor';\n\nexport default class EditTemplatePanel extends React.Component<any> {\n  state = {\n    code: this.props.code\n  };\n\n  componentWillReceiveProps(newProps: any) {\n    this.setState({ code: newProps.code });\n  }\n\n  render() {\n    return (\n      <div>\n        <div>\n          <button onClick={() => this.props.onClose(this.state.code)}>Save</button>\n          &nbsp;\n          {/* <button onClick={() => {}}>Cancel</button> */}\n        </div>\n        <hr />\n        <CodeEditor code={this.state.code} onValueChange={code => this.setState({ code })} />\n      </div>\n    );\n  }\n}\n"
  },
  {
    "path": "src/view/app/components/CodeGen/MainView.css",
    "content": "body,\nhtml {\n  margin: 0;\n  padding: 0;\n}\nbutton {\n  background-color: #0063a0;\n  color: #fff;\n  padding: 5px 20px;\n  border: none;\n  cursor: pointer;\n}\nbutton:hover {\n  background-color: #0073bb;\n}\nbutton:disabled {\n  color: #333;\n  cursor: not-allowed;\n}\n\n.mainView {\n  display: flex;\n  flex-direction: column;\n  margin: 0;\n  padding: 10px;\n}\n\n.mainView .body {\n  /* min-height: 92vh; */\n  padding: 10px 0;\n}\n.mainView a {\n  text-decoration: none;\n}\n.mainView textarea {\n  width: 97%;\n  height: 50vh;\n  padding: 5px;\n  font-family: 'Courier New', Courier, monospace;\n  font-size: 1.2em;\n}\n\n.mainView .fileList {\n  margin: 10px 10px;\n  padding: 0;\n}\n.mainView .fileList li {\n  list-style: none;\n}\n.mainView .fileList input {\n  margin-right: 10px;\n}\n\n.mainView footer {\n  position: fixed;\n  bottom: 0px;\n  width: 100%;\n  background-color: #333;\n  display: flex;\n  margin: 0;\n  padding: 0;\n}\n\n.mainView footer input {\n  flex: 1;\n  height: 25px;\n  background-color: #333;\n  color: #eee;\n  border-color: #555;\n  padding: 2px 5px;\n  font-family: 'Courier New', Courier, monospace;\n  font-size: 1.2em;\n}\n\n.mainView .infoBox div {\n  display: flex;\n  margin-bottom: 10px;\n}\n.mainView .infoBox span {\n  display: inline-block;\n  width: 90px;\n  align-self: center;\n}\n.mainView .infoBox input {\n  flex: 1;\n  font-family: Verdana, Geneva, Tahoma, sans-serif; /* 'Courier New', Courier, monospace */\n  background-color: #373837;\n  color: #fff;\n  padding: 2px 4px;\n  border: 1px solid #333;\n}\n"
  },
  {
    "path": "src/view/app/components/CodeGen/MainView.tsx",
    "content": "import * as React from 'react';\nimport { IConfig, ICommand, CommandAction } from '../../model';\nimport { FilesInterface, nunjucksRender, getTemplateItems, DefaultTemplateString, parseJsonString } from '../../Utils';\n\nimport EditTemplatePanel from './EditTemplatePanel';\nimport CodeEditor from './CodeEditor';\nimport './MainView.css';\n\nconst saveTemplate = (vscode: any, templateString: string) => {\n  let command: ICommand = {\n    action: CommandAction.Save,\n    content: {\n      name: '',\n      description: templateString\n    }\n  };\n  if (vscode.postMessage) {\n    vscode.postMessage(command);\n  }\n};\n\nconst generateFiles = (vscode: any, path: string, files: FilesInterface) => {\n  let command: ICommand = {\n    action: CommandAction.GenerateFiles,\n    content: {\n      name: '',\n      description: JSON.stringify({\n        path,\n        files\n      })\n    }\n  };\n  if (vscode.postMessage) {\n    vscode.postMessage(command);\n  }\n};\n\ninterface IConfigProps {\n  vscode: any;\n  initialData?: IConfig;\n}\n\ninterface IConfigState {\n  path: string;\n  templateString: string;\n  editMode: boolean;\n  config: IConfig;\n  output: string;\n  name: string;\n  action: string;\n  files: any;\n  params: string;\n}\n\nlet templateStr = window && window['initialData'] ? window['initialData']['templateString'] : '';\nif (!templateStr) {\n  templateStr = DefaultTemplateString;\n}\n\nconst initialPath = window && window['initialData'] ? window['initialData']['path'] : '';\nconst DefaultName = 'NewComponent';\n\nexport default class MainView extends React.Component<IConfigProps, IConfigState> {\n  state = {\n    path: '',\n    templateString: templateStr,\n    editMode: false,\n    config: null,\n    output: '',\n    name: DefaultName,\n    action: 'Preview Output',\n    files: getTemplateItems(window && window['initialData'] ? window['initialData']['templateString'] : ''),\n    params: ''\n  };\n\n  componentDidMount() {\n    this.setState({ path: initialPath + '/' + DefaultName });\n  }\n\n  render() {\n    // const templateString = window['initialData']['templateString'];\n    const { path, templateString, output, name, params } = this.state;\n\n    // const [editMode, setEditMode] = React.useState(false); // Hooks don't work in VSCode Ext ??\n    // console.log(\n    //   '--- ',\n    //   getTemplateItems(\n    //     templateString,\n    //     {},\n    //     {\n    //       '{{fileName}}.css': { checked: false }\n    //     }\n    //   )\n    // );\n    return (\n      <main className=\"mainView\">\n        <section className=\"body\">\n          {this.state.editMode ? (\n            <EditTemplatePanel\n              code={templateString}\n              onClose={(newContent: string) => {\n                this.setState({\n                  editMode: false,\n                  templateString: newContent,\n                  output: ''\n                });\n                saveTemplate(this.props.vscode, newContent);\n              }}\n            />\n          ) : (\n            <div>\n              <div>\n                <a href=\"javascript:;\" onClick={() => this.setState({ editMode: true })}>\n                  Edit Templates\n                </a>\n              </div>\n              <p className=\"infoBox\">\n                <div>\n                  <span>File Name*</span>\n                  <input\n                    placeholder=\"to replace {{fileName}}\"\n                    value={name}\n                    onChange={e => {\n                      const name = e.target.value;\n                      this.setState({\n                        name,\n                        output: '',\n                        path: initialPath + '/' + name\n                      });\n                    }}\n                  />\n                </div>\n                <div>\n                  <span>Path*</span>\n                  <input\n                    placeholder=\"relative to project\"\n                    value={path}\n                    onChange={e => this.setState({ path: e.target.value, output: '' })}\n                  />\n                </div>\n                <div>\n                  <span>Params</span>\n                  <input\n                    placeholder=\"(json, optional)\"\n                    value={this.state.params}\n                    onChange={e => this.setState({ params: e.target.value, output: '' })}\n                  />\n                </div>\n              </p>\n              <p>Choose files to generate:</p>\n              <ul className=\"fileList\">\n                {Object.keys(this.state.files).map(itemKey => {\n                  const item = this.state.files[itemKey];\n\n                  const outputName = nunjucksRender(itemKey, {\n                    fileName: name,\n                    ...parseJsonString(params)\n                  });\n                  return (\n                    <li>\n                      <label>\n                        <input\n                          type=\"checkbox\"\n                          checked={item.checked}\n                          onChange={() => {\n                            const files = this.state.files;\n                            files[itemKey].checked = !files[itemKey].checked;\n                            this.setState({ files });\n                          }}\n                        />\n                        {outputName}\n                      </label>\n                    </li>\n                  );\n                })}\n              </ul>\n              <p>\n                <button\n                  disabled={!name}\n                  onClick={() => {\n                    const files = getTemplateItems(\n                      templateString,\n                      { fileName: name, ...parseJsonString(params) },\n                      this.state.files\n                    );\n                    let newOutput = '';\n                    Object.keys(files).forEach(itemKey => {\n                      if (files[itemKey].checked === true) {\n                        newOutput += '\\n\\n' + files[itemKey].fileMarker + '\\n';\n                        newOutput += files[itemKey].fileContent;\n                      }\n                    });\n\n                    this.setState({ output: newOutput });\n                    // this.props.vscode.window.showInformationMessage()\n                  }}\n                >\n                  Preview Outputs\n                </button>\n                &nbsp;&nbsp;\n                {name.length > 0 && output && (\n                  <button\n                    onClick={() => {\n                      // const output = nunjucksRender(templateString, {\n                      //   fileName: name\n                      // });\n                      // this.setState({ output });\n\n                      const files = getTemplateItems(\n                        this.state.output, // templateString,\n                        { fileName: name, ...parseJsonString(params) },\n                        this.state.files\n                      );\n                      generateFiles(this.props.vscode, path, files);\n                    }}\n                  >\n                    Generate Files\n                  </button>\n                )}\n              </p>\n              {output && <hr />}\n              <CodeEditor code={output} onValueChange={(code) => this.setState({ output: code })} />\n            </div>\n          )}\n        </section>\n        <footer>\n          <input />\n        </footer>\n      </main>\n    );\n  }\n}\n"
  },
  {
    "path": "src/view/app/components/TaskBoard/ButtonBar.tsx",
    "content": "import * as React from 'react';\nimport styled from 'styled-components';\nimport Select from 'react-select';\n\nimport { getMarkdown, SearchIcon } from './Helpers';\nimport SearchInput from './SearchInput';\n\nconst Container = styled.div`\n  display: flex;\n  justify-content: space-between;\n  text-align: right;\n  margin: 5px 10px;\n  span[data-name='project-name'] {\n    font-size: 1.2em;\n    align-self: center;\n    margin-top: 5px;\n    margin-left: 0px;\n    svg {\n      visibility: hidden;\n    }\n    &:hover {\n      svg {\n        visibility: visible;\n      }\n    }\n  }\n  > button {\n    margin-left: 10px;\n  }\n`;\n// const newData = parseMarkdown(defaultDataString);\n\nexport function OpenFileIcon(props: any) {\n  return (\n    <svg\n      {...props}\n      title=\"Open file\"\n      style={{\n        width: '0.7em',\n        height: '0.7em',\n        cursor: 'pointer',\n        marginLeft: 5,\n        marginTop: 5\n      }}\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 16 16\"\n      version=\"1.1\"\n    >\n      <path d=\"M14 16v-11l-1 1v9h-12v-12h9l1-1h-11v14z\" fill=\"currentColor\" />\n      <path d=\"M16 0h-5l1.8 1.8-6.8 6.8 1.4 1.4 6.8-6.8 1.8 1.8z\" fill=\"currentColor\" />\n    </svg>\n  );\n}\n\nexport function RefreshIcon(props: any) {\n  return (\n    <svg\n      {...props}\n      title=\"Refresh\"\n      style={{ verticalAlign: 'middle', cursor: 'pointer', marginLeft: 5 }}\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 100.25 100.25\"\n    >\n      <polyline points=\"70,57.75 82.5,45.75 95,57.75 \" />\n      <g>\n        <path\n          fill=\"currentColor\"\n          d=\"M83.854,45.613c-0.076-0.825-0.807-1.438-1.632-1.356c-0.824,0.076-1.432,0.806-1.356,1.631 c0.09,0.97,0.135,1.956,0.135,2.929c0,17.333-14.043,31.434-31.304,31.434c-8.36,0-16.222-3.269-22.134-9.205 c-0.585-0.587-1.534-0.589-2.121-0.004s-0.589,1.535-0.004,2.122c6.479,6.505,15.095,10.087,24.259,10.087 c18.915,0,34.304-15.447,34.304-34.434C84,47.752,83.95,46.674,83.854,45.613z\"\n        />\n        <path\n          fill=\"currentColor\"\n          d=\"M31.082,44.712c-0.575-0.599-1.524-0.618-2.121-0.043L18.513,54.7c-0.336-1.852-0.512-3.737-0.512-5.632 c0-17.268,14.051-31.317,31.322-31.317c8.351,0,16.206,3.247,22.118,9.143c0.588,0.585,1.538,0.583,2.122-0.003 c0.585-0.586,0.583-1.536-0.003-2.121c-6.479-6.46-15.087-10.019-24.237-10.019c-18.926,0-34.322,15.395-34.322,34.317 c0,1.505,0.102,3.004,0.296,4.488L6.04,44.669c-0.597-0.573-1.545-0.555-2.121,0.043c-0.574,0.597-0.555,1.547,0.043,2.121l12.5,12 c0.29,0.279,0.665,0.418,1.039,0.418s0.749-0.139,1.039-0.418l12.5-12C31.637,46.259,31.656,45.309,31.082,44.712z\"\n        />\n      </g>\n    </svg>\n  );\n}\n\nexport default ({\n  vscodeHelper,\n  fileArray,\n  selectedFile,\n  data,\n  onLoadData,\n  onSave,\n  onRefresh,\n  onOpenFile,\n  onSearch,\n  onSelectFile\n}) => {\n  const fileOptions = fileArray.map(path => {\n    return { value: path, label: path };\n  });\n  fileOptions.push({ value: 'ADD', label: '＋ Add file...' });\n\n  const selectedOpt = fileOptions.find(opt => opt.value === selectedFile);\n  const [selectValue, setSelectValue] = React.useState(selectedOpt);\n  const [searchActive, setSearchActive] = React.useState(false);\n  return (\n    <div>\n      <div style={{ display: 'flex' }}>\n        <span style={{ width: '30%', marginLeft: 10 }}>\n          <Select\n            className=\"__reactSelect\"\n            classNamePrefix=\"__select\"\n            options={fileOptions}\n            value={selectValue}\n            styles={{}}\n            onChange={sel => {\n              if (sel.value === 'ADD') {\n                vscodeHelper.showMessage(\n                  'In your workspace \"settings.json\" file, add this: \"coddx.taskBoard.fileList\": \"TODO.md, folder/TODO-name.md\" (comma separated, use your file paths)'\n                );\n                return;\n              }\n              setSelectValue(sel);\n              if (onSelectFile) {\n                onSelectFile(sel);\n              }\n            }}\n          />\n        </span>\n        <a\n          style={{ position: 'absolute', right: 15, top: 15 }}\n          href=\"https://nnote.cc/s/k0o93/todomd-kanban-board-documentation\"\n        >\n          Help | Doc\n        </a>\n      </div>\n      <Container>\n        <span data-name=\"project-name\">\n          {data.projectName} <RefreshIcon onClick={() => onRefresh()} />{' '}\n          <OpenFileIcon\n            onClick={() => {\n              onOpenFile();\n            }}\n          />\n        </span>\n        <div>\n          {searchActive ? (\n            <SearchInput style={{ marginRight: 10 }} onChange={value => onSearch(value)} />\n          ) : (\n            <SearchIcon\n              style={{ cursor: 'pointer', marginRight: 10, verticalAlign: '-4px' }}\n              onClick={() => setSearchActive(true)}\n            />\n          )}\n          <button\n            onClick={() => {\n              const id = `newtask_${Math.random()}`;\n              // const newData = parseMarkdown(defaultDataString);\n              const newData = { ...data };\n\n              let hasCheckbox = true;\n              const firstCol = Object.keys(newData.columns)[0];\n              const firstColTaskIds = newData.columns[firstCol].taskIds;\n              if (firstColTaskIds && firstColTaskIds.length > 0) {\n                const lastColTask = data.tasks[firstColTaskIds[firstColTaskIds.length - 1]];\n                if (lastColTask && lastColTask.hasCheckbox === false) {\n                  hasCheckbox = false;\n                }\n              }\n              newData.tasks[id] = { id, content: '', done: false, hasCheckbox };\n\n              newData.columns[firstCol].taskIds.unshift(id); // add to the top.\n              onLoadData(newData);\n\n              const str = getMarkdown(newData);\n              onSave(str);\n              window['isCreatingTask'] = true; // global flag to be caught in Task.tsx\n            }}\n          >\n            ＋ Task\n          </button>\n        </div>\n        {/* <button\n        onClick={() => {\n          refresh(newData);\n        }}\n      >\n        Refresh\n      </button> */}\n        {/* <button onClick={() => {\n        const str = getMarkdown(data);\n        onSave(str);\n      }}>Save to TODO.md</button> */}\n      </Container>\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/view/app/components/TaskBoard/Helpers.tsx",
    "content": "import * as React from 'react';\nimport { TaskInterface } from './Task';\n\nexport const defaultDataString = `# Project\n\nProject Description\n\n<em>[TODO.md spec & Kanban Board](https://bit.ly/3fCwKfM)</em>\n\n### Todo\n\n### In Progress\n\n### Done ✓\n\n`;\n// export const defaultDataString = `# Project Name\n\n// Project Description\n\n// <em>[TODO.md spec & Kanban Board](https://bit.ly/3fCwKfM)</em>\n\n// ### Todo\n\n// - [ ] Build Launch Pad\n// - [ ] Launch time 🎉\n//   - [ ] Prepare for launching\n//   - [ ] Detail description\n\n// ### In Progress\n\n// - [ ] Build the rocket engine\n\n// ### Done ✓\n\n// - [x] Designed my rocket\n// `;\n\nconst isDoneColumn = (columnName: string) => {\n  if (!columnName) {\n    return false;\n  }\n  const lowerColName = columnName.toLowerCase();\n  if (lowerColName.indexOf('[x]') > 0 || lowerColName.indexOf('✓') > 0) {\n    return true;\n  }\n  return false;\n};\n\nexport function getMarkdown(data) {\n  let md = '';\n  // loop through \"columns\", then column.taskIds => write to lines\n  for (const colKey in data.columns) {\n    const col = data.columns[colKey];\n    md += '### ' + col.title + '\\n\\n';\n\n    let checkboxStr = '[ ] ';\n    if (isDoneColumn(col.title)) {\n      checkboxStr = '[x] ';\n    }\n\n    // for (let i = 0; i < col.taskIds.length; i += 1) {\n    //   const task = col.taskIds[i];\n    //   md += '[ ] ' + task.content + '\\n';\n    // }\n    col.taskIds.forEach((taskId: string) => {\n      const task: TaskInterface = data.tasks[taskId];\n      if (!task) {\n        return;\n      }\n      const indent = task.level === 1 ? '  - ' : '- ';\n      md += indent + (task.hasCheckbox === false ? '' : checkboxStr) + task.content.trim() + '  \\n';\n    });\n    md += '\\n';\n  }\n  md = data.precontent + md; // prepend \"data.precontent\"\n  return md;\n}\n\n// parse TODO.md content (markdown), return object { tasks: {}, ... } - see \"output\":\nexport function parseMarkdown(md: string) {\n  const output = {\n    projectName: '',\n    precontent: '', // description content before the Lists.\n    tasks: {},\n    columns: {},\n    columnOrder: []\n  };\n  let lastColName = '';\n  const lines = md.split('\\n');\n  let taskNum = 0;\n\n  let listFound = false; // found after '### '\n\n  lines.forEach(line => {\n    if (listFound === false) {\n      if (line.indexOf('# ') === 0) {\n        output.projectName = line.replace('# ', '').trim();\n      }\n      output.precontent += line.indexOf('### ') === 0 ? '' : line + '\\n'; // append to precontent\n    }\n\n    if (line.indexOf('### ') === 0) {\n      listFound = true;\n      lastColName = line.replace('### ', '').trim();\n      output.columns[lastColName] = {\n        id: lastColName.trim(),\n        title: lastColName.trim(),\n        taskIds: []\n      };\n      output.columnOrder.push(lastColName);\n      return;\n    }\n    if (!listFound) {\n      return;\n    }\n    if (line.trim().length === 0) {\n      return;\n    }\n\n    // after a List is found => Tasks come next:\n    taskNum++;\n    const id = `task${taskNum}`;\n    const hasCheckbox = line.indexOf('[ ]') >= 0 || line.indexOf('[x]') >= 0;\n    const level = line.indexOf('  - ') === 0 ? 1 : 0;\n    let title = line\n      .replace('  - [ ] ', '')\n      .replace('  - [x] ', '')\n      .replace('  - ', '')\n      .replace('- [ ] ', '')\n      .replace('- [x] ', '')\n      .trim();\n    if (title.indexOf('- ') === 0) {\n      title = title.slice(2); // remove '- '\n    }\n    const task: TaskInterface = {\n      id,\n      content: title,\n      hasCheckbox,\n      done: isDoneColumn(lastColName),\n      level\n    };\n    output.tasks[id] = task;\n    output.columns[lastColName].taskIds.push(id);\n  });\n  // console.log('---- output', output);\n  return output;\n}\n\nexport function DragIcon(props: any) {\n  return (\n    <svg {...props} style={{ width: '1em', height: '1em' }} viewBox=\"0 0 1024 1024\" version=\"1.1\">\n      <path\n        d=\"M384 128h85.333333v85.333333H384V128m170.666667 0h85.333333v85.333333h-85.333333V128M384 298.666667h85.333333v85.333333H384V298.666667m170.666667 0h85.333333v85.333333h-85.333333V298.666667m-170.666667 170.666666h85.333333v85.333334H384v-85.333334m170.666667 0h85.333333v85.333334h-85.333333v-85.333334m-170.666667 170.666667h85.333333v85.333333H384v-85.333333m170.666667 0h85.333333v85.333333h-85.333333v-85.333333m-170.666667 170.666667h85.333333v85.333333H384v-85.333333m170.666667 0h85.333333v85.333333h-85.333333v-85.333333z\"\n        fill=\"currentColor\"\n      />\n    </svg>\n  );\n}\n\nexport function SearchIcon(props: any) {\n  return (\n    <svg {...props} width=\"16\" height=\"16\" x=\"0px\" y=\"0px\" viewBox=\"0 0 511.999 511.999\">\n      <g>\n        <g>\n          <path\n            d=\"M508.874,478.708L360.142,329.976c28.21-34.827,45.191-79.103,45.191-127.309c0-111.75-90.917-202.667-202.667-202.667 S0,90.917,0,202.667s90.917,202.667,202.667,202.667c48.206,0,92.482-16.982,127.309-45.191l148.732,148.732 c4.167,4.165,10.919,4.165,15.086,0l15.081-15.082C513.04,489.627,513.04,482.873,508.874,478.708z M202.667,362.667 c-88.229,0-160-71.771-160-160s71.771-160,160-160s160,71.771,160,160S290.896,362.667,202.667,362.667z\"\n            fill=\"currentColor\"\n          />\n        </g>\n      </g>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "src/view/app/components/TaskBoard/SearchInput.tsx",
    "content": "import * as React from 'react';\n\ninterface Props {\n  style?: object;\n  placeholder?: string;\n  delayTime?: number;\n  onChange?: (value: string) => void;\n}\n\nexport default function ({ style, placeholder = 'Search', delayTime = 200, onChange }: Props) {\n  const [timeoutId, setTimeoutId] = React.useState(null);\n  const [value, setValue] = React.useState('');\n  const inputRef: React.RefObject<HTMLInputElement> = React.useRef(null);\n  const allStyle = {\n    padding: '4px 3px',\n    backgroundColor: 'var(--vscode-tab-background)',\n    color: 'var(--button-text-color)',\n    border: '1px solid var(--vscode-tab-inactiveBackground)',\n    ...style\n  };\n  const resetTimer = () =>\n    setTimeout(() => {\n      clearTimeout(timeoutId);\n      setTimeoutId(null);\n      if (onChange) {\n        onChange(inputRef.current.value);\n      }\n    }, delayTime);\n\n  return (\n    <input\n      autoFocus\n      ref={inputRef}\n      placeholder={placeholder}\n      defaultValue={value}\n      onChange={ev => {\n        setValue(ev.target.value);\n        if (timeoutId) {\n          clearTimeout(timeoutId);\n          setTimeoutId(null);\n        }\n        const id = resetTimer();\n        setTimeoutId(id);\n      }}\n      style={{ ...allStyle }}\n    />\n  );\n}\n"
  },
  {
    "path": "src/view/app/components/TaskBoard/Task.tsx",
    "content": "import * as React from 'react';\nimport styled from 'styled-components';\nimport { Draggable } from 'react-beautiful-dnd';\nimport TextareaAutosize from 'react-autosize-textarea';\nimport { DragIcon } from './Helpers';\nimport TaskMenu from './TaskMenu';\nimport { parseInline } from 'marked';\n\nconst { memo } = React;\n\nexport interface TaskInterface {\n  id: string;\n  content: string;\n  done: boolean;\n  hasCheckbox?: boolean;\n  matched?: boolean;\n  level?: number;\n}\n\nconst TaskContainer = styled.div<{ isDragging: boolean }>`\n  position: relative;\n  border-radius: 4px;\n  margin-left: 5px;\n  margin-bottom: 8px;\n  background-color: ${props => (props.isDragging ? '#eef' : '#333')};\n  transition: background 0.1s;\n`;\n\nconst Handle = styled.span`\n  display: flex;\n  margin-right: 5px;\n`;\n\nconst TaskDisplay = styled.div`\n  box-sizing: border-box;\n  width: 100%;\n  padding: 3px 0 3px 0;\n  margin-bottom: 2px;\n  background-color: inherit;\n  color: var(--vscode-tab-foreground);\n  border: 1px solid transparent;\n  font-family: inherit;\n`;\n\nconst StyledTextarea = styled(TextareaAutosize)`\n  resize: none;\n  box-sizing: border-box;\n  width: 100%;\n  background-color: inherit;\n  color: var(--vscode-tab-foreground);\n  border: 1px solid transparent;\n  margin-top: 1px;\n  margin-bottom: 3px;\n  font-family: inherit;\n`;\n\n// use \"svg, [data-type='action-icon']\" to hide the drag icon svg also.\nconst TaskWrapper = styled.div`\n  display: flex;\n  align-items: center;\n  background-color: var(--vscode-tab-inactiveBackground);\n  padding-right: 4px;\n  [draggable] {\n    margin: 0;\n  }\n  [data-type='action-icon'] {\n    visibility: hidden;\n  }\n  &:hover {\n    [data-type='action-icon'] {\n      visibility: visible;\n    }\n  }\n`;\n\nconst ActionWrapper = styled.div`\n  position: absolute;\n  right: 0;\n  padding: 2px;\n`;\n\nconst ActionIcon = styled.span`\n  font-size: 1em;\n  padding: 1px 5px;\n  margin: 0 2px;\n  cursor: pointer;\n  background-color: #555;\n  color: #eee;\n  border-radius: 4px;\n  &:hover {\n    background-color: #777;\n  }\n`;\n\nconst TickMark = styled.span`\n  font-size: 1em;\n  color: #ddd;\n  margin-right: 3px;\n`;\n\ninterface TaskProps {\n  column: any;\n  columnIndex: number;\n  task: TaskInterface;\n  index: number;\n  onChangeTitle: (title: string) => void;\n  onDelete: (task: TaskInterface) => void;\n  onInProgress: (task: TaskInterface) => void;\n  onComplete: (task: TaskInterface) => void;\n  onChangeTask: (id: string, task: TaskInterface) => void;\n}\n\nexport default memo(\n  ({\n    column,\n    columnIndex,\n    task,\n    index,\n    onChangeTitle,\n    onDelete,\n    onInProgress,\n    onComplete,\n    onChangeTask\n  }: TaskProps) => {\n    // mainKey is used to force re-render StyledTextarea as it doesn't auto re-render as expected.\n    const [mainKey, setMainKey] = React.useState('key_' + Math.random());\n    const [isEditing, setIsEditing] = React.useState(false);\n    const [menuActive, setMenuActive] = React.useState('');\n    const inputRef: React.RefObject<HTMLTextAreaElement> = React.createRef();\n\n    React.useEffect(() => {\n      // on did mount\n      if (window['isCreatingTask'] === true) {\n        // after clicking on \"+ New Task\" button => auto focus when creating task\n        window['isCreatingTask'] = false;\n        setIsEditing(true);\n      }\n    }, []);\n\n    React.useEffect(() => {\n      if (isEditing === true) {\n        // when editing, auto set the cursor at the end:\n        inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);\n      }\n    }, [isEditing]);\n\n    const isHidden = task.matched === false; // filtered by SearchInput's value\n    if (isHidden) {\n      return null;\n    }\n\n    // console.log('column.title', column.title);\n    return (\n      <Draggable draggableId={task.id} index={index}>\n        {(provided, snapshot) => (\n          <TaskContainer\n            {...provided.draggableProps}\n            // {...provided.dragHandleProps}\n            ref={provided.innerRef}\n            isDragging={snapshot.isDragging}\n          >\n            <TaskWrapper>\n              <Handle {...provided.dragHandleProps}>\n                <DragIcon />\n              </Handle>\n              <TickMark>{column.title.indexOf('✓') >= 0 ? '✓ ' : ''}</TickMark>\n\n              {isEditing ? (\n                <StyledTextarea\n                  placeholder=\"New Task\"\n                  autoFocus={true}\n                  key={mainKey}\n                  ref={inputRef}\n                  onKeyDown={ev => {\n                    if (ev.keyCode === 13) {\n                      ev.preventDefault(); // in this Textarea, ignore Enter Key. Otherwise, it will be a linebreak.\n                      // inputRef.current.blur();\n                      setIsEditing(false);\n                    }\n                    // TODO: Ctrl or Cmd Enter to add the next task?\n                  }}\n                  style={{ paddingLeft: task.level > 0 ? 10 : 0 }}\n                  onChange={(ev: any) => onChangeTitle(ev.target.value)}\n                  onFocus={() => {\n                    setIsEditing(true);\n                    setMenuActive('');\n                  }}\n                  onClick={() => {\n                    setMenuActive('');\n                  }}\n                  onBlur={() => {\n                    if (menuActive === '' || menuActive === 'MENU') {\n                      // e.g. if user is focusing in EMOJI menu, don't exit out of editing:\n                      setIsEditing(false);\n                    }\n                  }}\n                >\n                  {task.content}\n                </StyledTextarea>\n              ) : (\n                <TaskDisplay\n                  dangerouslySetInnerHTML={{ __html: parseInline(task.content || '&nbsp;') }}\n                  onClick={ev => {\n                    if (ev.target.tagName.toUpperCase() === 'A') {\n                      // user clicked on a hyperlink <a> tag, let it behaves normally.\n                    } else {\n                      setIsEditing(true);\n                    }\n                  }}\n                />\n              )}\n\n              {isEditing ? (\n                <ActionWrapper>\n                  <ActionIcon\n                    data-type=\"action-icon\"\n                    onMouseOver={() => setMenuActive('MENU')}\n                    onClick={() => {\n                      setMenuActive('');\n                      setIsEditing(false);\n                    }}\n                  >\n                    ☰\n                  </ActionIcon>\n                  {menuActive && (\n                    <TaskMenu\n                      task={task}\n                      menuActive={menuActive}\n                      setMenuActive={setMenuActive}\n                      onChangeTask={onChangeTask}\n                      setMainKey={setMainKey}\n                      setIsEditing={setIsEditing}\n                    />\n                  )}\n                </ActionWrapper>\n              ) : (\n                <ActionWrapper>\n                  {(!task.done || !column.isLast) && (\n                    <ActionIcon data-type=\"action-icon\" onClick={() => onInProgress(task)}>\n                      🡢\n                    </ActionIcon>\n                  )}\n                  {/* TODO: don't show Tick icon on the first column => need column index? */}\n                  {!task.done && (\n                    <ActionIcon data-type=\"action-icon\" onClick={() => onComplete(task)}>\n                      ✓\n                    </ActionIcon>\n                  )}\n                  <ActionIcon data-type=\"action-icon\" onClick={() => onDelete(task)}>\n                    ✕\n                  </ActionIcon>\n                </ActionWrapper>\n              )}\n            </TaskWrapper>\n          </TaskContainer>\n        )}\n      </Draggable>\n    );\n  }\n);\n"
  },
  {
    "path": "src/view/app/components/TaskBoard/TaskBoard.css",
    "content": ""
  },
  {
    "path": "src/view/app/components/TaskBoard/TaskBoard.tsx",
    "content": "import * as React from 'react';\nimport styled from 'styled-components';\nimport { CommandAction } from '../../model';\nimport { sendCommand, getVscodeHelper } from '../../Utils';\nimport { DragDropContext, Droppable } from 'react-beautiful-dnd';\nimport { parseMarkdown, defaultDataString, getMarkdown } from './Helpers';\n\nimport { TaskInterface } from './Task';\nimport TaskColumn, { ColumnInterface } from './TaskColumn';\nimport ButtonBar from './ButtonBar';\n\n// import '@atlaskit/css-reset';\nimport '../../index.css';\nimport './TaskBoard.css';\nconst { useState } = React;\n\nconst Columns = styled.div`\n  display: flex;\n`;\n\nconst selectedFile = (window && window['initialData'] ? window['initialData']['selectedFile'] : '') || 'TODO.md';\nconst fileArray = (window && window['initialData'] ? window['initialData']['fileList'] : 'TODO.md')\n  .split(',')\n  .map(str => str.trim());\nconst dataString = (window && window['initialData'] ? window['initialData']['dataString'] : '') || defaultDataString;\nlet data = parseMarkdown(dataString);\n\nexport default function TaskBoard({ vscode, initialData }) {\n  const [state, setState] = useState(data);\n  const vscodeHelper = getVscodeHelper(vscode);\n\n  const reloadFile = () => sendCommand(vscode, CommandAction.Load, selectedFile);\n\n  React.useEffect(() => {\n    reloadFile();\n  }, []);\n\n  const updateStateAndSave = newState => {\n    setState(newState);\n    vscodeHelper.saveList(getMarkdown(newState));\n  };\n\n  // const [msg, setMsg] = useState('');\n  // window.addEventListener('message', event => {\n  //   setMsg(JSON.stringify(event));\n  //   // const message = event.data; // The JSON data our extension sent\n  //   // switch (message.command) {\n  //   //     case 'load':\n  //   //       break;\n  //   // }\n  // });\n  return (\n    <div>\n      <ButtonBar\n        vscodeHelper={vscodeHelper}\n        fileArray={fileArray}\n        selectedFile={selectedFile}\n        data={state}\n        onLoadData={newData => {\n          data = newData;\n          setState(newData);\n        }}\n        onSave={dataStr => {\n          vscodeHelper.saveList(dataStr);\n        }}\n        onRefresh={() => reloadFile()}\n        onOpenFile={() => sendCommand(vscode, CommandAction.OpenFile, '')}\n        onSearch={searchTerm => {\n          const searchTermStr = searchTerm.toLowerCase();\n          // console.log('search: ', searchTerm);\n          const newState = { ...state };\n          Object.keys(newState.tasks).forEach(taskId => {\n            const t = newState.tasks[taskId];\n            newState.tasks[taskId].matched = t.content.toLowerCase().indexOf(searchTermStr) >= 0;\n          });\n          updateStateAndSave(newState);\n        }}\n        onSelectFile={selectedOpt => {\n          sendCommand(vscode, CommandAction.Load, selectedOpt.value);\n        }}\n      />\n      <DragDropContext\n        onDragEnd={({ destination, source, draggableId, type }) => {\n          if (!destination) {\n            return;\n          }\n          if (destination.droppableId === source.droppableId && destination.index === source.index) {\n            return;\n          }\n\n          if (type === 'column') {\n            const newColOrd = Array.from(state.columnOrder);\n            newColOrd.splice(source.index, 1);\n            newColOrd.splice(destination.index, 0, draggableId);\n\n            const newState = {\n              ...state,\n              columnOrder: newColOrd\n            };\n            updateStateAndSave(newState);\n            return;\n          }\n\n          const startcol = state.columns[source.droppableId];\n          const endcol = state.columns[destination.droppableId];\n\n          // console.log(\"startcol\", startcol);\n          // if (!startcol) {\n          //   return;\n          // }\n\n          if (startcol === endcol) {\n            const tasks = Array.from(startcol.taskIds);\n            tasks.splice(source.index, 1);\n            tasks.splice(destination.index, 0, draggableId);\n\n            const newCol = {\n              ...startcol,\n              taskIds: tasks\n            };\n\n            const newState = {\n              ...state,\n              columns: {\n                ...state.columns,\n                [newCol.id]: newCol\n              }\n            };\n\n            // setState(newState);\n            updateStateAndSave(newState);\n            return;\n          }\n          const startTaskIds = Array.from(startcol.taskIds);\n          startTaskIds.splice(source.index, 1);\n          const newStart = {\n            ...startcol,\n            taskIds: startTaskIds\n          };\n          const endTaskIds = Array.from(endcol.taskIds);\n          endTaskIds.splice(destination.index, 0, draggableId);\n          const newEnd = {\n            ...endcol,\n            taskIds: endTaskIds\n          };\n          const newState = {\n            ...state,\n            columns: {\n              ...state.columns,\n              [newStart.id]: newStart,\n              [newEnd.id]: newEnd\n            },\n            tasks: data.tasks\n          };\n          // setState(newState);\n          updateStateAndSave(newState);\n          // loadData(vscode, getMarkdown(newState)) // reload data to make sure it's reliable.\n        }}\n      >\n        <Droppable droppableId=\"columns\" direction=\"horizontal\" type=\"column\">\n          {provided => (\n            <Columns {...provided.droppableProps} ref={provided.innerRef}>\n              {state.columnOrder.map((id, idx) => {\n                const col = state.columns[id];\n                if (idx === Object.keys(state.columns).length - 1) {\n                  col.isLast = true;\n                } else {\n                  col.isLast = false;\n                }\n                const tasks = col.taskIds.map(taskid => state.tasks[taskid]);\n                return (\n                  <TaskColumn\n                    key={id}\n                    column={col}\n                    columnIndex={idx}\n                    tasks={tasks}\n                    onChangeTask={(id: string, newTask: TaskInterface) => {\n                      tasks[id] = newTask;\n                      const newState = {\n                        ...state,\n                        tasks: data.tasks\n                      };\n                      updateStateAndSave(newState);\n                    }}\n                    onDeleteTask={(task: TaskInterface, column: ColumnInterface) => {\n                      const newState = { ...state };\n                      delete newState.tasks[task.id];\n                      newState.columns[column.id].taskIds = newState.columns[column.id].taskIds.filter(\n                        (taskId: string) => taskId !== task.id\n                      );\n                      updateStateAndSave(newState);\n                    }}\n                    onInProgressTask={(task: TaskInterface, column: ColumnInterface) => {\n                      const newState = { ...state };\n                      const columnKeys = Object.keys(newState.columns);\n                      const currentColumnIdx = Object.keys(newState.columns).findIndex(\n                        (id: string) => id === column.id\n                      );\n                      const doneColumnKey = columnKeys[columnKeys.length - 1];\n                      const nextColumnKey = columnKeys[currentColumnIdx + 1];\n                      if (nextColumnKey === doneColumnKey) {\n                        task.done = true; // user moved this task to the right column and reached Done Column.\n                      }\n                      // remove task from current column:\n                      newState.columns[column.id].taskIds = newState.columns[column.id].taskIds.filter(\n                        (taskId: string) => taskId !== task.id\n                      );\n                      // append task to the next column:\n                      newState.columns[nextColumnKey].taskIds.unshift(task.id);\n                      updateStateAndSave(newState);\n                    }}\n                    onCompleteTask={(task: TaskInterface, column: ColumnInterface) => {\n                      task.done = true;\n                      const newState = { ...state };\n                      const columnKeys = Object.keys(newState.columns);\n                      const doneColumnKey = columnKeys[columnKeys.length - 1];\n                      // remove task from current column:\n                      newState.columns[column.id].taskIds = newState.columns[column.id].taskIds.filter(\n                        (taskId: string) => taskId !== task.id\n                      );\n                      // append task to the top of Done column:\n                      newState.columns[doneColumnKey].taskIds.unshift(task.id);\n                      updateStateAndSave(newState);\n                    }}\n                  />\n                );\n              })}\n              {provided.placeholder}\n            </Columns>\n          )}\n        </Droppable>\n      </DragDropContext>\n      {/* <pre>{msg}</pre> */}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/view/app/components/TaskBoard/TaskColumn.tsx",
    "content": "import * as React from 'react';\nimport styled from 'styled-components';\nimport { Droppable, Draggable } from 'react-beautiful-dnd';\nimport Task, { TaskInterface } from './Task';\n\nconst { memo } = React;\n\nconst Container = styled.div<{ isDragging: boolean }>`\n  min-height: 150px;\n  margin: 0px;\n  border-radius: 2px;\n  width: 33.3vw;\n  display: flex;\n  flex-direction: column;\n  background-color: ${props => (props.isDragging ? 'lightgreen' : 'inherit')};\n`;\nconst Title = styled.div`\n  padding: 5px;\n  margin: 5px;\n  > span {\n    padding: 5px;\n    background-color: var(--vscode-editor-selectionBackground);\n    border-radius: 2px;\n    color: var(--vscode-editor-selectionForeground);\n  }\n`; // or use: vscode-tab-activeBackground & vscode-tab-foreground\nconst List = styled.div<{ isDraggingOver: boolean }>`\n  padding: 5px;\n  transition: background 0.1s;\n  background-color: ${props => (props.isDraggingOver ? 'var(--vscode-tab-border)' : 'inherit ')};\n  flex-grow: 1;\n`;\n\nexport interface ColumnInterface {\n  id: string;\n  title: string;\n}\n\ninterface ColumnProps {\n  tasks: TaskInterface[];\n  columnIndex: number;\n  column: ColumnInterface;\n  onChangeTask: (idx: string, task: any) => void;\n  onDeleteTask: (task: TaskInterface, column: ColumnInterface) => void;\n  onInProgressTask: (task: TaskInterface, column: ColumnInterface) => void;\n  onCompleteTask: (task: TaskInterface, column: ColumnInterface) => void;\n}\n\nexport default memo(\n  ({ column, tasks, columnIndex, onChangeTask, onDeleteTask, onInProgressTask, onCompleteTask }: ColumnProps) => (\n    <Draggable draggableId={column.id} index={columnIndex}>\n      {(provided, snapshot) => (\n        <Container\n          // {...provided.draggableProps}\n          // {...provided.dragHandleProps}\n          isDragging={snapshot.isDragging}\n          ref={provided.innerRef}\n        >\n          <Title {...provided.dragHandleProps}>\n            <span>{column.title}</span>\n          </Title>\n          <Droppable droppableId={column.id} type=\"task\">\n            {(provided, snapshot) => (\n              <List ref={provided.innerRef} {...provided.droppableProps} isDraggingOver={snapshot.isDraggingOver}>\n                {tasks.map((t, i) => {\n                  if (!t || !t.id) {\n                    return null;\n                  }\n                  return (\n                    <Task\n                      key={t.id}\n                      column={column}\n                      columnIndex={columnIndex}\n                      task={t}\n                      index={i}\n                      onChangeTitle={(newTitle: string) => {\n                        t.content = newTitle;\n                        onChangeTask(t.id, t);\n                      }}\n                      onDelete={(task: TaskInterface) => onDeleteTask(task, column)}\n                      onInProgress={(task: TaskInterface) => onInProgressTask(task, column)}\n                      onComplete={(task: TaskInterface) => onCompleteTask(task, column)}\n                      onChangeTask={onChangeTask}\n                    />\n                  );\n                })}\n                {provided.placeholder}\n              </List>\n            )}\n          </Droppable>\n        </Container>\n      )}\n    </Draggable>\n  )\n);\n"
  },
  {
    "path": "src/view/app/components/TaskBoard/TaskMenu.tsx",
    "content": "import * as React from 'react';\nimport styled from 'styled-components';\n\nconst emojis = [\n  { text: '(bug)', char: '🐞', names: 'bug, ladybug, defect, issue, problem, error' },\n  // JIRA emojis\n  { text: ':)', char: '😊', names: 'smile, happy, glad' },\n  { text: ':(', char: '😟', names: 'sad, unhappy, cry' },\n  { text: ':P', char: '😛', names: 'tongue, fun' },\n  { text: ':D', char: '😄', names: 'broad smile, big open smile, amusemed, excited, happy' },\n  { text: ';)', char: '😉', names: 'wink' },\n  { text: '(y)', char: '👍', names: 'yes, thumb up, okay, agree, correct, good' },\n  { text: '(n)', char: '👎', names: 'no, thumb down, not okay, disagree, bad' },\n  { text: '(on)', char: '☑️', names: 'on, ticked, checked, yes, done' },\n  { text: '(off)', char: '🔲', names: 'off, unchecked, checkbox, empty box, square' },\n  { text: '(!)', char: '⚠️', names: 'warning, attention, dangerous, exclamation' },\n  { text: '(*)', char: '⭐', names: 'star, rate, favorite, favourite, award, reward' },\n  { text: '(/)', char: '✅', names: 'ticked, checked, yes, done, correct' },\n  { text: '(x)', char: '❌', names: 'crossed, deleted, blocked, wrong, incorrect, no' },\n  { text: '(i)', char: 'ℹ️', names: 'info, information, attention' },\n  { text: '(+)', char: '➕', names: 'plus, addition' },\n  { text: '(-)', char: '➖', names: 'minus, substraction' },\n  { text: '(?)', char: '❓', names: 'question mark, ask' },\n  { text: '<3', char: '💗', names: 'heart, pink heart, love, happy' },\n  { text: '</3', char: '💔', names: 'broken heart, unhappy, sad' },\n  // More emojis\n  { text: '(rocket)', char: '🚀', names: 'rocket, launch, delivered' },\n  { text: '(party)', char: '🎉', names: 'party, celebration, firework, excited, awesome, funs, congratulations' },\n  { text: '(sparkle)', char: '✨', names: 'sparkle' },\n  { text: '(hot)', char: '🔥', names: 'hot, fire, flame, burn' },\n  { text: '(fire)', char: '🔥', names: 'fire, hot, flame, burn' },\n  { text: '(burger)', char: '🍔', names: 'burger, food' },\n  { text: '(pizza)', char: '🍕', names: 'pizza, food' },\n  { text: '(hotdog)', char: '🌭', names: 'hotdog, food' },\n  { text: '(cake)', char: '🎂', names: 'cake, sweet' },\n  { text: '(cupcake)', char: '🧁', names: 'cupcake, sweet' },\n  { text: '(cookie)', char: '🍪', names: 'cookie, sweet' },\n  { text: '(candy)', char: '🍬', names: 'candy, sweet' },\n  { text: '(eat)', char: '🍽️', names: 'eat, dine, food, lunch, dinner' },\n  { text: '(icecream)', char: '🍦', names: 'icecream, sweet' },\n  { text: '(coffee)', char: '☕', names: 'coffee, cafe' },\n  { text: '(wine)', char: '🍷', names: 'wine, drink' },\n  { text: '(beer)', char: '🍺', names: 'beer, drink' },\n  { text: '(balloon)', char: '🎈', names: 'balloon' },\n  { text: '(rose)', char: '🌹', names: 'rose, flower' },\n  { text: '(soccer)', char: '⚽', names: 'soccer, sport, ball' },\n  { text: '(baseball)', char: '⚾', names: 'baseball, sport, ball' },\n  { text: '(tennis)', char: '🎾', names: 'tennis, sport' },\n  { text: '(football)', char: '🏈', names: 'football, sport' },\n  { text: '(basketball)', char: '🏀', names: 'basketball, sport' },\n  { text: '(car)', char: '🚗', names: 'car, travel, drive' },\n  { text: '(plane)', char: '✈️', names: 'plane, travel, fly, flight' },\n  { text: '(beach)', char: '🏖️', names: 'beach, relax, vacation' },\n  { text: '(sun)', char: '☀️', names: 'sun, weather' },\n  { text: '(moon)', char: '🌙', names: 'moon, night' },\n  { text: '(rain)', char: '🌧️', names: 'rain, weather' },\n  { text: '(snow)', char: '❄️', names: 'snow, weather' },\n  { text: '(chart)', char: '📈', names: 'chart, presentation, stock' },\n  { text: '(search)', char: '🔎', names: 'search, find, looking, research' }\n];\n\nconst MenuList = styled.ul`\n  list-style: none;\n  margin: 0;\n  padding: 0;\n  li {\n    margin: 0;\n    padding: 10px;\n    cursor: pointer;\n    &:hover {\n      background-color: #777;\n    }\n  }\n`;\n\nexport default function TaskMenu({ task, menuActive, setMenuActive, onChangeTask, setMainKey, setIsEditing }) {\n  const [searchText, setSearchText] = React.useState('');\n  const closeMenu = () => {\n    setMenuActive('');\n    setIsEditing(false);\n  };\n  return (\n    <div\n      style={{\n        position: 'absolute',\n        top: 25,\n        left: 5,\n        borderRadius: 4,\n        backgroundColor: 'var(--vscode-tab-border)',\n        width: 170,\n        minHeight: 100,\n        maxHeight: 250,\n        overflowX: 'hidden',\n        overflowY: 'auto',\n        fontFamily: 'Verdana',\n        fontSize: '0.8em',\n        zIndex: 1\n      }}\n    >\n      {menuActive === 'MENU' ? (\n        <MenuList>\n          <li\n            onMouseDown={ev => {\n              // can't use onClick as it will close the menu without coming here.\n              ev.preventDefault();\n              task.level = (task.level + 1) % 2;\n              onChangeTask(task.id, task);\n              closeMenu();\n            }}\n          >\n            Toggle Task / Sub-task\n          </li>\n          <li\n            onMouseDown={ev => {\n              // can't use onClick as it will close the menu without coming here.\n              ev.preventDefault();\n              setMenuActive('EMOJI');\n            }}\n          >\n            Insert Emoji Icon\n          </li>\n        </MenuList>\n      ) : (\n        <MenuList>\n          <input\n            autoFocus\n            placeholder=\"Search\"\n            value={searchText}\n            onChange={ev => setSearchText(ev.target.value)}\n            onKeyUp={ev => {\n              if (ev.keyCode === 27) {\n                closeMenu();\n              }\n            }}\n            style={{ width: '87%', margin: 5, padding: 3 }}\n          />\n          {emojis.map(emoji => {\n            const txt = searchText.trim().toLowerCase();\n            if (txt && emoji.names.toLowerCase().indexOf(txt) < 0) {\n              return null;\n            }\n            return (\n              <li\n                onMouseDown={ev => {\n                  ev.preventDefault();\n                  task.content += emoji.char;\n                  onChangeTask(task.id, task);\n                  closeMenu();\n                  setMainKey('key_' + Math.random());\n                }}\n              >\n                <span style={{ fontSize: '1.5em' }}>{emoji.char}</span> &nbsp;&nbsp;&nbsp; {emoji.text}\n              </li>\n            );\n          })}\n        </MenuList>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/view/app/config.tsx",
    "content": "import * as React from \"react\";\nimport { IConfig, IUser, ICommand, CommandAction } from \"./model\";\n\ninterface IConfigProps {\n  vscode: any;\n  initialData: IConfig;\n}\n\ninterface IConfigState {\n  config: IConfig;\n}\n\nexport default class Config extends React.Component<\n  IConfigProps,\n  IConfigState\n> {\n  constructor(props: any) {\n    super(props);\n\n    let initialData = this.props.initialData;\n\n    let oldState = this.props.vscode.getState();\n    if (oldState) {\n      this.state = oldState;\n    } else {\n      this.state = { config: initialData };\n    }\n  }\n\n  private defineState(newSate: IConfigState) {\n    this.setState(newSate);\n    this.props.vscode.setState(newSate);\n  }\n\n  onChangeUserActiveState(userIndex: number) {\n    let newState = { ...this.state };\n    newState.config.users[userIndex].active = !newState.config.users[userIndex]\n      .active;\n\n    this.defineState(newState);\n  }\n\n  onAddRole(event: React.KeyboardEvent<HTMLInputElement>, userIndex: number) {\n    if (event.keyCode === 13 && event.currentTarget.value !== \"\") {\n      let newState = { ...this.state };\n      newState.config.users[userIndex].roles.push(event.currentTarget.value);\n      this.defineState(newState);\n      event.currentTarget.value = \"\";\n    }\n  }\n\n  onAddUser(event: React.KeyboardEvent<HTMLInputElement>) {\n    if (event.keyCode === 13 && event.currentTarget.value !== \"\") {\n      let newState = { ...this.state };\n      let newUser: IUser = {\n        name: event.currentTarget.value,\n        active: true,\n        roles: []\n      };\n      newState.config.users.push(newUser);\n      this.defineState(newState);\n      event.currentTarget.value = \"\";\n    }\n  }\n\n  renderUsers(users: IUser[]) {\n    return (\n      <React.Fragment>\n        <h2>User List :</h2>\n        <ul className=\"\">\n          {users && users.length > 0\n            ? users.map((user, userIndex) => {\n                let roles =\n                  user.roles && user.roles.length > 0\n                    ? user.roles.join(\",\")\n                    : null;\n\n                return (\n                  <li key={userIndex}>\n                    {user.name}\n                    <br />\n                    Is active :{\" \"}\n                    <input\n                      type=\"checkbox\"\n                      checked={user.active}\n                      onChange={() => this.onChangeUserActiveState(userIndex)}\n                    />\n                    <br />\n                    Roles : {roles}\n                    <input\n                      type=\"text\"\n                      placeholder=\"Add Role\"\n                      onKeyUp={event => this.onAddRole(event, userIndex)}\n                    />\n                  </li>\n                );\n              })\n            : null}\n        </ul>\n        <input\n          type=\"text\"\n          placeholder=\"Add User\"\n          onKeyUp={event => this.onAddUser(event)}\n        />\n      </React.Fragment>\n    );\n  }\n\n  render() {\n    return (\n      <React.Fragment>\n        <h1>Config name : {this.state.config.name}</h1>{\" \"}\n        {this.state.config.description}\n        {this.renderUsers(this.state.config.users)}\n        <br />\n        <input\n          className=\"save\"\n          type=\"button\"\n          value=\"Save the configuration\"\n          onClick={() => this.saveConfig()}\n        />\n      </React.Fragment>\n    );\n  }\n\n  saveConfig() {\n    let command: ICommand = {\n      action: CommandAction.Save,\n      content: this.state.config\n    };\n    this.props.vscode.postMessage(command);\n  }\n}\n"
  },
  {
    "path": "src/view/app/index.css",
    "content": ":root {\n  /* --text-color: #eee; */\n  /* --bg-color: #1d1e1d; */\n\n  --button-bg-color: rgb(3, 0, 150);\n  --button-text-color: #777;\n}\nhtml,\nbody {\n  background-color: var(--bg-color); /* Dracula+ Dark Theme */\n}\n\na {\n  text-decoration: none;\n  color: darkslategray;\n}\n\nli {\n  margin-bottom: 15px;\n}\n\ninput {\n  margin-left: 10px;\n}\n\n.save {\n  margin-top: 10px;\n}\n\n.__reactSelect .__select__control {\n  background-color: var(--vscode-tab-background);\n  border: none;\n  margin-top: 5px;\n  min-height: 30px;\n}\n.__reactSelect .__select__input {\n  color: var(--button-text-color);\n}\n.__reactSelect .__select__single-value {\n  color: var(--button-text-color);\n  padding: 0;\n}\n.__reactSelect .__select__option {\n  color: var(--button-text-color);\n}\n.__reactSelect .__select__indicator-separator {\n  background-color: var(--vscode-tab-inactiveBackground);\n}\n.__reactSelect .__select__indicator {\n  color: var(--button-text-color);\n}\n.__reactSelect .__select__option--is-selected {\n  background-color: var(--vscode-tab-inactiveBackground);\n}\n"
  },
  {
    "path": "src/view/app/index.tsx",
    "content": "import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\n\nimport './index.css';\nimport { IConfig } from './model';\n// import Config from \"./config\";\n\nimport MainView from './components/CodeGen/MainView';\nimport TaskBoard from './components/TaskBoard/TaskBoard';\n\ndeclare global {\n  interface Window {\n    acquireVsCodeApi(): any;\n    initialData: IConfig;\n  }\n}\n\nconst vscode = window.acquireVsCodeApi();\n\nif (window.initialData.name === 'TaskBoard') {\n  ReactDOM.render(\n    <TaskBoard vscode={vscode} initialData={window.initialData} />,\n    document.getElementById('root')\n  );\n} else {\n  ReactDOM.render(\n    <MainView vscode={vscode} initialData={window.initialData} />,\n    document.getElementById('root')\n  );\n}\n"
  },
  {
    "path": "src/view/app/model.ts",
    "content": "export interface IConfig {\n  name: string;\n  description?: string;\n  users?: IUser[];\n}\nexport interface IUser {\n  name: string;\n  active: boolean;\n  roles: string[];\n}\n\nexport interface ICommand {\n  action: CommandAction;\n  content: IConfig;\n}\n\nexport enum CommandAction {\n  ShowMessage,\n  Save,\n  Load,\n  OpenFile,\n  GenerateFiles\n}\n"
  },
  {
    "path": "src/view/app/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"module\": \"esnext\",\n        \"moduleResolution\": \"node\",\n        \"target\": \"es6\",\n        \"outDir\": \"configViewer\",\n        \"lib\": [\n            \"es6\",\n            \"dom\"\n        ],  \n        \"jsx\": \"react\",\n        \"sourceMap\": true,\n        \"rootDir\": \"..\",\n        \"noUnusedLocals\": true,\n        \"noImplicitReturns\": true,\n        \"noFallthroughCasesInSwitch\": true,\n        \"experimentalDecorators\": true\n    },\n    \"exclude\": [\n        \"node_modules\"\n    ]\n}"
  },
  {
    "path": "src/webpack.config.js",
    "content": "const path = require(\"path\");\n\nmodule.exports = {\n  entry: {\n    configViewer: \"./src/view/app/index.tsx\"\n  },\n  output: {\n    path: path.resolve(__dirname, \"configViewer\"),\n    filename: \"[name].js\"\n  },\n  devtool: \"eval-source-map\",\n  resolve: {\n    extensions: [\".js\", \".ts\", \".tsx\", \".json\"]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(ts|tsx)$/,\n        loader: \"ts-loader\",\n        options: {}\n      },\n      {\n        test: /\\.css$/,\n        use: [\n          {\n            loader: \"style-loader\"\n          },\n          {\n            loader: \"css-loader\"\n          }\n        ]\n      }\n    ]\n  },\n  performance: {\n    hints: false\n  }\n};\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"module\": \"commonjs\",\n\t\t\"target\": \"es6\",\n\t\t\"outDir\": \"out\",\n\t\t\"lib\": [\n\t\t\t\"es6\", \"dom\"\n\t\t],\n\t\t\"sourceMap\": false,\n\t\t\"rootDir\": \"src\",\n\t\t// \"strict\": true   /* enable all strict type-checking options */\n\t\t/* Additional Checks */\n\t\t// \"noImplicitReturns\": true, /* Report error when not all code paths in function return a value. */\n\t\t// \"noFallthroughCasesInSwitch\": true, /* Report errors for fallthrough cases in switch statement. */\n\t\t// \"noUnusedParameters\": true,  /* Report errors on unused parameters. */\n\t},\n\t\"exclude\": [\n\t\t\"node_modules\",\n\t\t\".vscode-test\",\n\t\t\"**/view/app/index.css\",\n\t\t\"**/view/app/index.tsx\",\n\t\t\"**/view/app/tsconfig.json\",\n\t\t\"**/view/app/config.tsx\"\n\t]\n}\n"
  },
  {
    "path": "tslint.json",
    "content": "{\n\t\"rules\": {\n\t\t\"no-string-throw\": true,\n\t\t\"no-unused-expression\": true,\n\t\t\"no-duplicate-variable\": true,\n\t\t\"curly\": true,\n\t\t\"class-name\": true,\n\t\t\"semicolon\": [\n\t\t\ttrue,\n\t\t\t\"always\"\n\t\t],\n\t\t\"triple-equals\": true\n\t},\n\t\"defaultSeverity\": \"warning\"\n}\n"
  },
  {
    "path": "vsc-extension-quickstart.md",
    "content": "# Welcome to your VS Code Extension\n\n## What's in the folder\n\n* This folder contains all of the files necessary for your extension.\n* `package.json` - this is the manifest file in which you declare your extension and command.\n  * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.\n* `src/extension.ts` - this is the main file where you will provide the implementation of your command.\n  * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.\n  * We pass the function containing the implementation of the command as the second parameter to `registerCommand`.\n\n## Get up and running straight away\n\n* Press `F5` to open a new window with your extension loaded.\n* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.\n* Set breakpoints in your code inside `src/extension.ts` to debug your extension.\n* Find output from your extension in the debug console.\n\n## Make changes\n\n* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.\n* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.\n\n## Explore the API\n\n* You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`.\n\n## Run tests\n\n* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`.\n* Press `F5` to run the tests in a new window with your extension loaded.\n* See the output of the test result in the debug console.\n* Make changes to `test/extension.test.ts` or create new test files inside the `test` folder.\n  * By convention, the test runner will only consider files matching the name pattern `**.test.ts`.\n  * You can create folders inside the `test` folder to structure your tests any way you want.\n\n## Go further\n\n * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/testing-extension).\n * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace.\n * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require(\"path\");\n\nmodule.exports = {\n  entry: {\n    configViewer: \"./src/view/app/index.tsx\"\n  },\n  output: {\n    path: path.resolve(__dirname, \"configViewer\"),\n    filename: \"[name].js\"\n  },\n  devtool: \"eval-source-map\",\n  resolve: {\n    extensions: [\".js\", \".ts\", \".tsx\", \".json\"]\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(ts|tsx)$/,\n        loader: \"ts-loader\",\n        options: {}\n      },\n      {\n        test: /\\.css$/,\n        use: [\n          {\n            loader: \"style-loader\"\n          },\n          {\n            loader: \"css-loader\"\n          }\n        ]\n      }\n    ]\n  },\n  performance: {\n    hints: false\n  }\n};\n"
  }
]