Showing preview only (1,463K chars total). Download the full file or copy to clipboard to get everything.
Repository: hackjutsu/Lepton
Branch: master
Commit: a1b2c4f0ec26
Files: 201
Total size: 1.4 MB
Directory structure:
gitextract_qzfayoof/
├── .all-contributorsrc
├── .eslintignore
├── .eslintrc.js
├── .github/
│ └── ISSUE_TEMPLATE.md
├── .gitignore
├── .travis.yml
├── CLAUDE.md
├── LICENSE
├── README.md
├── app/
│ ├── actions/
│ │ └── index.js
│ ├── containers/
│ │ ├── aboutPage/
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── appContainer/
│ │ │ ├── index.js
│ │ │ ├── index.scss
│ │ │ └── scrollbar.scss
│ │ ├── codeArea/
│ │ │ ├── index.js
│ │ │ ├── jupyterNotebook.scss
│ │ │ └── markdown.scss
│ │ ├── dashboard/
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── gistEditor/
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── gistEditorForm/
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── loginPage/
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── navigationPanel/
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── navigationPanelDetails/
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── searchPage/
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── snippet/
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ ├── snippetPanel/
│ │ │ ├── index.js
│ │ │ └── index.scss
│ │ └── userPanel/
│ │ ├── index.js
│ │ └── index.scss
│ ├── index.js
│ ├── reducers/
│ │ ├── index.js
│ │ ├── reducer_about_modal.js
│ │ ├── reducer_active_gist.js
│ │ ├── reducer_active_gist_tag.js
│ │ ├── reducer_auth_window_status.js
│ │ ├── reducer_dashboard_modal.js
│ │ ├── reducer_file_expand_status.js
│ │ ├── reducer_gist_delete_modal.js
│ │ ├── reducer_gist_edit_modal.js
│ │ ├── reducer_gist_new_modal.js
│ │ ├── reducer_gist_raw_modal.js
│ │ ├── reducer_gist_sync_status.js
│ │ ├── reducer_gist_tags.js
│ │ ├── reducer_gists.js
│ │ ├── reducer_immersive_mode.js
│ │ ├── reducer_logout_modal.js
│ │ ├── reducer_new_version_info.js
│ │ ├── reducer_pinned_tags.js
│ │ ├── reducer_pinned_tags_selections_modal.js
│ │ ├── reducer_scroll_request_status.js
│ │ ├── reducer_search_window_status.js
│ │ ├── reducer_sync_time.js
│ │ ├── reducer_token.js
│ │ ├── reducer_update_available_bar_status.js
│ │ └── reducer_user_session.js
│ └── utilities/
│ ├── githubApi/
│ │ └── index.js
│ ├── jupyterNotebook/
│ │ └── index.js
│ ├── markdown/
│ │ └── index.js
│ ├── menu/
│ │ └── mainMenu.js
│ ├── notifier/
│ │ └── index.js
│ ├── parser/
│ │ └── index.js
│ ├── search/
│ │ └── index.js
│ ├── store/
│ │ └── index.js
│ ├── themeManager/
│ │ ├── index.js
│ │ └── themes/
│ │ ├── darkTheme.json
│ │ └── lightTheme.json
│ └── vendor/
│ ├── bootstrap/
│ │ └── css/
│ │ ├── bootstrap-theme.css
│ │ └── bootstrap.css
│ ├── highlightJS/
│ │ └── styles/
│ │ ├── agate.css
│ │ ├── androidstudio.css
│ │ ├── arduino-light.css
│ │ ├── arta.css
│ │ ├── ascetic.css
│ │ ├── atelier-cave-dark.css
│ │ ├── atelier-cave-light.css
│ │ ├── atelier-dune-dark.css
│ │ ├── atelier-dune-light.css
│ │ ├── atelier-estuary-dark.css
│ │ ├── atelier-estuary-light.css
│ │ ├── atelier-forest-dark.css
│ │ ├── atelier-forest-light.css
│ │ ├── atelier-heath-dark.css
│ │ ├── atelier-heath-light.css
│ │ ├── atelier-lakeside-dark.css
│ │ ├── atelier-lakeside-light.css
│ │ ├── atelier-plateau-dark.css
│ │ ├── atelier-plateau-light.css
│ │ ├── atelier-savanna-dark.css
│ │ ├── atelier-savanna-light.css
│ │ ├── atelier-seaside-dark.css
│ │ ├── atelier-seaside-light.css
│ │ ├── atelier-sulphurpool-dark.css
│ │ ├── atelier-sulphurpool-light.css
│ │ ├── atom-one-dark.css
│ │ ├── atom-one-light.css
│ │ ├── brown-paper.css
│ │ ├── codepen-embed.css
│ │ ├── color-brewer.css
│ │ ├── darcula.css
│ │ ├── dark.css
│ │ ├── darkula.css
│ │ ├── default.css
│ │ ├── docco.css
│ │ ├── dracula.css
│ │ ├── far.css
│ │ ├── foundation.css
│ │ ├── github-gist.css
│ │ ├── github.css
│ │ ├── googlecode.css
│ │ ├── grayscale.css
│ │ ├── gruvbox-dark.css
│ │ ├── gruvbox-light.css
│ │ ├── hopscotch.css
│ │ ├── hybrid.css
│ │ ├── idea.css
│ │ ├── ir-black.css
│ │ ├── kimbie.dark.css
│ │ ├── kimbie.light.css
│ │ ├── magula.css
│ │ ├── mono-blue.css
│ │ ├── monokai-sublime.css
│ │ ├── monokai.css
│ │ ├── obsidian.css
│ │ ├── ocean.css
│ │ ├── paraiso-dark.css
│ │ ├── paraiso-light.css
│ │ ├── pojoaque.css
│ │ ├── purebasic.css
│ │ ├── qtcreator_dark.css
│ │ ├── qtcreator_light.css
│ │ ├── railscasts.css
│ │ ├── rainbow.css
│ │ ├── school-book.css
│ │ ├── solarized-dark.css
│ │ ├── solarized-light.css
│ │ ├── sunburst.css
│ │ ├── tomorrow-night-blue.css
│ │ ├── tomorrow-night-bright.css
│ │ ├── tomorrow-night-eighties.css
│ │ ├── tomorrow-night.css
│ │ ├── tomorrow.css
│ │ ├── vs.css
│ │ ├── xcode.css
│ │ ├── xt256.css
│ │ └── zenburn.css
│ └── prism/
│ └── prism.scss
├── build/
│ └── icon.icns
├── configs/
│ ├── accountDummy.js
│ └── defaultConfig.js
├── docs/
│ ├── css/
│ │ └── freelancer.css
│ ├── index.html
│ ├── js/
│ │ ├── contact_me.js
│ │ ├── freelancer.js
│ │ └── jqBootstrapValidation.js
│ └── vendor/
│ ├── bootstrap/
│ │ ├── css/
│ │ │ └── bootstrap.css
│ │ └── js/
│ │ └── bootstrap.js
│ ├── font-awesome/
│ │ ├── css/
│ │ │ └── font-awesome.css
│ │ ├── fonts/
│ │ │ └── FontAwesome.otf
│ │ ├── less/
│ │ │ ├── animated.less
│ │ │ ├── bordered-pulled.less
│ │ │ ├── core.less
│ │ │ ├── fixed-width.less
│ │ │ ├── font-awesome.less
│ │ │ ├── icons.less
│ │ │ ├── larger.less
│ │ │ ├── list.less
│ │ │ ├── mixins.less
│ │ │ ├── path.less
│ │ │ ├── rotated-flipped.less
│ │ │ ├── screen-reader.less
│ │ │ ├── stacked.less
│ │ │ └── variables.less
│ │ └── scss/
│ │ ├── _animated.scss
│ │ ├── _bordered-pulled.scss
│ │ ├── _core.scss
│ │ ├── _fixed-width.scss
│ │ ├── _icons.scss
│ │ ├── _larger.scss
│ │ ├── _list.scss
│ │ ├── _mixins.scss
│ │ ├── _path.scss
│ │ ├── _rotated-flipped.scss
│ │ ├── _screen-reader.scss
│ │ ├── _stacked.scss
│ │ ├── _variables.scss
│ │ └── font-awesome.scss
│ └── jquery/
│ └── jquery.js
├── index.html
├── license.json
├── main.js
├── package.json
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .all-contributorsrc
================================================
{
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "hackjutsu",
"name": "CosmoX",
"avatar_url": "https://avatars3.githubusercontent.com/u/7756581?v=4",
"profile": "https://airbnb.io/",
"contributions": [
"code",
"design",
"test",
"maintenance",
"platform",
"ideas"
]
},
{
"login": "wujysh",
"name": "Jiaye Wu",
"avatar_url": "https://avatars1.githubusercontent.com/u/5550402?v=4",
"profile": "https://loveac.cn",
"contributions": [
"code",
"maintenance",
"ideas"
]
},
{
"login": "DNLHC",
"name": "Danila",
"avatar_url": "https://avatars1.githubusercontent.com/u/14959483?v=4",
"profile": "https://github.com/DNLHC",
"contributions": [
"code",
"design",
"maintenance"
]
},
{
"login": "meilinz",
"name": "Meilin Zhan",
"avatar_url": "https://avatars2.githubusercontent.com/u/13786673?v=4",
"profile": "http://www.meilinzhan.com/",
"contributions": [
"code",
"ideas",
"maintenance"
]
},
{
"login": "lcgforever",
"name": "lcgforever",
"avatar_url": "https://avatars1.githubusercontent.com/u/5697293?v=4",
"profile": "http://www.linkedin.com/in/liuchenguang",
"contributions": [
"code"
]
},
{
"login": "passerbyid",
"name": "Yuer Lee",
"avatar_url": "https://avatars1.githubusercontent.com/u/2075566?v=4",
"profile": "https://github.com/passerbyid",
"contributions": [
"doc",
"platform"
]
},
{
"login": "YYSU",
"name": "Su,Yen-Yun",
"avatar_url": "https://avatars3.githubusercontent.com/u/12994810?v=4",
"profile": "http://yysu.github.io/About-me",
"contributions": [
"doc"
]
},
{
"login": "cixuuz",
"name": "Chen Tong",
"avatar_url": "https://avatars3.githubusercontent.com/u/26782336?v=4",
"profile": "https://cixuuz.github.io/",
"contributions": [
"code",
"ideas",
"maintenance"
]
},
{
"login": "Gisonrg",
"name": "Jason Jiang",
"avatar_url": "https://avatars0.githubusercontent.com/u/4332224?v=4",
"profile": "https://github.com/Gisonrg",
"contributions": [
"code"
]
},
{
"login": "popey",
"name": "Alan Pope",
"avatar_url": "https://avatars0.githubusercontent.com/u/1841272?v=4",
"profile": "http://popey.com/",
"contributions": [
"platform"
]
},
{
"login": "tonyxu-io",
"name": "Tony Xu",
"avatar_url": "https://avatars3.githubusercontent.com/u/6280136?v=4",
"profile": "https://tonyxu.io",
"contributions": [
"platform"
]
},
{
"login": "rawrmonstar",
"name": "Tegan Churchill",
"avatar_url": "https://avatars0.githubusercontent.com/u/13814048?v=4",
"profile": "https://tegan.lol",
"contributions": [
"code"
]
},
{
"login": "AngieW0908",
"name": "Angie Wang",
"avatar_url": "https://avatars3.githubusercontent.com/u/26016229?v=4",
"profile": "https://github.com/AngieW0908",
"contributions": [
"design"
]
},
{
"login": "baybatu",
"name": "Batuhan Bayrakci",
"avatar_url": "https://avatars0.githubusercontent.com/u/965804?v=4",
"profile": "http://batuhanbayrakci.com",
"contributions": [
"code"
]
},
{
"login": "samuelmeuli",
"name": "Samuel Meuli",
"avatar_url": "https://avatars0.githubusercontent.com/u/22477950?v=4",
"profile": "https://samuelmeuli.com",
"contributions": [
"code"
]
},
{
"login": "alexandreamadocastro",
"name": "Alexandre Amado de Castro",
"avatar_url": "https://avatars2.githubusercontent.com/u/5918765?v=4",
"profile": "https://www.linkedin.com/in/alexandreamadocastro",
"contributions": [
"code"
]
},
{
"login": "abnersajr",
"name": "Abner Soares Alves Junior",
"avatar_url": "https://avatars2.githubusercontent.com/u/1998649?v=4",
"profile": "http://abner.space/",
"contributions": [
"code"
]
},
{
"login": "seancheung",
"name": "Sean",
"avatar_url": "https://avatars0.githubusercontent.com/u/5442563?v=4",
"profile": "http://seanz.me",
"contributions": [
"code"
]
},
{
"login": "moia-sven-ole",
"name": "Ole",
"avatar_url": "https://avatars0.githubusercontent.com/u/32508538?v=4",
"profile": "https://github.com/moia-sven-ole",
"contributions": [
"code"
]
},
{
"login": "GabrielNicolasAvellaneda",
"name": "Gabriel Nicolas Avellaneda",
"avatar_url": "https://avatars3.githubusercontent.com/u/1248101?v=4",
"profile": "https://www.linkedin.com/in/GabrielNicolasAvellaneda/",
"contributions": [
"code",
"doc"
]
},
{
"login": "dideler",
"name": "Dennis Ideler",
"avatar_url": "https://avatars2.githubusercontent.com/u/497458?v=4",
"profile": "https://dideler.github.io",
"contributions": [
"code",
"ideas",
"doc"
]
},
{
"login": "anthonyattard",
"name": "Anthony Attard",
"avatar_url": "https://avatars0.githubusercontent.com/u/8838135?v=4",
"profile": "http://AnthonyAttard.com",
"contributions": [
"code"
]
},
{
"login": "ArLEquiN64",
"name": "ArLE",
"avatar_url": "https://avatars1.githubusercontent.com/u/7821318?v=4",
"profile": "https://ArLEquiN64.github.io/",
"contributions": [
"code"
]
},
{
"login": "polnetwork",
"name": "Pol Maresma",
"avatar_url": "https://avatars1.githubusercontent.com/u/639877?v=4",
"profile": "http://www.polnetwork.com",
"contributions": [
"code"
]
},
{
"login": "PMExtra",
"name": "PM Extra",
"avatar_url": "https://avatars.githubusercontent.com/u/11289158?v=4",
"profile": "https://blog.jubeat.net",
"contributions": [
"code"
]
},
{
"login": "EdZava",
"name": "Zava",
"avatar_url": "https://avatars.githubusercontent.com/u/1155199?v=4",
"profile": "https://zava.carrd.co/",
"contributions": [
"code"
]
},
{
"login": "sunnysidesounds",
"name": "Jason R Alexander",
"avatar_url": "https://avatars.githubusercontent.com/u/1030838?v=4",
"profile": "http://www.linkedin.com/in/jasonralexander",
"contributions": [
"code"
]
},
{
"login": "Sebastian-Hojas",
"name": "Sebastian Hojas",
"avatar_url": "https://avatars.githubusercontent.com/u/279378?v=4",
"profile": "http://irrelevant.at",
"contributions": [
"doc"
]
},
{
"login": "yuhang-dong",
"name": "董雨航",
"avatar_url": "https://avatars.githubusercontent.com/u/20642641?v=4",
"profile": "https://github.com/yuhang-dong",
"contributions": [
"code"
]
},
{
"login": "sxyazi",
"name": "sxyazi",
"avatar_url": "https://avatars.githubusercontent.com/u/17523360?v=4",
"profile": "https://sxyz.blog",
"contributions": [
"platform"
]
},
{
"login": "ProfessorManhattan",
"name": "Brian Zalewski",
"avatar_url": "https://avatars.githubusercontent.com/u/59970525?v=4",
"profile": "https://megabyte.space",
"contributions": [
"platform"
]
}
],
"contributorsPerLine": 7,
"projectName": "Lepton",
"projectOwner": "hackjutsu",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true,
"commitType": "docs",
"commitConvention": "angular"
}
================================================
FILE: .eslintignore
================================================
dist
**/node_modules
**/webpack*.config.js
================================================
FILE: .eslintrc.js
================================================
module.exports = {
"extends": "standard",
"env": {
"browser": true,
"mocha": true,
"node": true
},
"rules": {
"indent": [2, 2, {"SwitchCase": 1}],
"no-new": 0,
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/react-in-jsx-scope": 2,
"react/sort-comp": 0,
"react/no-multi-comp": 0,
"comma-dangle": 0,
"id-length": 0,
"new-cap": 0,
"eol-last": 0,
"jsx-quotes": 0,
"consistent-return": 0,
"import/no-named-default": 0,
},
"parser": "babel-eslint",
"plugins": [
"standard",
"react"
],
};
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
The more we know about your system and use case, the more easily and likely we can help.
#### Environment info
* OS:
* App Version (`Command/Ctrl + ,`):
#### Description of the problem / feature request / question:
#### If possible, provide a sample gist or screenshot:
#### If possible, provide the log files
For Lepton after v1.9.0, the path of the log file can be found at `Command/Ctrl + ,`
- Mac: `~/Library/Application Support/Lepton/logs`
- Windows: `C:\Users\<username>\AppData\Roaming\Lepton\logs`
- Linux: `~/.cached/Lepton/logs`
>The [debug mode](https://github.com/hackjutsu/Lepton/wiki) is recommended when producing the logs.
>You can send your log file to cosmo.lepton@gmail.com if don't want to post it here.
================================================
FILE: .gitignore
================================================
node_modules
bundle
dist
.DS_Store
configs/account.js
npm-debug.log
test
screenshots
*backup*
.idea
.vscode
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "14"
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Lepton is a lean code snippet manager powered by GitHub Gist, built with Electron, React, and Redux. It provides a desktop application for managing and organizing code snippets with features like unlimited public/secret snippets, tagging, markdown/Jupyter notebook support, and GitHub Enterprise integration.
## Tech Stack
- **Framework**: Electron (desktop app)
- **Frontend**: React + Redux (with Redux Thunk for async actions, Redux Form for forms)
- **Build System**: Webpack + Babel (ES6 transpilation)
- **Styling**: Sass/SCSS
- **Code Editor**: CodeMirror (via react-codemirror)
- **Linting**: ESLint with Standard config
- **Dependencies**: Uses yarn package manager
## Key Commands
### Development
```bash
# Install dependencies
yarn install
# Development build and run
yarn build && yarn start
# Watch mode for development
yarn webpack-watch
# Production build
yarn webpack-prod
```
### Building & Distribution
```bash
# Create installer for current platform
yarn dist
# Platform-specific builds
yarn dist -m # macOS
yarn dist -w # Windows
yarn dist -l # Linux (requires Docker for snap)
yarn dist -wml # All platforms
```
### Code Quality
```bash
# Lint code
yarn lint
# Check for outdated dependencies
yarn check-outdated
# Pre-version checks (runs lint + test + outdated check)
yarn preversion
```
### Testing
The `yarn test` command runs webpack in development mode (essentially a build verification). There are no formal unit tests configured - the project relies on build-time checks and manual testing.
## Architecture
### Application Structure
- `/app` - Main React application source
- `/containers` - React container components (connected to Redux)
- `/reducers` - Redux reducers for state management
- `/actions` - Redux action creators
- `/utilities` - Shared utilities (GitHub API, parser, search, etc.)
- `/configs` - Configuration files including GitHub OAuth credentials
- `/main.js` - Electron main process entry point
- `/bundle` - Webpack build output directory
### Key Components
- **GitHub API Integration**: `/app/utilities/githubApi/` - handles Gist CRUD operations
- **Theme Management**: `/app/utilities/themeManager/` - light/dark theme switching
- **Code Rendering**: Uses CodeMirror for syntax highlighting and editing
- **Search**: `/app/utilities/search/` - snippet search functionality
- **Configuration**: Uses nconf for config management, stored in `~/.leptonrc`
### GitHub OAuth Setup
The app requires GitHub OAuth credentials in `/configs/account.js`:
```js
module.exports = {
client_id: '<your_client_id>',
client_secret: '<your_client_secret>'
}
```
Register your application at https://github.com/settings/applications/new
## Development Notes
- **Electron Version**: Uses Electron 13.x with @electron/remote for main-renderer communication
- **ES6 Support**: Babel transpiles ES6+ to support older Electron versions
- **Hot Reloading**: Use `yarn webpack-watch` for auto-rebuilding during development
- **Styling**: Uses Sass with component-level SCSS files
- **State Management**: Redux store handles application state, actions use Redux Thunk for async operations
- **Shortcuts**: Customizable keyboard shortcuts defined in config, registered via electron-localshortcut
## Configuration
The app uses a hierarchical configuration system:
1. Default config in `/configs/defaultConfig.js`
2. User config in `~/.leptonrc` (JSON format)
3. Environment variables and command line args
Key configuration areas include theme, shortcuts, proxy settings, editor preferences, and GitHub Enterprise support.
## Important Guidelines
When working with this codebase:
- **NEVER modify `node_modules/`** - this directory contains installed dependencies and should not be edited
- **DO NOT commit `yarn.lock`** unless specifically updating dependencies - this file locks dependency versions
- **DO NOT change LICENSE files** unless told
- Focus code changes on the `/app` directory, `/configs`, `main.js`, and configuration files
- Avoid searching or reading files in `node_modules/`, `/bundle`, `/build`, `/dist` directories unless absolutely necessary
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2017-present CosmoX
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->

[](https://opensource.org/licenses/MIT)
[](https://snapcraft.io/lepton)
**Lepton** is a lean code snippet manager powered by GitHub Gist. [Check out the latest release.](https://github.com/hackjutsu/Lepton/releases)
## Features
- Unlimited public/secret snippets
- Unlimited tags
- Language groups
- Markdown/JupyterNotebook
- [GitHub Enterprise](https://github.com/hackjutsu/Lepton/wiki/FAQ#enable-github-enterprise)
- GitHub token
- Immersive mode
- [Customizable](https://github.com/hackjutsu/Lepton/wiki/Configuration)
- Light/Dark theme
- macOS/Win/Linux
- Dashboard
- [Search](https://github.com/hackjutsu/Lepton/wiki/FAQ#search)
- [Proxy](https://github.com/hackjutsu/Lepton/wiki/FAQ#proxy)
- Free

| [Light Theme](https://github.com/hackjutsu/Lepton#customization) | [Dark Theme](https://github.com/hackjutsu/Lepton#customization) |
| :-------------:| :-----:|
|||
| Organize | Markdown | Jupyter Notebook |
| :-------------:| :-----:| :-----: |
|  |  |  |
| Search (*⇧ + Space*) | Immersive Mode *(⌘/Ctrl + i)* | Dashboard *(⌘/Ctrl + d)* |
| :-------------:| :-----:| :-----: |
|  |  | 
## Shortcuts
| Function | Shortcut | Note |
| :------------: |:-------------: |:-----:|
| New Snippet | `Cmd/Ctrl + N` | Create a snippet |
| Edit Snippet | `Cmd/Ctrl + E` | Edit a snippet |
| Delete Snippet | `Cmd/Ctrl + Del` | Delete selected snippet |
| Submit | `Cmd/Ctrl + S` | Submit the changes from the editor |
| Cancel | `Cmd/Ctrl + ESC` | Exit the editor without saving |
| Sync | `Cmd/Ctrl + R` | Sync with remote Gist server |
| Immersive Mode | `Cmd/Ctrl + I` | Toggle the [Immersive mode](https://github.com/hackjutsu/Lepton/blob/master/docs/img/portfolio/immersive.png) |
| Dashboard | `Cmd/Ctrl + D` | Toggle the [dashboard](https://github.com/hackjutsu/Lepton/blob/master/docs/img/portfolio/dashboard.png) |
| About Page | `Cmd/Ctrl + ,` | Toggle the [About page](https://github.com/hackjutsu/Lepton/blob/dev/docs/img/portfolio/about.png) |
| Search | `Shift + Space`| Toggle the [search bar](https://github.com/hackjutsu/Lepton/blob/master/docs/img/portfolio/search_bar.png) |
## Customization
Lepton's can be customized by `<home_dir>/.leptonrc`! You can find its exact path in the About page by `Command/Ctrl + ,`. Create the file if it does not exist.
- Theme (light/dark)
- Snippet
- Editor
- Logger
- Proxy
- Shortcuts
- Enterprise
- Notifications
Check out the [configuration docs](https://github.com/hackjutsu/Lepton/wiki/Configuration) to explore different customization options.
## Tech Stack

1. Framework: [Electron](http://electron.atom.io/)
2. Bundler: [Webpack](http://webpack.github.io/docs/), [Babel](https://babeljs.io), [electron-builder](https://github.com/electron-userland/electron-builder)
3. Language: [ES6](https://babeljs.io/docs/learn-es2015/), [Sass](http://sass-lang.com/)
4. Library: [React](https://facebook.github.io/react/), [Redux](https://github.com/reactjs/redux), [Redux Thunk](https://github.com/gaearon/redux-thunk), [Redux Form](http://redux-form.com/)
5. Lint: [ESLint](http://eslint.org/)
Now you can learn about Lepton project's code structure in [DeepWiki](https://deepwiki.com/hackjutsu/Lepton)!
## Installation
- macOS/Windows/Linux: Download [the released packages](https://github.com/hackjutsu/Lepton/releases)
- macOS: Install via Homebrew
```bash
brew install --cask lepton
```
- Linux: Install via [Snap Store](https://snapcraft.io/lepton)
```bash
snap install lepton
```

## Development
### Install dependencies
```bash
$ git clone https://github.com/hackjutsu/Lepton.git
$ cd Lepton && yarn install
```
```bash
# inspect stale dependencies
$ yarn check-outdated
```
### Client ID/Secret
[Register your application](https://github.com/settings/applications/new), and put your client id and client secret in `./configs/account.js`.
```js
module.exports = {
client_id: <your_client_id>,
client_secret: <your_client_secret>
}
```
### Run
```bash
$ yarn build && yarn start
```
## Build Installer App
>Read [electron-builder docs](https://github.com/electron-userland/electron-builder#readme) and check out the [code signing wiki](https://github.com/electron-userland/electron-builder#code-signing) before building the installer app.
Build apps for macOS.
```bash
$ yarn dist -m
```
Build apps for Windows.
```bash
$ yarn dist -w
```
Build apps for Linux.
>Need a running [Docker](https://www.docker.com/) daemon to build a `snap` package.
```bash
$ yarn dist -l
```
Build apps for macOS, Windows and Linux.
```bash
$ yarn dist -wml
```
Build apps for the current OS with the current arch.
```bash
$ yarn dist
```
## FAQ
[--> Wiki FAQ](https://github.com/hackjutsu/Lepton/wiki/FAQ)
## Contributors
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://airbnb.io/"><img src="https://avatars3.githubusercontent.com/u/7756581?v=4?s=100" width="100px;" alt="CosmoX"/><br /><sub><b>CosmoX</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=hackjutsu" title="Code">💻</a> <a href="#design-hackjutsu" title="Design">🎨</a> <a href="https://github.com/hackjutsu/Lepton/commits?author=hackjutsu" title="Tests">⚠️</a> <a href="#maintenance-hackjutsu" title="Maintenance">🚧</a> <a href="#platform-hackjutsu" title="Packaging/porting to new platform">📦</a> <a href="#ideas-hackjutsu" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://loveac.cn"><img src="https://avatars1.githubusercontent.com/u/5550402?v=4?s=100" width="100px;" alt="Jiaye Wu"/><br /><sub><b>Jiaye Wu</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=wujysh" title="Code">💻</a> <a href="#maintenance-wujysh" title="Maintenance">🚧</a> <a href="#ideas-wujysh" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DNLHC"><img src="https://avatars1.githubusercontent.com/u/14959483?v=4?s=100" width="100px;" alt="Danila"/><br /><sub><b>Danila</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=DNLHC" title="Code">💻</a> <a href="#design-DNLHC" title="Design">🎨</a> <a href="#maintenance-DNLHC" title="Maintenance">🚧</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.meilinzhan.com/"><img src="https://avatars2.githubusercontent.com/u/13786673?v=4?s=100" width="100px;" alt="Meilin Zhan"/><br /><sub><b>Meilin Zhan</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=meilinz" title="Code">💻</a> <a href="#ideas-meilinz" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-meilinz" title="Maintenance">🚧</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.linkedin.com/in/liuchenguang"><img src="https://avatars1.githubusercontent.com/u/5697293?v=4?s=100" width="100px;" alt="lcgforever"/><br /><sub><b>lcgforever</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=lcgforever" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/passerbyid"><img src="https://avatars1.githubusercontent.com/u/2075566?v=4?s=100" width="100px;" alt="Yuer Lee"/><br /><sub><b>Yuer Lee</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=passerbyid" title="Documentation">📖</a> <a href="#platform-passerbyid" title="Packaging/porting to new platform">📦</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://yysu.github.io/About-me"><img src="https://avatars3.githubusercontent.com/u/12994810?v=4?s=100" width="100px;" alt="Su,Yen-Yun"/><br /><sub><b>Su,Yen-Yun</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=YYSU" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://cixuuz.github.io/"><img src="https://avatars3.githubusercontent.com/u/26782336?v=4?s=100" width="100px;" alt="Chen Tong"/><br /><sub><b>Chen Tong</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=cixuuz" title="Code">💻</a> <a href="#ideas-cixuuz" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-cixuuz" title="Maintenance">🚧</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Gisonrg"><img src="https://avatars0.githubusercontent.com/u/4332224?v=4?s=100" width="100px;" alt="Jason Jiang"/><br /><sub><b>Jason Jiang</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=Gisonrg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://popey.com/"><img src="https://avatars0.githubusercontent.com/u/1841272?v=4?s=100" width="100px;" alt="Alan Pope"/><br /><sub><b>Alan Pope</b></sub></a><br /><a href="#platform-popey" title="Packaging/porting to new platform">📦</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://tonyxu.io"><img src="https://avatars3.githubusercontent.com/u/6280136?v=4?s=100" width="100px;" alt="Tony Xu"/><br /><sub><b>Tony Xu</b></sub></a><br /><a href="#platform-tonyxu-io" title="Packaging/porting to new platform">📦</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://tegan.lol"><img src="https://avatars0.githubusercontent.com/u/13814048?v=4?s=100" width="100px;" alt="Tegan Churchill"/><br /><sub><b>Tegan Churchill</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=rawrmonstar" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AngieW0908"><img src="https://avatars3.githubusercontent.com/u/26016229?v=4?s=100" width="100px;" alt="Angie Wang"/><br /><sub><b>Angie Wang</b></sub></a><br /><a href="#design-AngieW0908" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://batuhanbayrakci.com"><img src="https://avatars0.githubusercontent.com/u/965804?v=4?s=100" width="100px;" alt="Batuhan Bayrakci"/><br /><sub><b>Batuhan Bayrakci</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=baybatu" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://samuelmeuli.com"><img src="https://avatars0.githubusercontent.com/u/22477950?v=4?s=100" width="100px;" alt="Samuel Meuli"/><br /><sub><b>Samuel Meuli</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=samuelmeuli" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/alexandreamadocastro"><img src="https://avatars2.githubusercontent.com/u/5918765?v=4?s=100" width="100px;" alt="Alexandre Amado de Castro"/><br /><sub><b>Alexandre Amado de Castro</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=alexandreamadocastro" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://abner.space/"><img src="https://avatars2.githubusercontent.com/u/1998649?v=4?s=100" width="100px;" alt="Abner Soares Alves Junior"/><br /><sub><b>Abner Soares Alves Junior</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=abnersajr" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://seanz.me"><img src="https://avatars0.githubusercontent.com/u/5442563?v=4?s=100" width="100px;" alt="Sean"/><br /><sub><b>Sean</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=seancheung" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/moia-sven-ole"><img src="https://avatars0.githubusercontent.com/u/32508538?v=4?s=100" width="100px;" alt="Ole"/><br /><sub><b>Ole</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=moia-sven-ole" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/GabrielNicolasAvellaneda/"><img src="https://avatars3.githubusercontent.com/u/1248101?v=4?s=100" width="100px;" alt="Gabriel Nicolas Avellaneda"/><br /><sub><b>Gabriel Nicolas Avellaneda</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=GabrielNicolasAvellaneda" title="Code">💻</a> <a href="https://github.com/hackjutsu/Lepton/commits?author=GabrielNicolasAvellaneda" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://dideler.github.io"><img src="https://avatars2.githubusercontent.com/u/497458?v=4?s=100" width="100px;" alt="Dennis Ideler"/><br /><sub><b>Dennis Ideler</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=dideler" title="Code">💻</a> <a href="#ideas-dideler" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/hackjutsu/Lepton/commits?author=dideler" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://AnthonyAttard.com"><img src="https://avatars0.githubusercontent.com/u/8838135?v=4?s=100" width="100px;" alt="Anthony Attard"/><br /><sub><b>Anthony Attard</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=anthonyattard" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ArLEquiN64.github.io/"><img src="https://avatars1.githubusercontent.com/u/7821318?v=4?s=100" width="100px;" alt="ArLE"/><br /><sub><b>ArLE</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=ArLEquiN64" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.polnetwork.com"><img src="https://avatars1.githubusercontent.com/u/639877?v=4?s=100" width="100px;" alt="Pol Maresma"/><br /><sub><b>Pol Maresma</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=polnetwork" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://blog.jubeat.net"><img src="https://avatars.githubusercontent.com/u/11289158?v=4?s=100" width="100px;" alt="PM Extra"/><br /><sub><b>PM Extra</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=PMExtra" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://zava.carrd.co/"><img src="https://avatars.githubusercontent.com/u/1155199?v=4?s=100" width="100px;" alt="Zava"/><br /><sub><b>Zava</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=EdZava" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.linkedin.com/in/jasonralexander"><img src="https://avatars.githubusercontent.com/u/1030838?v=4?s=100" width="100px;" alt="Jason R Alexander"/><br /><sub><b>Jason R Alexander</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=sunnysidesounds" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://irrelevant.at"><img src="https://avatars.githubusercontent.com/u/279378?v=4?s=100" width="100px;" alt="Sebastian Hojas"/><br /><sub><b>Sebastian Hojas</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=Sebastian-Hojas" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yuhang-dong"><img src="https://avatars.githubusercontent.com/u/20642641?v=4?s=100" width="100px;" alt="董雨航"/><br /><sub><b>董雨航</b></sub></a><br /><a href="https://github.com/hackjutsu/Lepton/commits?author=yuhang-dong" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://sxyz.blog"><img src="https://avatars.githubusercontent.com/u/17523360?v=4?s=100" width="100px;" alt="sxyazi"/><br /><sub><b>sxyazi</b></sub></a><br /><a href="#platform-sxyazi" title="Packaging/porting to new platform">📦</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://megabyte.space"><img src="https://avatars.githubusercontent.com/u/59970525?v=4?s=100" width="100px;" alt="Brian Zalewski"/><br /><sub><b>Brian Zalewski</b></sub></a><br /><a href="#platform-ProfessorManhattan" title="Packaging/porting to new platform">📦</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
## License
MIT © [hackjutsu](https://github.com/hackjutsu)
================================================
FILE: app/actions/index.js
================================================
import { getGitHubApi, GET_SINGLE_GIST } from '../utilities/githubApi'
import { notifyFailure } from '../utilities/notifier'
import { remote } from 'electron'
const logger = remote.getGlobal('logger')
export const UPDATE_USER_SESSION = 'UPDATE_USER_SESSION'
export const LOGOUT_USER_SESSION = 'LOGOUT_USER_SESSION'
export const UPDATE_ACCESS_TOKEN = 'UPDATE_ACCESS_TOKEN'
export const REMOVE_ACCESS_TOKEN = 'REMOVE_ACCESS_TOKEN'
export const UPDATE_GISTS = 'UPDATE_GISTS'
export const UPDATE_SYNC_TIME = 'UPDATE_SYNC_TIME'
export const UPDATE_SINGLE_GIST = 'UPDATE_SINGLE_GIST'
export const UPDATE_GIST_TAGS = 'UPDATE_GIST_TAGS'
export const UPDATE_PINNED_TAGS = 'UPDATE_PINNED_TAGS'
export const SELECT_GIST_TAG = 'SELECT_GIST_TAG'
export const SELECT_GIST = 'SELECT_GIST'
export const UPDATE_AUTHWINDOW_STATUS = 'UPDATE_AUTHWINDOW_STATUS'
export const UPDATE_GIST_SYNC_STATUS = 'UPDATE_GIST_SYNC_STATUS'
export const UPDATE_SEARCHWINDOW_STATUS = 'UPDATE_SEARCHWINDOW_STATUS'
export const UPDATE_SCROLL_REQUEST_STATUS = 'UPDATE_SCROLL_REQUEST_STATUS'
export const UPDATE_UPDATEAVAILABLEBAR_STATUS = 'UPDATE_UPDATEAVAILABLEBAR_STATUS'
export const UPDATE_NEW_VERSION_INFO = 'UPDATE_NEW_VERSION_INFO'
export const UPDATE_IMMERSIVE_MODE_STATUS = 'UPDATE_IMMERSIVE_MODE_STATUS'
export const UPDATE_LOGOUT_MODAL_STATUS = 'UPDATE_LOGOUT_MODAL_STATUS'
export const UPDATE_GIST_RAW_MODAL = 'UPDATE_GIST_RAW_MODAL'
export const UPDATE_GIST_EDIT_MODAL = 'UPDATE_GIST_EDIT_MODAL'
export const UPDATE_GIST_NEW_MODAL = 'UPDATE_GIST_NEW_MODAL'
export const UPDATE_GIST_DELETE_MODAL_STATUS = 'UPDATE_GIST_DELETE_MODAL_STATUS'
export const UPDATE_PINNED_TAGS_MODAL_STATUS = 'UPDATE_PINNED_TAGS_MODAL_STATUS'
export const UPDATE_FILE_EXPAND_STATUS = 'UPDATE_FILE_EXPAND_STATUS'
export const UPDATE_DASHBOARD_MODAL_STATUS = 'UPDATE_DASHBOARD_MODAL_STATUS'
export const UPDATE_ABOUT_MODAL_STATUS = 'UPDATE_ABOUT_MODAL_STATUS'
export function updateDashboardModalStatus (status) {
return {
type: UPDATE_DASHBOARD_MODAL_STATUS,
payload: status
}
}
export function updateAboutModalStatus (status) {
return {
type: UPDATE_ABOUT_MODAL_STATUS,
payload: status
}
}
export function updateFileExpandStatus (status) {
return {
type: UPDATE_FILE_EXPAND_STATUS,
payload: status
}
}
export function updatePinnedTagsModalStatus (status) {
return {
type: UPDATE_PINNED_TAGS_MODAL_STATUS,
payload: status
}
}
export function updateGistDeleteModeStatus (status) {
return {
type: UPDATE_GIST_DELETE_MODAL_STATUS,
payload: status
}
}
export function updateGistEditModeStatus (status) {
return {
type: UPDATE_GIST_EDIT_MODAL,
payload: status
}
}
export function updateGistNewModeStatus (status) {
return {
type: UPDATE_GIST_NEW_MODAL,
payload: status
}
}
export function updateGistRawModal (modalInfo) {
return {
type: UPDATE_GIST_RAW_MODAL,
payload: modalInfo
}
}
export function updateImmersiveModeStatus (status) {
return {
type: UPDATE_IMMERSIVE_MODE_STATUS,
payload: status
}
}
export function updateNewVersionInfo (status) {
return {
type: UPDATE_NEW_VERSION_INFO,
payload: status
}
}
export function updateUpdateAvailableBarStatus (status) {
return {
type: UPDATE_UPDATEAVAILABLEBAR_STATUS,
payload: status
}
}
export function updateGistSyncStatus (status) {
return {
type: UPDATE_GIST_SYNC_STATUS,
payload: status
}
}
export function updateAuthWindowStatus (status) {
return {
type: UPDATE_AUTHWINDOW_STATUS,
payload: status
}
}
export function updateSearchWindowStatus (status) {
return {
type: UPDATE_SEARCHWINDOW_STATUS,
payload: status
}
}
export function updatescrollRequestStatus (status) {
return {
type: UPDATE_SCROLL_REQUEST_STATUS,
payload: status
}
}
export function updateAccessToken (token) {
return {
type: UPDATE_ACCESS_TOKEN,
payload: token
}
}
export function removeAccessToken () {
return {
type: REMOVE_ACCESS_TOKEN,
payload: null
}
}
export function updateUserSession (session) {
return {
type: UPDATE_USER_SESSION,
payload: session
}
}
export function logoutUserSession () {
return {
type: LOGOUT_USER_SESSION,
payload: null
}
}
export function updateGists (gists) {
return {
type: UPDATE_GISTS,
payload: gists
}
}
export function updateSyncTime (time) {
return {
type: UPDATE_SYNC_TIME,
payload: time
}
}
export function updateSingleGist (gist) {
return {
type: UPDATE_SINGLE_GIST,
payload: gist
}
}
export function updateGistTags (tags) {
return {
type: UPDATE_GIST_TAGS,
payload: tags
}
}
export function updatePinnedTags (tags) {
return {
type: UPDATE_PINNED_TAGS,
payload: tags
}
}
export function selectGistTag (tag) {
return {
type: SELECT_GIST_TAG,
payload: tag
}
}
export function selectGist (id) {
return {
type: SELECT_GIST,
payload: id
}
}
export function updateLogoutModalStatus (status) {
return {
type: UPDATE_LOGOUT_MODAL_STATUS,
payload: status
}
}
export function fetchSingleGist (oldGist, id) {
return (dispatch, getState) => {
const state = getState()
return getGitHubApi(GET_SINGLE_GIST)(state.accessToken, id)
.then((details) => {
const newGist = Object.assign(oldGist, { details: details })
const newGistWithId = {}
newGistWithId[id] = newGist
dispatch(updateSingleGist(newGistWithId))
})
.catch((err) => {
logger.error('The request has failed: ' + err)
notifyFailure('Sync failed', 'Please check your network condition. 01')
})
}
}
================================================
FILE: app/containers/aboutPage/index.js
================================================
import fs from 'fs'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Modal, Image } from 'react-bootstrap'
import { remote, shell } from 'electron'
import { updateAboutModalStatus } from '../../actions'
import defaultConfig from '../../../configs/defaultConfig'
import appInfo from '../../../package.json'
import LicenseInfo from '../../../license.json'
import ContributorInfo from '../../../.all-contributorsrc'
import logoDarkImage from './logo-dark.png'
import logoLightImage from './logo-light.png'
import React, { Component } from 'react'
import './index.scss'
const conf = remote.getGlobal('conf')
const logFilePath = remote.getGlobal('logFilePath')
const configFilePath = remote.getGlobal('configFilePath')
class AboutPage extends Component {
openFileInEditor (filePath) {
if (!fs.existsSync(filePath)) {
fs.closeSync(fs.writeFileSync(filePath, JSON.stringify(defaultConfig, null, 2)))
}
shell.openPath(filePath)
}
renderAboutSection () {
// Get the contributor list
const contributorList = []
const contributors = ContributorInfo.contributors || [] // just in case the format changed
contributors.forEach(item => {
const contributorProfileLink = `https://github.com/${item.login}`
contributorList.push(
<div key={ item.login } className='contributor'>
<a href={ contributorProfileLink }>{ item.login }</a>
</div>
)
})
// Get the license list
const licenseList = []
// Add Evil icons license as an exception
licenseList.push(
<div key ='Octodex Images' className='license-item'>
<div className='license-project'>Octodex Images</div>
<div className='license-type'>octodex.github.com</div>
</div>,
<div key ='Evil icons@1.9.0' className='license-item'>
<div className='license-project'>Evil icons@1.9.0</div>
<div className='license-type'>License: MIT</div>
</div>
)
Object.keys(LicenseInfo).forEach(item => {
if (item.startsWith(appInfo.name)) {
return
}
licenseList.push(
<div key={ item } className='license-item'>
<div className='license-project'>
{ item }
</div>
<div className='license-type'>License: { LicenseInfo[item].licenses }</div>
</div>
)
})
const logoImage = conf.get('theme') === 'dark' ? logoDarkImage : logoLightImage
return (
<div className='about-section'>
<div className='logo-section'>
<Image className='logo' src={ logoImage } rounded/>
<div>{ appInfo.name + ' v' + appInfo.version }</div>
<a className='logo-sub' href='https://github.com/hackjutsu/Lepton'>GitHub</a>
<a className='logo-sub' href='https://github.com/hackjutsu/Lepton/issues'>Feedback</a>
<a className='logo-sub' href='https://github.com/hackjutsu/Lepton/blob/master/LICENSE'>License</a>
</div>
<div className='setting-title-clickable' onClick={ this.openFileInEditor.bind(this, configFilePath) }>
Configurations
</div>
<div className='one-line-section'>{ configFilePath }</div>
<div className='setting-title-clickable' onClick={ this.openFileInEditor.bind(this, logFilePath) }>Logs</div>
<div className='one-line-section'>{ logFilePath }</div>
<div className='setting-title'>Contributors</div>
<div className='contributor-section'>
{ contributorList }
</div>
<div className='setting-title'>Acknowledgement</div>
<div className='license-section'>
{ licenseList }
</div>
</div>
)
}
renderSettingModalBody () {
return (
<div>
{ this.renderAboutSection() }
</div>
)
}
handleCloseButtonClicked () {
const { updateAboutModalStatus } = this.props
updateAboutModalStatus('OFF')
}
render () {
return (
<Modal
className='about-modal'
bsSize='small'
show={ this.props.aboutModalStatus === 'ON' }
onHide={ this.handleCloseButtonClicked.bind(this) }>
<Modal.Header closeButton>
<Modal.Title>About</Modal.Title>
</Modal.Header>
<Modal.Body>
{ this.renderSettingModalBody() }
</Modal.Body>
</Modal>
)
}
}
function mapStateToProps (state) {
return {
aboutModalStatus: state.aboutModalStatus
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
updateAboutModalStatus: updateAboutModalStatus
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(AboutPage)
================================================
FILE: app/containers/aboutPage/index.scss
================================================
.about-modal {
.font-style-base {
font-family: Consolas, Menlo, Monaco, "Courier New", monospace;
font-size: 13px;
color: var(--text-secondary);
}
.section-base {
height: 80vh;
}
.general-section {
@extend .section-base;
}
.about-section {
@extend .section-base;
display: flex;
flex-direction: column;
color: var(--text-secondary);
}
.logo-section {
@extend .font-style-base;
font-size: 12px;
text-align: center;
margin-bottom: 10px;
}
.logo {
width: 100px;
margin: 5px;
}
.contributor-section {
overflow: auto;
min-height: 150px;
border-radius: 5px;
padding: 0 20px;
border: solid 1px var(--border-color);
color: var(--text-secondary);
}
.license-section {
overflow: auto;
flex: auto;
margin-bottom: 20px;
border-radius: 5px;
padding: 0 20px;
border: solid 1px var(--border-color);
color: var(--text-secondary);
}
.one-line-section {
overflow: auto;
min-height: 30px;
border-radius: 5px;
padding: 4px 10px 0px 10px;
white-space: nowrap;
border: solid 1px var(--border-color);
color: var(--text-secondary);
}
.license-item {
margin: 10px 0px;
}
.license-project {
@extend .font-style-base;
font-weight: bold;
}
.license-type {
@extend .font-style-base;
font-size: 11px;
}
.setting-title {
@extend .font-style-base;
font-size: 14px;
font-weight: bold;
margin-top: 10px;
margin-left: 5px;
margin-bottom: 5px;
}
.setting-title-clickable {
@extend .setting-title;
}
.setting-title-clickable:hover {
cursor: pointer;
}
.modal-sm {
width: 375px;
}
.logo-sub {
margin: 0 5px;
}
.contributor {
@extend .font-style-base;
margin: 5px;
}
hr {
margin-bottom: 5px;
}
/* Settings only affecting the about modal */
.modal {
-webkit-app-region: drag;
background: rgba(0, 0, 0, 0.5);
}
}
================================================
FILE: app/containers/appContainer/index.js
================================================
import { Alert } from 'react-bootstrap'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { remote, shell } from 'electron'
import { updateUpdateAvailableBarStatus } from '../../actions/index'
import AboutPage from '../aboutPage'
import Dashboard from '../dashboard'
import electronLocalStorage from 'electron-json-storage-sync'
import LoginPage from '../loginPage'
import NavigationPanel from '../navigationPanel'
import NavigationPanelDetails from '../navigationPanelDetails'
import React, { Component } from 'react'
import SearchPage from '../searchPage'
import SnippetPanel from '../snippetPanel'
import SplitPane from 'react-split-pane'
import ThemeManager from '../../utilities/themeManager'
import './index.scss'
import './scrollbar.scss'
const conf = remote.getGlobal('conf')
const themeManager = new ThemeManager()
themeManager.setTheme(conf.get('theme'))
class AppContainer extends Component {
renderAboutPage () {
const { updateAboutModalStatus } = this.props
return (
<AboutPage updateAboutModalStatus = { updateAboutModalStatus }/>
)
}
renderDashboard () {
const { updateDashboardStatus } = this.props
return (
<Dashboard updateDashboardStatus = { updateDashboardStatus }/>
)
}
renderSearchPage () {
const { searchWindowStatus, searchIndex } = this.props
return (
<div>
{ searchWindowStatus === 'OFF'
? null
: <SearchPage searchIndex = { searchIndex } /> }
</div>
)
}
dismissUpdateAlert () {
const { updateUpdateAvailableBarStatus } = this.props
updateUpdateAvailableBarStatus('OFF')
}
handleDownloadClicked () {
const { newVersionInfo } = this.props
shell.openExternal(newVersionInfo.url)
this.dismissUpdateAlert()
}
handleReleaseNotesClicked () {
shell.openExternal('https://github.com/hackjutsu/Lepton/releases')
this.dismissUpdateAlert()
}
handleSkipClicked () {
const { newVersionInfo } = this.props
electronLocalStorage.set('skipped-version', newVersionInfo.version)
this.dismissUpdateAlert()
}
renderUpdateAlert () {
const { updateAvailableBarStatus, newVersionInfo } = this.props
return (
<div>
{ updateAvailableBarStatus === 'ON'
? <Alert bsStyle='warning' onDismiss={ this.dismissUpdateAlert.bind(this) }>
{ `New version ${newVersionInfo.version} is available! ` }
<a className='customized-button' onClick={ this.handleSkipClicked.bind(this) }>#skip</a>
{ newVersionInfo.url
? <a className='customized-button' onClick={ this.handleReleaseNotesClicked.bind(this) }>#release</a>
: <a className='customized-button' onClick={ this.handleReleaseNotesClicked.bind(this) }>#download</a> }
{ newVersionInfo.url
? <a className='customized-button' onClick={ this.handleDownloadClicked.bind(this) }>#download</a>
: null }
</Alert>
: null }
</div>
)
}
renderActiveNormalSection () {
const {
updateLocalStorage,
updateActiveGistAfterClicked,
reSyncUserGists,
localPref,
searchIndex
} = this.props
return (
<div>
{ this.renderAboutPage() }
{ this.renderDashboard() }
{ this.renderSearchPage() }
{ this.renderUpdateAlert() }
<NavigationPanel
localPref = { localPref }
searchIndex = { searchIndex }
updateLocalStorage = { updateLocalStorage }
updateActiveGistAfterClicked = { updateActiveGistAfterClicked }
reSyncUserGists = { reSyncUserGists } />
<SplitPane split='vertical' minSize={180} maxSize={300} defaultSize={230}>
<NavigationPanelDetails />
<SnippetPanel
searchIndex = { searchIndex }
reSyncUserGists = { reSyncUserGists } />
</SplitPane>
</div>
)
}
renderActiveImmersiveSection () {
const { searchIndex, reSyncUserGists } = this.props
return (
<SnippetPanel
searchIndex = { searchIndex }
reSyncUserGists = { reSyncUserGists } />
)
}
renderActiveSection () {
const { immersiveMode } = this.props
return (
<div>
{ immersiveMode === 'ON'
? this.renderActiveImmersiveSection()
: this.renderActiveNormalSection() }
</div>
)
}
renderInactiveSection () {
const { loggedInUserInfo, launchAuthWindow } = this.props
return (
<LoginPage
loggedInUserInfo = { loggedInUserInfo }
launchAuthWindow = { launchAuthWindow } />
)
}
render () {
const { userSession } = this.props
return (
<div className='app-container'>
{ userSession.activeStatus === 'ACTIVE'
? this.renderActiveSection()
: this.renderInactiveSection() }
</div>
)
}
}
function mapStateToProps (state) {
return {
userSession: state.userSession,
searchWindowStatus: state.searchWindowStatus,
aboutModalStatus: state.aboutModalStatus,
dashboardModalStatus: state.dashboardModalStatus,
newVersionInfo: state.newVersionInfo,
updateAvailableBarStatus: state.updateAvailableBarStatus,
immersiveMode: state.immersiveMode
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
updateUpdateAvailableBarStatus: updateUpdateAvailableBarStatus
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(AppContainer)
================================================
FILE: app/containers/appContainer/index.scss
================================================
.app-container {
margin: 0px;
max-height: 100vh;
}
.app-container {
.font-style-base {
font-family: Consolas, Menlo, Monaco, "Courier New", monospace;
font-size: 13px;
}
.bar-button {
width: 100px;
}
.customized-button {
@extend .font-style-base;
float: right;
margin: 5px 5px 0px 10px;
}
.customized-button:hover {
cursor: pointer;
}
.SplitPane {
position: relative !important;
}
.alert {
margin-bottom: 0px;
}
}
/*For the 'x' close button at top-right corner of the modal*/
div > div.modal-header > button > span:nth-child(1) {
color: var(--modal-close-button);
}
div > div.modal-header > button {
color: var(--modal-close-button);
}
/**/
/*For the resizer handle*/
.Resizer {
background: #000;
opacity: .2;
z-index: 1;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding-box;
}
.Resizer:hover {
-webkit-transition: all 1s ease;
transition: all 1s ease;
}
.Resizer.horizontal {
height: 11px;
margin: -2px 0;
border-top: 2px solid rgba(0, 0, 0, 0);
border-bottom: 2px solid rgba(0, 0, 0, 0);
cursor: row-resize;
width: 100%;
}
.Resizer.horizontal:hover {
border-top: 2px solid rgba(0, 0, 0, 0.0);
border-bottom: 2px solid rgba(0, 0, 0, 0.0);
}
.Resizer.vertical {
width: 2px;
margin: 0 -2px;
border-left: 2px solid rgba(0, 0, 0, 0);
border-right: 2px solid rgba(0, 0, 0, 0);
cursor: col-resize;
}
.Resizer.vertical:hover {
border-left: 2px solid rgba(0, 0, 0, 0.0);
border-right: 2px solid rgba(0, 0, 0, 0.0);
}
.Resizer.disabled {
cursor: not-allowed;
}
.Resizer.disabled:hover {
border-color: transparent;
}
.btn-default {
color: var(--text-primary);
background-color: transparent;
border-color: #ccc;
}
================================================
FILE: app/containers/appContainer/scrollbar.scss
================================================
*:not(.modal):not(:hover)::-webkit-scrollbar {
display: none;
}
::-webkit-scrollbar {
width: 8px;
height: 5px;
}
::-webkit-scrollbar-thumb {
border-radius: 2px;
background: #f5f5f5;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
}
================================================
FILE: app/containers/codeArea/index.js
================================================
import HighlightJS from 'highlight.js'
import hljsDefineSolidity from 'highlightjs-solidity'
import hljsDefineGraphQL from 'highlightjs-graphql'
import Markdown from '../../utilities/markdown'
import nb from '../../utilities/jupyterNotebook'
import React, { Component } from 'react'
import '../../utilities/vendor/prism/prism.scss'
import './jupyterNotebook.scss'
import './markdown.scss'
const remote = require('@electron/remote')
const logger = remote.getGlobal('logger')
const conf = remote.getGlobal('conf')
// resolve syntax highlight style based on app theme
if (conf.get('theme') === 'dark') {
require('../../utilities/vendor/highlightJS/styles/atom-one-dark.css')
} else {
require('../../utilities/vendor/highlightJS/styles/github-gist.css')
}
hljsDefineSolidity(HighlightJS) // register solidity to hightlight.js
hljsDefineGraphQL(HighlightJS) // register graphql to hightlight.js
export default class CodeArea extends Component {
createJupyterNotebookCodeBlock (content, language, kTabLength) {
try {
const notebook = nb.parse(JSON.parse(content))
const notebookHtml = notebook.render().outerHTML
return `<div class='jupyterNotebook-section'>${notebookHtml}</div>`
} catch (err) {
logger.error(`Failed to render Jupyter Notebook content with err ${err}`)
return this.createHighlightedCodeBlock(content, language, kTabLength)
}
}
createMarkdownCodeBlock (content) {
return `<div class='markdown-section'>${Markdown.render(content)}</div>`
}
createHighlightedCodeBlock (content, language, kTabLength) {
let lineNumber = 0
const highlightedContent = HighlightJS.highlightAuto(content, [language]).value
/*
Highlight.js wraps comment blocks inside <span class='hljs-comment'></span>.
However, when the multi-line comment block is broken down into different
table rows, only the first row, which is appended by the <span> tag, is
highlighted. The following code fixes it by appending <span> to each line
of the comment block.
*/
const commentPattern = /<span class="hljs-comment">(.|\n)*?\*\/\s*<\/span>/g
const adaptedHighlightedContent = highlightedContent.replace(commentPattern, data => {
return data.replace(/\r?\n/g, () => {
// Chromium is smart enough to add the closing </span>
return "\n<span class='hljs-comment'>"
})
})
const contentTable = adaptedHighlightedContent.split(/\r?\n/).map(lineContent => {
return `<tr>
<td class='line-number' data-pseudo-content=${++lineNumber}></td>
<td>${lineContent === '' ? '\n' : lineContent}</td>
</tr>`
}).join('')
return `<pre><code><table class='code-table' style='tab-size: ${kTabLength};'>${contentTable}</table></code></pre>`
}
// Find the best language for code highlighting by best effort.
adaptedLanguage (filename, lang) {
let language = lang || 'Other'
// Adjust the language based on file extensions.
const filenameExtension = filename.split('.').pop()
switch (filenameExtension) {
case 'leptonrc':
language = 'json'
break
case 'zshrc':
language = 'bash'
break
case 'sql':
language = 'sql'
break
case 'solidity':
case 'sol':
language = 'solidity'
break
default:
// intentionally left blank
}
// Adapt the language name for Highlight.js. For example, 'C#' should be
// expressed as 'cs' to be recognized by Highlight.js.
switch (language) {
case 'Shell': return 'bash'
case 'C#': return 'cs'
case 'Objective-C': return 'objectivec'
case 'Objective-C++': return 'objectivec'
case 'Visual Basic': return 'vbscript'
case 'Batchfile': return 'bat'
default:
}
return language
}
renderCodeArea (filename, content, lang, kTabLength) {
const language = this.adaptedLanguage(filename, lang)
let htmlContent = ''
switch (language) {
case 'Jupyter Notebook':
htmlContent = this.createJupyterNotebookCodeBlock(content, language, kTabLength)
break
case 'Markdown':
htmlContent = this.createMarkdownCodeBlock(content)
break
default:
htmlContent = this.createHighlightedCodeBlock(content, language, kTabLength)
}
return (
<div className='code-area'
dangerouslySetInnerHTML={ { __html: htmlContent } }/>
)
}
render () {
const { filename, content, language, kTabLength } = this.props
return this.renderCodeArea(filename, content, language, kTabLength)
}
}
================================================
FILE: app/containers/codeArea/jupyterNotebook.scss
================================================
.font-style-base {
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Droid Sans, sans-serif;
font-size: 14px;
}
.jupyterNotebook-section {
h1 {
@extend .font-style-base;
font-size: 30px !important;
font-weight: 600;
margin-top: 20px !important;
margin-bottom: 10px !important;
}
h2 {
@extend .font-style-base;
font-size: 25px !important;
margin-top: 20px !important;
margin-bottom: 10px !important;
}
.nb-notebook {
@extend .font-style-base;
line-height: 1.5;
margin-top: 20px;
margin-bottom: 20px;
margin-left: 100px;
margin-right: 50px;
}
.nb-stdout, .nb-stderr {
@extend .font-style-base;
white-space: pre-wrap;
margin: 1em 0;
padding: 0.1em 0.5em;
}
.nb-stderr {
@extend .font-style-base;
background-color: #FAA;
}
.nb-cell + .nb-cell {
margin-top: 0.5em;
}
.nb-output table {
border: 1px solid #000;
border-collapse: collapse;
}
.nb-output th {
font-weight: bold;
}
.nb-output th, .nb-output td {
@extend .font-style-base;
border: 1px solid #000;
padding: 0.25em;
text-align: left;
vertical-align: middle;
border-collapse: collapse;
}
.nb-cell {
@extend .font-style-base;
position: relative;
}
.nb-raw-cell {
@extend .font-style-base;
white-space: pre-wrap;
background-color: #f5f2f0;
padding: 1em;
margin: .5em 0;
}
.nb-cell.nb-code-cell .nb-input {
border-style: solid;
border-radius: 5px;
border-color: lightgray;
border-width: 1px;
padding-top: 5px;
padding-bottom: 5px;
padding-left: 10px;
padding-right: 10px;
font-size: 12px;
}
.nb-output {
@extend .font-style-base;
min-height: 1em;
width: 100%;
overflow-x: scroll;
}
.nb-output img {
max-width: 100%;
}
.nb-input:before {
position: absolute;
font-family: monospace;
color: #999;
left: -7.5em;
width: 7em;
text-align: right;
padding: 10px;
font-size: 12px;
}
.nb-output:before {
position: absolute;
font-family: monospace;
color: #999;
left: -7.5em;
width: 7em;
text-align: right;
padding: 10px;
padding-left: 8px;
font-size: 12px;
}
.nb-input:before {
content: "In [" attr(data-prompt-number) "]:";
}
.nb-output:before {
content: "Out [" attr(data-prompt-number) "]:";
}
.nb-input, .nb-output{
font-size: 13px;
}
// Fix pandas dataframe formatting
div[style="max-height:1000px;max-width:1500px;overflow:auto;"] {
max-height: none !important;
}
code {
padding: 2px 4px;
font-size: 90%;
color: var(--text-primary);
background-color: transparent;
}
}
================================================
FILE: app/containers/codeArea/markdown.scss
================================================
// GitHub style markdown css
.markdown-section {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Segoe UI Symbol';
font-size: 14px;
line-height: 1.6 !important;
padding-top: 10px;
padding-bottom: 10px;
background-color: var(--bg-primary);
padding: 30px;
}
body > *:first-child {
margin-top: 0 !important;
}
body > *:last-child {
margin-bottom: 0 !important;
}
a {
color: #4183C4;
text-decoration: none;
}
a.absent {
color: #cc0000;
}
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0;
}
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative;
}
h2:first-child, h1:first-child, h1:first-child + h2, h3:first-child, h4:first-child, h5:first-child, h6:first-child {
margin-top: 0;
padding-top: 0;
}
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
text-decoration: none;
}
h1 tt, h1 code {
font-size: inherit;
}
h2 tt, h2 code {
font-size: inherit;
}
h3 tt, h3 code {
font-size: inherit;
}
h4 tt, h4 code {
font-size: inherit;
}
h5 tt, h5 code {
font-size: inherit;
}
h6 tt, h6 code {
font-size: inherit;
}
h1 {
font-size: 28px;
}
h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
color: #777777;
font-size: 14px;
}
p, blockquote, ul, ol, dl, li, table, pre {
margin: 15px 0;
line-height: 1.5;
}
li {
margin: 0;
}
hr {
background: transparent repeat-x 0 0;
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0;
}
body > h2:first-child {
margin-top: 0;
padding-top: 0;
}
body > h1:first-child {
margin-top: 0;
padding-top: 0;
}
body > h1:first-child + h2 {
margin-top: 0;
padding-top: 0;
}
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
margin-top: 0;
padding-top: 0;
}
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0;
}
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
margin-top: 0;
}
li p.first {
display: inline-block;
}
ul, ol {
padding-left: 30px;
}
ul :first-child, ol :first-child {
margin-top: 0;
}
ul :last-child, ol :last-child {
margin: 0;
}
dl {
padding: 0;
}
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px;
}
dl dt:first-child {
padding: 0;
}
dl dt > :first-child {
margin-top: 0;
}
dl dt > :last-child {
margin-bottom: 0;
}
dl dd {
margin: 0 0 15px;
padding: 0 15px;
}
dl dd > :first-child {
margin-top: 0;
}
dl dd > :last-child {
margin-bottom: 0;
}
blockquote {
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777;
font-size: 15px;
}
blockquote > :first-child {
margin-top: 0;
}
blockquote > :last-child {
margin-bottom: 0;
}
table {
padding: 0;
}
table tr {
border-top: 1px solid #cccccc;
margin: 0;
padding: 0;
}
table tr th {
font-weight: bold;
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px;
}
table tr td {
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px;
}
table tr th :first-child, table tr td :first-child {
margin-top: 0;
}
table tr th :last-child, table tr td :last-child {
margin-bottom: 0;
}
img {
max-width: 100%;
}
span.frame {
display: block;
overflow: hidden;
}
span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
overflow: hidden;
margin: 13px 0 0;
padding: 7px;
width: auto;
}
span.frame span img {
display: block;
float: left;
}
span.frame span span {
clear: both;
display: block;
padding: 5px 0 0;
}
span.align-center {
display: block;
overflow: hidden;
clear: both;
}
span.align-center > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: center;
}
span.align-center span img {
margin: 0 auto;
text-align: center;
}
span.align-right {
display: block;
overflow: hidden;
clear: both;
}
span.align-right > span {
display: block;
overflow: hidden;
margin: 13px 0 0;
text-align: right;
}
span.align-right span img {
margin: 0;
text-align: right;
}
span.float-left {
display: block;
margin-right: 13px;
overflow: hidden;
float: left;
}
span.float-left span {
margin: 13px 0 0;
}
span.float-right {
display: block;
margin-left: 13px;
overflow: hidden;
float: right;
}
span.float-right > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: right;
}
code, tt {
margin: 0 2px;
white-space: nowrap;
border:1px solid var(--border-color);
background-color: var(--md-inline-code-background);
border-radius: 3px;
color: var(--text-primary);
}
pre code {
margin: 0;
padding: 0;
white-space: pre-wrap;
border: none;
background: transparent;
}
.highlight pre {
background-color: #f8f7fa;
border: 1px solid var(--border-color);
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 12px 12px;
border-radius: 3px;
}
pre {
padding: 9.5px !important;
margin: 0 0 10px !important;
font-size: 13px !important;
line-height: 1.42857143;
color: #333;
word-break: break-all;
word-wrap: break-word;
// background-color: #f5f5f5 !important;
border: 1px solid var(--border-color) !important;
border-radius: 3px !important;
}
pre code, pre tt {
background-color: transparent;
border: none;
}
img {
max-width: 80%;
display: block;
margin: 0 auto;
}
}
================================================
FILE: app/containers/dashboard/index.js
================================================
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Modal, Image } from 'react-bootstrap'
import { Radar } from 'react-chartjs'
import { updateDashboardModalStatus } from '../../actions'
import React, { Component } from 'react'
import robotocatImage from '../../utilities/octodex/robotocat.png'
import './index.scss'
class Dashboard extends Component {
renderDashboardSection () {
const { gistTags } = this.props
const langTags = Object.keys(gistTags)
.filter(key => key.startsWith('lang@') && key !== 'lang@All')
.sort((t1, t2) => gistTags[t2].length - gistTags[t1].length)
const maxNum = 5
const rawLabels = langTags.slice(0, langTags.length > maxNum ? maxNum : langTags.length)
const data = rawLabels.map(lang => gistTags[lang].length)
const labels = rawLabels.map(rawLabel => rawLabel.substr('@lang'.length))
if (data.length > 2) return this.buildRadarChart(data, labels)
return (
<div className='dashboard-section'>
<Image className='octocat' src={ robotocatImage } rounded/>
<div className='greeting'>
Not enough data. Try creating gists of more than two languages. Happy Coding!
</div>
</div>
)
}
// https://github.com/chartjs/Chart.js/blob/v1.1.1/docs/03-Radar-Chart.md
buildRadarChart (data, labels) {
const GREY = '#C2C4D1'
const chartData = {
labels: labels,
datasets: [
{
label: 'My Language Stats',
fillColor: 'rgba(81,192,191,0.2)',
strokeColor: 'rgba(81,192,191,1)',
pointColor: 'rgba(81,192,191,1)',
pointStrokeColor: GREY,
pointHighlightFill: GREY,
pointHighlightStroke: 'rgba(81,192,191,1)',
data: data
}
]
}
const chartOptions = {
pointLabelFontSize: 12,
}
return (
<div className='dashboard-section'>
<Radar data={ chartData } options={ chartOptions } width="350" height="300"/>
<div className='compliment'>
{ this.renderCompliments(labels[0]) }
</div>
</div>
)
}
renderCompliments (lang) {
if (lang === 'Other') return 'Hmm... Looks like you are learning a mysterious language.'
return (
<div>
<div className='compliment-word'> Well done! You are on the road to </div>
<div className='compliment-word'><b> { `${lang} Master!` } </b></div>
</div>
)
}
renderSettingModalBody () {
return (
<div>
{ this.renderDashboardSection() }
</div>
)
}
handleCloseButtonClicked () {
const { updateDashboardModalStatus } = this.props
updateDashboardModalStatus('OFF')
}
render () {
return (
<Modal
className='dashboard-modal'
bsSize='small'
show={ this.props.dashboardModalStatus === 'ON' }
onHide={ this.handleCloseButtonClicked.bind(this) }>
<Modal.Header closeButton>
<Modal.Title>Dashboard</Modal.Title>
</Modal.Header>
<Modal.Body>
{ this.renderSettingModalBody() }
</Modal.Body>
</Modal>
)
}
}
function mapStateToProps (state) {
return {
dashboardModalStatus: state.dashboardModalStatus,
gistTags: state.gistTags
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
updateDashboardModalStatus: updateDashboardModalStatus
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard)
================================================
FILE: app/containers/dashboard/index.scss
================================================
.dashboard-modal {
.font-style-base {
font-family: Consolas, Menlo, Monaco, "Courier New", monospace;
font-size: 14px;
color: rgb(100,100,100);
}
.section-base {
height: 400px;
}
.dashboard-section {
@extend .section-base;
display: flex;
flex-direction: column;
}
.compliment {
@extend .font-style-base;
text-align: center;
margin-top: 20px;
}
.compliment-word {
margin: 2px;
}
.octocat {
width: 300px;
margin: 5px;
}
.modal-sm {
width: 375px;
}
/* Settings only affecting the about modal */
.modal {
-webkit-app-region: drag;
background: rgba(0, 0, 0, 0.5);
}
}
================================================
FILE: app/containers/gistEditor/index.js
================================================
import React, { Component } from 'react'
import CodeMirror from 'react-codemirror'
import 'codemirror/mode/meta'
// When Webpack imports these AMD modules for modes, the first one will be set as
// the default mode for CodeMirror, which then analyzes the content accordingly
// under the hood for syntax highlight. However, some mode will report errors if
// the content doesn't comply with its syntax, like dylan which doesn't allow the
// word 'constructor' (though this could be a bug in CodeMirror). Therefore the
// first mode matters. We intentionally select markdown as the first(default) mode
// because of its flexibility in syntax.
import 'codemirror/mode/markdown/markdown'
import 'codemirror/mode/dylan/dylan'
import 'codemirror/mode/textile/textile'
import 'codemirror/mode/pegjs/pegjs'
import 'codemirror/mode/factor/factor'
import 'codemirror/mode/asterisk/asterisk'
import 'codemirror/mode/crystal/crystal'
import 'codemirror/mode/sql/sql'
import 'codemirror/mode/pug/pug'
import 'codemirror/mode/forth/forth'
import 'codemirror/mode/lua/lua'
import 'codemirror/mode/mumps/mumps'
import 'codemirror/mode/elm/elm'
import 'codemirror/mode/tornado/tornado'
import 'codemirror/mode/d/d'
import 'codemirror/mode/rpm/rpm'
import 'codemirror/mode/gfm/gfm'
import 'codemirror/mode/swift/swift'
import 'codemirror/mode/solr/solr'
import 'codemirror/mode/stex/stex'
import 'codemirror/mode/haskell/haskell'
import 'codemirror/mode/velocity/velocity'
import 'codemirror/mode/properties/properties'
import 'codemirror/mode/ruby/ruby'
import 'codemirror/mode/htmlembedded/htmlembedded'
import 'codemirror/mode/smarty/smarty'
import 'codemirror/mode/tiddlywiki/tiddlywiki'
import 'codemirror/mode/go/go'
import 'codemirror/mode/ttcn/ttcn'
import 'codemirror/mode/slim/slim'
import 'codemirror/mode/tiki/tiki'
import 'codemirror/mode/sieve/sieve'
import 'codemirror/mode/troff/troff'
import 'codemirror/mode/z80/z80'
import 'codemirror/mode/eiffel/eiffel'
import 'codemirror/mode/yaml/yaml'
import 'codemirror/mode/powershell/powershell'
import 'codemirror/mode/vhdl/vhdl'
import 'codemirror/mode/turtle/turtle'
import 'codemirror/mode/ebnf/ebnf'
import 'codemirror/mode/livescript/livescript'
import 'codemirror/mode/haml/haml'
import 'codemirror/mode/jinja2/jinja2'
import 'codemirror/mode/php/php'
import 'codemirror/mode/mathematica/mathematica'
import 'codemirror/mode/htmlmixed/htmlmixed'
import 'codemirror/mode/gherkin/gherkin'
import 'codemirror/mode/q/q'
import 'codemirror/mode/ecl/ecl'
import 'codemirror/mode/perl/perl'
import 'codemirror/mode/vue/vue'
import 'codemirror/mode/ntriples/ntriples'
import 'codemirror/mode/cmake/cmake'
import 'codemirror/mode/handlebars/handlebars'
import 'codemirror/mode/modelica/modelica'
import 'codemirror/mode/dockerfile/dockerfile'
import 'codemirror/mode/yaml-frontmatter/yaml-frontmatter'
import 'codemirror/mode/groovy/groovy'
import 'codemirror/mode/oz/oz'
import 'codemirror/mode/twig/twig'
import 'codemirror/mode/pascal/pascal'
import 'codemirror/mode/diff/diff'
import 'codemirror/mode/idl/idl'
import 'codemirror/mode/rst/rst'
import 'codemirror/mode/smalltalk/smalltalk'
import 'codemirror/mode/nsis/nsis'
import 'codemirror/mode/mbox/mbox'
import 'codemirror/mode/spreadsheet/spreadsheet'
import 'codemirror/mode/haskell-literate/haskell-literate'
import 'codemirror/mode/dart/dart'
import 'codemirror/mode/octave/octave'
import 'codemirror/mode/mirc/mirc'
import 'codemirror/mode/haxe/haxe'
import 'codemirror/mode/mllike/mllike'
import 'codemirror/mode/shell/shell'
import 'codemirror/mode/asn.1/asn.1'
import 'codemirror/mode/clike/clike'
import 'codemirror/mode/css/css'
import 'codemirror/mode/pig/pig'
import 'codemirror/mode/xquery/xquery'
import 'codemirror/mode/asciiarmor/asciiarmor'
import 'codemirror/mode/erlang/erlang'
import 'codemirror/mode/scheme/scheme'
import 'codemirror/mode/python/python'
import 'codemirror/mode/coffeescript/coffeescript'
import 'codemirror/mode/clojure/clojure'
import 'codemirror/mode/fcl/fcl'
import 'codemirror/mode/puppet/puppet'
import 'codemirror/mode/brainfuck/brainfuck'
import 'codemirror/mode/http/http'
import 'codemirror/mode/dtd/dtd'
import 'codemirror/mode/r/r'
import 'codemirror/mode/verilog/verilog'
import 'codemirror/mode/xml/xml'
import 'codemirror/mode/javascript/javascript'
import 'codemirror/mode/webidl/webidl'
import 'codemirror/mode/vbscript/vbscript'
import 'codemirror/mode/toml/toml'
import 'codemirror/mode/vb/vb'
import 'codemirror/mode/rust/rust'
import 'codemirror/mode/cypher/cypher'
import 'codemirror/mode/jsx/jsx'
import 'codemirror/mode/django/django'
import 'codemirror/mode/ttcn-cfg/ttcn-cfg'
import 'codemirror/mode/cobol/cobol'
import 'codemirror/mode/commonlisp/commonlisp'
import 'codemirror/mode/soy/soy'
import 'codemirror/mode/stylus/stylus'
import 'codemirror/mode/sparql/sparql'
import 'codemirror/mode/nginx/nginx'
import 'codemirror/mode/sas/sas'
import 'codemirror/mode/mscgen/mscgen'
import 'codemirror/mode/gas/gas'
import 'codemirror/mode/fortran/fortran'
import 'codemirror/mode/apl/apl'
import 'codemirror/mode/julia/julia'
import 'codemirror/mode/tcl/tcl'
import 'codemirror/mode/protobuf/protobuf'
import 'codemirror/mode/yacas/yacas'
import 'codemirror/mode/sass/sass'
import 'codemirror/addon/edit/matchbrackets'
import 'codemirror/addon/edit/matchtags'
import 'codemirror/addon/dialog/dialog'
import 'codemirror/addon/mode/loadmode'
import 'codemirror/addon/search/search'
import 'codemirror/addon/search/match-highlighter'
import 'codemirror/addon/search/searchcursor'
import 'codemirror/addon/fold/comment-fold'
import 'codemirror/addon/fold/xml-fold'
import 'codemirror/addon/fold/foldgutter'
import 'codemirror/addon/fold/indent-fold'
import 'codemirror/addon/fold/brace-fold'
import 'codemirror/addon/fold/foldcode'
import 'codemirror/addon/fold/markdown-fold'
import 'codemirror/addon/display/placeholder'
import './index.scss'
import { remote } from 'electron'
const conf = remote.getGlobal('conf')
const highlightTheme = conf.get('theme') === 'dark' ? 'one-dark' : 'github'
const defaultOptions = Object.assign({}, {
theme: highlightTheme,
lineNumbers: true,
matchBrackets: true,
matchTags: true,
lineWrapping: true,
viewportMargin: Infinity,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']
}, conf.get('editor'))
class GistEditor extends Component {
componentDidMount () {
const { filename } = this.props
this.editor = this.refs.editor
this.CodeMirror = this.editor.getCodeMirrorInstance()
this.CodeMirror.modeURL = '../../../node_modules/codemirror/mode/%N/%N.js'
this.setMode(filename)
}
componentDidUpdate (prevProps, prevState) {
const { filename } = this.props
if (prevProps.filename !== filename) {
this.setMode(filename)
}
}
setMode (filename) {
const modeInfo = filename ? this.CodeMirror.findModeByFileName(filename) : this.CodeMirror.findModeByName('Plain Text')
modeInfo && this.editor.getCodeMirror().setOption('mode', modeInfo.mime)
}
render () {
const { value, placeholder } = this.props
const options = Object.assign({}, defaultOptions, { placeholder })
return (
<CodeMirror
ref='editor'
value={ value }
options={ options }
onChange={ value => this.props.onChange(value) }
/>
)
}
}
export default GistEditor
================================================
FILE: app/containers/gistEditor/index.scss
================================================
.font-style-base {
font-family: Consolas, Menlo, Monaco, "Courier New", monospace;
font-size: 13px;
}
.CodeMirror {
@extend .font-style-base;
height: auto;
}
.CodeMirror-gutters {
border-right: 1px solid var(--border-color);
background-color: var(--bg-secondary);
}
// To overwrite the gutter color under one-dark theme
.cm-s-one-dark .CodeMirror-gutter,
.cm-s-one-dark .CodeMirror-gutters {
border: none;
background-color: var(--bg-secondary);
}
.CodeMirror-scroll {
min-height: 400px;
background: var(--bg-primary);
}
.CodeMirror-empty {
color: #969896
}
.cm-s-github .cm-comment {
color: #969896;
}
.cm-s-github .cm-string {
color: #183691
}
.cm-s-github .cm-string-2 {
color: #0086b3
}
.cm-s-github .cm-tag {
color: #63a35c
}
.cm-s-github .cm-keyword, .cm-s-github .cm-operator {
color: #a71d5d
}
.cm-s-github .cm-property {
color: #333
}
.cm-s-github .cm-atom {
color: #0086b3
}
.cm-s-github .cm-error, .cm-s-github .cm-invalidchar {
font-style: italic;
font-weight: 600;
color: #bd2c00;
text-decoration: underline
}
.cm-s-github .cm-variable-2, .cm-s-github .cm-number {
color: #0086b3
}
@import '../../../node_modules/codemirror/lib/codemirror.css';
@import '../../../node_modules/codemirror/mode/tiddlywiki/tiddlywiki.css';
@import '../../../node_modules/codemirror/mode/tiki/tiki.css';
@import '../../../node_modules/codemirror/addon/dialog/dialog.css';
@import '../../../node_modules/codemirror/addon/fold/foldgutter.css';
@import '../../../node_modules/codemirror-one-dark-theme/one-dark.css';
================================================
FILE: app/containers/gistEditorForm/index.js
================================================
import { connect } from 'react-redux'
import { Field, FieldArray, reduxForm, formValueSelector } from 'redux-form'
import { remote, ipcRenderer } from 'electron'
import { OverlayTrigger, Tooltip, Button, ListGroup, ListGroupItem, Panel } from 'react-bootstrap'
import GistEditor from '../gistEditor'
import React, { Component } from 'react'
import validFilename from 'valid-filename'
import tipsIcon from './ei-question.svg'
import './index.scss'
export const NEW_GIST = 'NEW_GIST'
export const UPDATE_GIST = 'UPDATE_GIST'
const conf = remote.getGlobal('conf')
const logger = remote.getGlobal('logger')
const descriptionTips = '[title] description #tag1 #tag2'
const tooltip = (
<Tooltip id='tooltip'>{ descriptionTips }</Tooltip>
)
class GistEditorFormImpl extends Component {
componentWillMount () {
const { change, initialData } = this.props
// Initialize the form
initialData.private && change('private', initialData.private)
initialData.description && change('description', initialData.description)
initialData.gists && change('gistFiles', initialData.gists)
}
componentDidMount () {
ipcRenderer.on('submit-gist', () => {
this.shortcutSubmit()
})
}
componentWillUnmount () {
ipcRenderer.removeAllListeners('submit-gist')
}
shortcutSubmit () {
// https://github.com/erikras/redux-form/issues/1304
const submitter = this.props.handleSubmit(this.props.onSubmit)
submitter() // submits
}
render () {
const { handleSubmit, handleCancel, submitting, formStyle, filenameList } = this.props
return (
<form className='gist-editor-form' onSubmit={ handleSubmit }>
<Field
name='description'
type='text'
component={ renderDescriptionField }
validate={ valideNotEmptyContent }/>
<FieldArray
name='gistFiles'
formStyle={ formStyle }
filenameList={ filenameList }
component={ renderGistFiles }/>
<hr/>
<div className='control-button-group'>
<Button
className='gist-editor-control-button'
type='submit'
bsStyle='default'
disabled={ submitting }>
Submit
</Button>
<Button
className='gist-editor-control-button'
onClick={ handleCancel }
bsStyle='default'
disabled={ submitting }>
Cancel
</Button>
</div>
</form>
)
}
}
const valideNotEmptyContent = value => value ? null : 'required'
const validateFilename = value => {
// empty filename is not allowed
if (!value) {
return 'required'
}
// validate filename according to the .leptonrc configs
if (!conf.get('editor').validateFilename) {
logger.info('[Filename Validation] According to the config, filename validation has been skipped')
} else if (!validFilename(value)) {
return 'invalid filename'
}
}
const renderTitleInputField = ({ input, placeholder, type, meta: { touched, error, warning } }) => (
<div className='title-input-field'>
<input className='gist-editor-filename-area' {...input} placeholder={ placeholder } type={ type }/>
{ touched && ((error && <span className='error-msg'>{ error }</span>) ||
(warning && <span className='error-msg'>{ warning }</span>)) }
</div>
)
const renderDescriptionField = ({ input, type, meta: { touched, error, warning } }) => (
<div className='gist-editor-section gist-editor-name'>
<input
className='gist-editor-input-area'
{ ...input }
type={ type }
placeholder={ descriptionTips }/>
{ touched && ((error && <span className='error-msg'>{ error }</span>) ||
(warning && <span className='error-msg'>{ warning }</span>)) }
<OverlayTrigger placement="top" overlay={ tooltip }>
<a className='tips' href='#'>
<div
className='tips-icon'
dangerouslySetInnerHTML={{ __html: tipsIcon }} />
<span>tips</span>
</a>
</OverlayTrigger>
</div>
)
const renderContentField = ({ input, type, meta: { touched, error, warning }, filename }) => (
<div>
<GistEditor
filename={ filename }
{ ...input }
type={ type }/>
{ touched && ((error && <span className='error-msg'>{error}</span>) ||
(warning && <span className='error-msg'>{warning}</span>)) }
</div>
)
function renderGistFileHeader (member, fields, index) {
return (
<div>
<Field
name={ `${member}.filename` }
type='text'
component={ renderTitleInputField }
placeholder='file name... (e.g. snippet.js)'
validate={ validateFilename }/>
<a href='#'
className={ fields.length === 1 ? 'gist-editor-customized-tag-hidden' : 'gist-editor-customized-tag' }
onClick={ () => fields.remove(index) }>#remove</a>
</div>
)
}
const renderGistFiles = ({ fields, formStyle, filenameList }) => (
<ListGroup className='gist-editor-section'>
{ fields.map((member, index) =>
<ListGroupItem className='gist-editor-gist-file' key={index}>
<Panel>
<Panel.Heading>{ renderGistFileHeader(member, fields, index) }</Panel.Heading>
<Panel.Body>
<Field name={ `${member}.content` }
type='text'
filename={ filenameList && filenameList[index] }
component={ renderContentField }
validate={ valideNotEmptyContent }/>
</Panel.Body>
</Panel>
</ListGroupItem>
) }
<div>
<a href='#'
className='gist-editor-customized-tag'
onClick={ () => fields.push({}) }>
#add file
</a>
<div className='gist-editor-privacy-checkbox'>
<Field name='private' id='private' component='input' type='checkbox' disabled={ formStyle === UPDATE_GIST }/>
secret
</div>
</div>
</ListGroup>
)
const selector = formValueSelector('gistEditorForm')
const GistEditorForm = connect(
state => {
const gistFiles = selector(state, 'gistFiles')
const filenameList = gistFiles && gistFiles.map(({ filename }) =>
filename)
return {
filenameList
}
}
)(GistEditorFormImpl)
export default reduxForm({
form: 'gistEditorForm'
})(GistEditorForm)
================================================
FILE: app/containers/gistEditorForm/index.scss
================================================
.gist-editor-form {
.font-style-base {
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Droid Sans, sans-serif;
font-size: 14px;
color: var(--text-primary);
}
.font-style-blue {
@extend .font-style-base;
color: #4078C0 !important;
}
.gist-editor-control-button {
margin: 5px;
float: right;
}
.gist-editor-new-gist-button {
margin: 5px 0px;
}
.gist-editor-section {
margin: 5px 5px;
}
.gist-editor-name {
display: flex;
align-items: center;
padding-right: 15px;
}
.gist-editor-section .panel-body {
padding: 0px !important;
}
.gist-editor-section .panel-heading {
padding: 2px 5px !important;
color: inherit;
border-color: var(--border-color);
background-color: var(--bg-secondary);
}
.gist-editor-input-area {
@extend .font-style-base;
flex: 1 1 95%;
padding: 5px 10px;
border: solid 1px;
border-color: var(--border-color);
border-radius: 3px;
margin-right: 10px;
background: var(--bg-primary);
}
.gist-editor-filename-area {
@extend .font-style-base;
width: 80%;
margin: 5px 20px 5px 0px;
padding: 5px 10px;
border: solid 1px;
border-color: var(--border-color);
border-radius: 3px;
background: var(--bg-primary);
}
.panel {
border-color: var(--border-color);
border-radius: 0;
}
.gist-editor-content-area {
@extend .font-style-base;
width: 100%;
padding: 5px;
min-height: 300px;
border: 0px;
resize: none;
}
.gist-editor-gist-file {
border: 0px;
padding: 10px 0px !important;
background: var(--text-bg-primary);
}
.control-button-group {
height: 45px;
}
.gist-editor-customized-tag {
@extend .font-style-blue;
margin-left: 5px;
}
.gist-editor-customized-tag-hidden {
@extend .gist-editor-customized-tag;
visibility: hidden;
}
.gist-editor-privacy-checkbox {
@extend .font-style-base;
display: inline;
float: right;
}
.title-input-field {
display: inline;
}
.error-msg {
@extend .font-style-base;
font-size: 12px;
font-weight: bold;
color: #B0580E;
}
.tips {
@extend .font-style-base;
font-size: 13px;
font-style: italic;
flex: 0 0 auto;
position: relative;
& > span {
vertical-align: middle;
}
}
.tips-icon {
height: 24px;
width: 24px;
display: inline-block;
vertical-align: middle;
fill: currentColor;
}
hr {
margin-top: 20px;
margin-bottom: 20px;
border: 1;
border-top: 1px solid var(--border-color);
}
}
================================================
FILE: app/containers/loginPage/index.js
================================================
import { Alert, Button, Image, Modal, ProgressBar } from 'react-bootstrap'
import Avatar from 'boring-avatars'
import { connect } from 'react-redux'
import { remote, ipcRenderer } from 'electron'
import React, { Component } from 'react'
import dojocatImage from '../../utilities/octodex/dojocat.jpg'
import privateinvestocatImage from '../../utilities/octodex/privateinvestocat.jpg'
import saritocatImage from '../../utilities/octodex/saritocat.png'
import './index.scss'
const conf = remote.getGlobal('conf')
const logger = remote.getGlobal('logger')
const LoginModeEnum = { CREDENTIALS: 1, TOKEN: 2 }
class LoginPage extends Component {
constructor (props) {
super(props)
this.state = {
inputTokenValue: '',
loginMode: LoginModeEnum.CREDENTIALS
}
}
componentWillMount () {
const { loggedInUserInfo } = this.props
logger.debug('-----> Inside LoginPage componentWillMount with loggedInUserInfo' + JSON.stringify(loggedInUserInfo))
logger.debug('-----> Registering listener for auto-login signal')
ipcRenderer.on('auto-login', () => {
logger.debug('-----> Received "auto-login" signal with loggedInUserInfo ' + JSON.stringify(loggedInUserInfo))
loggedInUserInfo && loggedInUserInfo.token && this.handleContinueButtonClicked(loggedInUserInfo.token)
})
logger.debug('-----> sending login-page-ready signal')
ipcRenderer.send('login-page-ready')
}
componentWillUnmount () {
logger.debug('-----> Removing listener for auto-login signal')
ipcRenderer.removeAllListeners('auto-login')
}
handleLoginClicked () {
if (this.props.authWindowStatus === 'OFF') {
this.props.launchAuthWindow()
}
}
handleContinueButtonClicked (token) {
this.handleTokenLoginButtonClicked(token)
}
handleTokenLoginButtonClicked (token) {
if (token && this.props.authWindowStatus === 'OFF') {
this.props.launchAuthWindow(token)
}
}
handleLoginModeSwitched () {
if (this.state.loginMode === LoginModeEnum.CREDENTIALS) {
this.setState({
loginMode: LoginModeEnum.TOKEN
})
} else {
this.setState({
loginMode: LoginModeEnum.CREDENTIALS
})
}
}
renderControlSection () {
const { authWindowStatus, loggedInUserInfo, userSessionStatus } = this.props
const { loginMode } = this.state
const loggedInUserName = loggedInUserInfo ? loggedInUserInfo.profile : null
const welcomeMessage = 'Lepton is FREE. Like us on GitHub! ⭐'
if (userSessionStatus === 'IN_PROGRESS') {
return (
<div className='button-group-modal'>
<ProgressBar active now={ 100 }/>
<div className="login-page-text-link">
<a href="https://github.com/hackjutsu/Lepton">{ welcomeMessage }</a>
</div>
</div>
)
}
if (conf.get('enterprise:enable')) {
const token = conf.get('enterprise:token')
return (
<div className='button-group-modal'>
<div className="login-page-text-link">
<a href="https://github.com/hackjutsu/Lepton">{ welcomeMessage }</a>
</div>
{ token
? <Button
autoFocus
className='modal-button'
bsStyle="default"
onClick={ this.handleContinueButtonClicked.bind(this, token) }>
{ loggedInUserName ? `Continue as ${loggedInUserName}` : 'HAPPY CODING' }
</Button>
: this.renderTokenLoginSection(false, userSessionStatus)}
</div>
)
}
if (userSessionStatus === 'EXPIRED' || userSessionStatus === 'INACTIVE' ||
loggedInUserName === null || loggedInUserName === 'null') {
return (
<div className='button-group-modal'>
<div className="login-page-text-link">
<a href="https://github.com/hackjutsu/Lepton">{ welcomeMessage }</a>
</div>
{ loginMode === LoginModeEnum.CREDENTIALS
? this.renderCredentialLoginSection(authWindowStatus, userSessionStatus)
: this.renderTokenLoginSection(true, userSessionStatus)
}
</div>
)
}
return null
}
updateInputValue (evt) {
this.setState({
inputTokenValue: evt.target.value
})
}
renderCredentialLoginSection (authWindowStatus, userSessionStatus) {
return (
<div>
{ userSessionStatus === 'EXPIRED'
? <Alert bsStyle="warning" className="login-alert">Token invalid</Alert>
: null
}
<Button
autoFocus
className={ authWindowStatus === 'OFF' ? 'modal-button' : 'modal-button-disabled' }
onClick={ this.handleLoginClicked.bind(this) }>
GitHub Login
</Button>
<div className="login-page-text-link">
<a href="#" onClick={ this.handleLoginModeSwitched.bind(this) }>Switch to token?</a>
</div>
</div>
)
}
renderTokenLoginSection (showLoginSwitch, userSessionStatus) {
return (
<form>
{ userSessionStatus === 'EXPIRED'
? <Alert bsStyle="warning" className="login-alert">Token invalid</Alert>
: null
}
<input
className="form-control"
placeholder="scope: gist"
value={ this.state.inputTokenValue }
onChange={ this.updateInputValue.bind(this) }
/>
<Button
autoFocus
className='modal-button'
onClick={ this.handleTokenLoginButtonClicked.bind(this, this.state.inputTokenValue) }>
Token Login
</Button>
{ showLoginSwitch
? <div className="login-page-text-link">
<a href="#" onClick={ this.handleLoginModeSwitched.bind(this) }>Switch to credentials?</a>
</div>
: null}
</form>
)
}
renderLoginModalBody () {
return (
<center>
{ this.renderAvatar() }
{ this.renderControlSection() }
</center>
)
}
renderAvatar () {
const { loginMode } = this.state
if (conf.get('avatar:type') === 'boring') {
return <a href="https://github.com/hackjutsu/Lepton">
<Avatar
size={ 200 }
name={ Math.random().toString(36).substr(2, 5) }
square={ false }
variant={ conf.get('avatar:boringAvatarVariant') }
colors={ ['#4D3B36', '#EB613B', '#F98F6F', '#C1D9CD', '#F7EADC'] }
/>
</a>
} else {
let profileImage = dojocatImage
if (conf.get('enterprise:enable')) {
profileImage = conf.get('enterprise:avatarUrl')
? conf.get('enterprise:avatarUrl')
: privateinvestocatImage
} else if (loginMode === LoginModeEnum.TOKEN) {
profileImage = saritocatImage
}
return <a href="https://github.com/hackjutsu/Lepton">
<Image className='profile-image-modal' src={ profileImage } rounded/>
</a>
}
}
render () {
return (
<div className='login-modal'>
<Modal.Dialog bsSize='small'>
<Modal.Header>
<Modal.Title>Login</Modal.Title>
</Modal.Header>
<Modal.Body>
{ this.renderLoginModalBody() }
</Modal.Body>
</Modal.Dialog>
</div>
)
}
}
function mapStateToProps (state) {
return {
authWindowStatus: state.authWindowStatus,
userSessionStatus: state.userSession.activeStatus
}
}
export default connect(mapStateToProps)(LoginPage)
================================================
FILE: app/containers/loginPage/index.scss
================================================
.login-modal {
.modal-button-base {
margin-top: 5px;
margin-bottom: 5px;
width: 100%;
}
.modal {
-webkit-user-select: none;
-webkit-app-region: drag;
background: #808080;
}
.modal-content {
margin-top: 100px;
min-height: 400px;
}
.profile-image-modal {
width: 200px;
height: 200px;
margin: 5px;
}
.button-group-modal {
margin: 20px 20px 0px 20px;
}
.modal-button {
@extend .modal-button-base;
}
.modal-button:focus {
outline: none;
}
.modal-button-disabled {
@extend .modal-button-base;
cursor: not-allowed;
}
.modal-button-disabled:hover {
cursor: not-allowed;
}
.login-page-text-link {
font-size: 12px;
font-style: italic;
color: gray;
margin-top: 10px;
margin-bottom: 10px;
text-decoration: underline;
}
.login-alert {
margin-bottom: 5px !important;
}
}
================================================
FILE: app/containers/navigationPanel/index.js
================================================
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Modal, Button } from 'react-bootstrap'
import { parseLangName as Resolved } from '../../utilities/parser'
import { remote } from 'electron'
import React, { Component } from 'react'
import UserPanel from '../userPanel'
import {
fetchSingleGist,
selectGist,
selectGistTag,
updatePinnedTags,
updatePinnedTagsModalStatus
} from '../../actions'
import plusIcon from './plus.svg'
import './index.scss'
const conf = remote.getGlobal('conf')
const logger = remote.getGlobal('logger')
class NavigationPanel extends Component {
constructor (props) {
super(props)
const { localPref, userSession } = this.props
const userName = userSession.profile.login
let activeSection = -1
if (localPref && localPref.get(userName)) {
const cachedActiveSession = localPref.get(userName).activeSection
if (cachedActiveSession !== undefined) {
activeSection = cachedActiveSession
}
}
logger.debug(`-----> The tag activeSection is ${activeSection}`)
this.state = {
tmpPinnedTags: new Set(),
activeSection,
}
}
handleClicked (key) {
const { selectGistTag, updateActiveGistAfterClicked, gists, gistTags } = this.props
selectGistTag(key)
updateActiveGistAfterClicked(gists, gistTags, key)
}
renderPinnedTags () {
const { pinnedTags, gistTags, activeGistTag } = this.props
const pinnedTagList = []
pinnedTags.forEach(tag => {
if (gistTags[tag]) {
pinnedTagList.push(
<div key={ tag }>
<a className={ tag === activeGistTag ? 'active-gist-tag' : 'gist-tag' }
onClick={ this.handleClicked.bind(this, tag) }>
#{ tag.startsWith('lang@') ? Resolved(tag) : tag }
</a>
</div>
)
}
})
return pinnedTagList
} // renderPinnedTags
renderLangTags () {
const { gistTags, activeGistTag } = this.props
const langTagList = []
Object.keys(gistTags)
.filter(tag => {
return tag.startsWith('lang@')
})
.sort()
.forEach(prefixedLang => {
langTagList.push(
<div key={ prefixedLang }>
<a className={ prefixedLang === activeGistTag ? 'active-gist-tag' : 'gist-tag' }
onClick={ this.handleClicked.bind(this, prefixedLang) }>
{ '#' + Resolved(prefixedLang) }
</a>
</div>
)
})
return langTagList
} // renderLangTags()
renderCustomTags () {
const { gistTags, activeGistTag } = this.props
const customTagList = []
Object.keys(gistTags)
.filter(tag => {
return !tag.startsWith('lang@')
})
.sort()
.forEach(prefixedLang => {
customTagList.push(
<div key={ prefixedLang }>
<a className={ prefixedLang === activeGistTag ? 'active-gist-tag' : 'gist-tag' }
onClick={ this.handleClicked.bind(this, prefixedLang) }>
{ '#' + Resolved(prefixedLang) }
</a>
</div>
)
})
return customTagList
}
handleConfigurePinnedTagClicked () {
const { updatePinnedTagsModalStatus, pinnedTags } = this.props
this.setState({
tmpPinnedTags: new Set(pinnedTags)
})
updatePinnedTagsModalStatus('ON')
}
handleSectionClick (index) {
const { activeSection } = this.state
const { localPref, userSession } = this.props
const nextActiveSection = activeSection === index ? -1 : index
this.setState({
activeSection: nextActiveSection
})
// Saving the activeSection to local preference
const userName = userSession.profile.login
logger.debug(`-----> Saving new tag activeSection ${nextActiveSection}`)
localPref.set(userName,
Object.assign({}, localPref.get(userName), { activeSection: nextActiveSection }))
}
renderTagSection () {
const { userSession } = this.props
const { activeSection } = this.state
let gitHubHost = 'github.com'
if (conf.get('enterprise:enable')) {
gitHubHost = conf.get('enterprise:host')
}
return (
<div className='gist-tag-section'>
<div className='starred-tag-section'>
<div className='tag-section-content'>
<a className='gist-tag' href={ `https://gist.${gitHubHost}/${userSession.profile.login}/starred` }>#starred</a>
</div>
</div>
<div className='tag-section-list'>
<div
className={
activeSection === 0
? 'tag-section tag-section-active'
: activeSection === -1
? 'tag-section'
: 'tag-section tag-section-hidden'}>
<a href='#'
className='tag-section-title'
onClick={this.handleSectionClick.bind(this, 0)}>
Languages</a>
<div className='tag-section-content'>
{ this.renderLangTags() }
</div>
</div>
<div
className={
activeSection === 1
? 'tag-section tag-section-active'
: activeSection === -1
? 'tag-section'
: 'tag-section tag-section-hidden'}>
<div className='pinned-tag-header'>
<a href='#'
onClick={this.handleSectionClick.bind(this, 1)}
className='tag-section-title'>
Pinned
</a>
<a className='configure-tag' onClick={ this.handleConfigurePinnedTagClicked.bind(this) }>
<div dangerouslySetInnerHTML={{ __html: plusIcon }} />
</a>
</div>
<div className='tag-section-content'>
{ this.renderPinnedTags() }
</div>
</div>
<div className={
activeSection === 2
? 'tag-section tag-section-active'
: activeSection === -1
? 'tag-section'
: 'tag-section tag-section-hidden'}>
<a href='#'
onClick={this.handleSectionClick.bind(this, 2)}
className='tag-section-title'>Tags</a>
<div className='tag-section-content'>
{ this.renderCustomTags() }
</div>
</div>
</div>
</div>
)
}
handleTagInPinnedTagsModalClicked (tag) {
const { tmpPinnedTags } = this.state
tmpPinnedTags.has(tag)
? tmpPinnedTags.delete(tag)
: tmpPinnedTags.add(tag)
this.setState({
tmpPinnedTags: tmpPinnedTags
})
}
closePinnedTagsModal () {
this.props.updatePinnedTagsModalStatus('OFF')
this.setState({
tmpPinnedTags: new Set()
})
}
renderAllTagsForPin () {
const { gistTags } = this.props
const { tmpPinnedTags } = this.state
const langTags = []
const customTags = []
Object.keys(gistTags).sort().forEach(item => {
item.startsWith('lang@')
? langTags.push(item)
: customTags.push(item)
})
const orderedGistTags = [...customTags, ...langTags]
const tagsForPinRows = []
let i = 1
let row = []
orderedGistTags.forEach(tag => {
row.push(
<td key={ tag }>
<a
onClick={ this.handleTagInPinnedTagsModalClicked.bind(this, tag) }
className={ tmpPinnedTags.has(tag) ? 'gist-tag-pinned' : 'gist-tag-not-pinned' }>
#{ tag.startsWith('lang@') ? Resolved(tag) : tag }
</a>
</td>)
if (i++ % 5 === 0) {
tagsForPinRows.push(<tr key={ i }>{ row }</tr>)
row = []
}
})
row && tagsForPinRows.push(<tr key={ i }>{ row }</tr>)
return (
<table className='pin-tag-table'>
<tbody>
{ tagsForPinRows }
</tbody>
</table>
)
}
handlePinnedTagSaved () {
const { tmpPinnedTags } = this.state
const { updatePinnedTags, userSession, localPref } = this.props
const pinnedTags = Array.from(tmpPinnedTags)
logger.info('[Dispatch] updatePinnedTags')
updatePinnedTags(pinnedTags)
this.closePinnedTagsModal()
// Saving the pinnedTags to local preference
const userName = userSession.profile.login
localPref.set(userName, Object.assign({}, localPref.get(userName), { pinnedTags }))
}
renderPinnedTagsModal () {
const { pinnedTagsModalStatus } = this.props
return (
<Modal
className='pinned-tags-modal'
show={ pinnedTagsModalStatus === 'ON' }
onHide={ this.closePinnedTagsModal.bind(this) }>
<Modal.Header closeButton>
<Modal.Title>Shortcuts</Modal.Title>
</Modal.Header>
<Modal.Body>
{ this.renderAllTagsForPin() }
</Modal.Body>
<Modal.Footer>
<Button onClick={ this.closePinnedTagsModal.bind(this) }>Cancel</Button>
<Button bsStyle="default" onClick={ this.handlePinnedTagSaved.bind(this) }>Save</Button>
</Modal.Footer>
</Modal>
)
}
render () {
const {
searchIndex,
updateLocalStorage,
getLoggedInUserInfo,
reSyncUserGists,
launchAuthWindow
} = this.props
return (
<div className='menu-panel'>
<UserPanel
className='user-panel'
searchIndex = { searchIndex }
updateLocalStorage = { updateLocalStorage }
getLoggedInUserInfo = { getLoggedInUserInfo }
reSyncUserGists = { reSyncUserGists }
launchAuthWindow = { launchAuthWindow }
/>
<hr/>
{ this.renderTagSection() }
{ this.renderPinnedTagsModal() }
</div>
)
}
}
function mapStateToProps (state) {
return {
gists: state.gists,
gistTags: state.gistTags,
pinnedTags: state.pinnedTags,
userSession: state.userSession,
activeGistTag: state.activeGistTag,
pinnedTagsModalStatus: state.pinnedTagsModalStatus
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
selectGistTag: selectGistTag,
selectGist: selectGist,
fetchSingleGist: fetchSingleGist,
updatePinnedTags: updatePinnedTags,
updatePinnedTagsModalStatus: updatePinnedTagsModalStatus
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(NavigationPanel)
================================================
FILE: app/containers/navigationPanel/index.scss
================================================
.menu-panel {
padding: 4px 7px;
float: left;
width: 140px;
height: 100vh;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border-color);
color: var(--text-primary);
background-color: var(--bg-secondary);
}
.menu-panel {
hr {
margin-top: 15px;
margin-bottom: 15px;
width: 100%;
border-top: 1px solid var(--border-color);
}
.font-style-base {
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Droid Sans, sans-serif;
font-size: 14px;
}
.gist-tag {
@extend .font-style-base;
color: var(--text-secondary-darken);
cursor: pointer;
transition: color .15s;
&:hover {
color: var(--text-primary)
}
}
.configure-tag {
padding: 3px;
width: 10px;
height: 14px;
box-sizing: content-box;
cursor: pointer;
fill: var(--text-secondary);
transition: fill .15s;
svg {
display: block;
width: 100%;
height: 100%
}
&:hover {
fill: #413D45;
}
}
.configure-tag-line {
// margin-top: 15px;
}
.user-panel {
flex: 0 0;
}
.gist-tag-section {
margin-left: 0px;
flex: 1 1 1px;
display: flex;
flex-direction: column;
position: relative;
}
.tag-section-list {
flex: 1 1 1px;
display: flex;
flex-direction: column;
margin-top: 15px;
// https://bugs.chromium.org/p/chromium/issues/detail?id=927066
// The item should not have a minimum height
min-height: 0;
}
.tag-section-title {
@extend .font-style-base;
font-size: 13px;
letter-spacing: .25px;
text-transform: uppercase;
font-weight: 500;
color: inherit;
margin-bottom: 6px;
position: relative;
transition: opacity .15s;
&:hover {
opacity: .65;
}
}
.tag-section {
flex: 1 0 33.333%;
max-height: 33.333%;
display: flex;
flex-direction: column;
padding: 15px 0;
border-top: 1px solid var(--border-color);
}
.tag-section-active {
flex: 1 1 auto;
max-height: none;
// https://bugs.chromium.org/p/chromium/issues/detail?id=927066
// The item should not have a minimum height
min-height: 0;
}
.tag-section-hidden {
flex: 0 0 auto;
.tag-section-title,
.pinned-tag-header {
margin-bottom: 0;
}
.tag-section-content {
height: 0;
}
}
.tag-section-content {
flex: 1 1 auto;
overflow: auto;
padding-left: 16px;
}
.active-gist-tag {
@extend .font-style-base;
color: var(--text-primary);
font-weight: bold;
cursor: pointer;
}
.pinned-tag-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 7px;
.tag-section-title {
margin-bottom: 0;
}
}
}
.pinned-tags-modal {
.pin-tag-table {
width: 100%;
}
.font-style-base {
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Droid Sans, sans-serif;
font-size: 14px;
}
.gist-tag-not-pinned {
@extend .font-style-base;
margin: 5px;
cursor: pointer;
}
.gist-tag-pinned {
@extend .font-style-base;
margin: 5px;
color: rgb(3, 123, 183);
font-weight: bold;
cursor: pointer;
}
}
================================================
FILE: app/containers/navigationPanelDetails/index.js
================================================
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { descriptionParser } from '../../utilities/parser'
import { remote } from 'electron'
import { selectGist, fetchSingleGist, updatescrollRequestStatus } from '../../actions'
import React, { Component } from 'react'
import './index.scss'
const logger = remote.getGlobal('logger')
const conf = remote.getGlobal('conf')
class NavigationPanelDetails extends Component {
componentDidUpdate () {
const { updatescrollRequestStatus, scrollRequestStatus, activeGist } = this.props
if (scrollRequestStatus === 'ON') {
this.refs[activeGist].scrollIntoView(true)
logger.info('[Dispatch] update scroll request to OFF')
updatescrollRequestStatus('OFF')
}
}
handleClicked (gistId) {
const { gists, fetchSingleGist, selectGist } = this.props
logger.info('A new gist is selected: ' + gistId)
if (!gists[gistId].details) {
logger.info('[Dispatch] fetchSingleGist ' + gistId)
fetchSingleGist(gists[gistId], gistId)
}
logger.info('[Dispatch] selectGist ' + gistId)
selectGist(gistId)
}
decideSnippetListItemClass (gistId) {
if (gistId === this.props.activeGist) {
if (this.props.gists[gistId].brief.public) {
return 'active-snippet-thumnail-public'
} else {
return 'active-snippet-thumnail-private'
}
}
return 'snippet-thumnail'
}
renderSnippetThumbnails () {
const { gists, gistTags, activeGistTag } = this.props
const snippetThumbnails = []
// When user has no gists, the default active language tag will be 'All' with
// an empty array.
if (!gistTags || !gistTags[activeGistTag] || gistTags[activeGistTag].length === 0) {
return (
<div className='snippet-thumnail'></div>
)
}
const rawGists = []
gistTags[activeGistTag].forEach((gistId) => {
// During the synchronization, gists will be updated before the gistTags,
// which introduces an interval where a gist exists in gistTags but not in
// the gists. This guard makes sure we push the gist only when it is already
// available in gists.
if (gists[gistId]) {
// Pick up the content inside the first [] as the snippet thumbnail's title.
// For example, "[Apple is delicious] It's affordable and healthy." will
// pick up "Apple is delicious" as the title. If no brackets are found,
// it shows to the original description. It provides users the flexibility
// to decide what to be shown in the thumbnail.
const gist = gists[gistId]
rawGists.push(gist)
}
})
const sortingKey = conf.get('snippet:sorting')
const sortingReverse = conf.get('snippet:sortingReverse')
if (sortingReverse) {
rawGists.sort((g1, g2) => g2.brief[sortingKey].localeCompare(g1.brief[sortingKey]))
} else {
rawGists.sort((g1, g2) => g1.brief[sortingKey].localeCompare(g2.brief[sortingKey]))
}
rawGists.forEach((gist) => {
const firstFilename = Object.keys(gist.brief.files)[0]
// '' will be converted to false, so this statement can handle situations
// for null, '' and undefined
const rawDescription = gist.brief.description || firstFilename
const { title, description } = descriptionParser(rawDescription)
const thumbnailTitle = title.length > 0 ? title : description
const gistId = gist.brief.id
snippetThumbnails.push(
<li className='snippet-thumnail-list-item' key={ gistId } ref={ gistId }>
<div className={ this.decideSnippetListItemClass(gistId) }
onClick={ this.handleClicked.bind(this, gistId) }>
<div className='snippet-thumnail-description'>{ thumbnailTitle }</div>
</div>
</li>
)
})
return snippetThumbnails
} // renderSnippetThumbnails()
render () {
return (
<div className='panel-thumbnails-background'>
<div className='panel-thumbnails-scroll'>
<div className='panel-thumbnails-content'>
<ul>
{ this.renderSnippetThumbnails() }
</ul>
</div>
</div>
</div>
)
}
}
function mapStateToProps (state) {
return {
gists: state.gists,
gistTags: state.gistTags,
activeGistTag: state.activeGistTag,
activeGist: state.activeGist,
scrollRequestStatus: state.scrollRequestStatus
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
selectGist: selectGist,
fetchSingleGist: fetchSingleGist,
updatescrollRequestStatus: updatescrollRequestStatus
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(NavigationPanelDetails)
================================================
FILE: app/containers/navigationPanelDetails/index.scss
================================================
.panel-thumbnails-background {
height: 100vh;
position: relative;
}
.panel-thumbnails-scroll {
float: left;
width: 100%;
overflow-y: overlay;
height: 100%;
visibility: visible;
}
// With .panel-thumbnails-scroll visible, this block is not needed
//
// .panel-thumbnails-content,
// .panel-thumbnails-scroll:hover {
// visibility: visible;
// }
.panel-thumbnails-scroll {
.font-style-base {
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Droid Sans, sans-serif;
font-size: 15px;
}
ul {
padding-left: 0px;
}
ul:first-child {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
ul:last-child {
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
// border-bottom: solid 1px var(--border-color);
}
.snippet-thumnail-list-item {
padding: 0 !important;
border-right-width: 0 !important;
border-left-width: 0 !important;
border-bottom: solid 1px var(--border-color);
background: var(--bg-primary);
cursor: pointer;
}
.snippet-thumnail-list-item:hover {
background-color: var(--bg-secondary);
}
.snippet-thumnail {
@extend .font-style-base;
padding: 6px;
overflow: hidden;
text-overflow: ellipsis;
}
.active-snippet-thumnail-base {
@extend .font-style-base;
padding: 6px;
font-weight: 500;
background: var(--bg-secondary);
}
.active-snippet-thumnail-public {
@extend .active-snippet-thumnail-base;
// background: #DFF0D4;
}
.active-snippet-thumnail-private {
@extend .active-snippet-thumnail-base;
// background: #F2DEDE;
}
.snippet-thumnail-description {
@extend .font-style-base;
margin: 5px;
overflow: hidden;
text-overflow: ellipsis;
}
}
================================================
FILE: app/containers/searchPage/index.js
================================================
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Modal } from 'react-bootstrap'
import { remote } from 'electron'
import React, { Component } from 'react'
import {
addLangPrefix as Prefixed,
descriptionParser,
} from '../../utilities/parser'
import {
fetchSingleGist,
selectGist,
selectGistTag,
updatescrollRequestStatus,
updateSearchWindowStatus,
} from '../../actions'
import './index.scss'
const logger = remote.getGlobal('logger')
class SearchPage extends Component {
constructor (props) {
super(props)
this.state = {
inputValue: '',
selectedIndex: 0,
searchResults: []
}
}
componentWillMount () {
const { searchIndex } = this.props
searchIndex.initFuseSearch()
}
selectPreGist () {
const { selectedIndex, searchResults } = this.state
let newSelectedIndex = selectedIndex - 1
if (!searchResults || newSelectedIndex < 0) {
newSelectedIndex = searchResults.length - 1
}
this.setState({
selectedIndex: newSelectedIndex,
})
this.refs[newSelectedIndex].scrollIntoView(false)
}
selectNextGist () {
const { selectedIndex, searchResults } = this.state
let newSelectedIndex = selectedIndex + 1
if (!searchResults || newSelectedIndex >= searchResults.length) {
newSelectedIndex = 0
}
this.setState({
selectedIndex: newSelectedIndex,
})
this.refs[newSelectedIndex].scrollIntoView(false)
}
selectCurrentGist () {
const { selectedIndex, searchResults } = this.state
if (searchResults && searchResults.length > 0) {
this.handleSnippetClicked(searchResults[selectedIndex].id)
}
}
handleKeyDown (e) {
const { updateSearchWindowStatus } = this.props
if (e.keyCode === 40) { // Down
e.preventDefault()
this.selectNextGist()
} else if (e.keyCode === 38) { // Up
e.preventDefault()
this.selectPreGist()
} else if (e.keyCode === 13) { // Enter
e.preventDefault()
this.selectCurrentGist()
} else if (e.keyCode === 27) { // Esc
e.preventDefault()
updateSearchWindowStatus('OFF')
}
}
handleSnippetClicked (gistId) {
const {
gists,
selectGistTag,
selectGist,
updateSearchWindowStatus,
updatescrollRequestStatus,
fetchSingleGist
} = this.props
if (!gists[gistId].details) {
logger.info('[Dispatch] fetchSingleGist ' + gistId)
fetchSingleGist(gists[gistId], gistId)
}
logger.info('[Dispatch] update scroll request to ON')
updatescrollRequestStatus('ON')
logger.info('[Dispatch] selectGist ' + gistId)
selectGist(gistId)
selectGistTag(Prefixed('All'))
updateSearchWindowStatus('OFF')
}
updateInputValue (evt) {
this.setState({
selectedIndex: 0,
inputValue: evt.target.value
})
}
queryInputValue (evt) {
const inputValue = evt.target.value
const searchIndex = this.props.searchIndex
const results = searchIndex.fuseSearch(inputValue)
this.setState({
searchResults: results
})
}
renderSnippetDescription (rawDescription) {
const { title, description } = descriptionParser(rawDescription)
const htmlForDescriptionSection = []
if (title.length > 0) {
htmlForDescriptionSection.push(<div className='title-section' key='title'>{ title }</div>)
}
htmlForDescriptionSection.push(<div className='description-section' key='description'>{ description }</div>)
return (
<div>
{ htmlForDescriptionSection }
</div>
)
}
renderSearchResults () {
const { searchResults, selectedIndex, inputValue } = this.state
// FIXME: In some unknown circumstance, searchResults is undefined. So we put a
// guard here. We should remove it once we better understand the mechanism
// behind it.
if (!inputValue || !searchResults) return null
if (inputValue.length > 0 && searchResults.length === 0) {
return (
<div className='not-found-msg'>
No result found...
</div>
)
}
const resultsJSXGroup = []
searchResults.forEach((gist, index) => {
const highlightedDescription = gist.description
let filenames = []
// FIXME: In some rare cases, gist.filename is undefined in runtime for some unknown reason. So
// we place a guard here as a workaround.
if (gist.filename) {
filenames = gist.filename.split(',').filter(file => file.trim()).map(file => {
return (
<div className='gist-tag' key={ file.trim() }>{ file }</div>
)
})
}
resultsJSXGroup.push(
<li
className={ index === selectedIndex
? 'search-result-item-selected'
: 'search-result-item' }
key={ gist.id }
ref={ index }
onClick={ this.handleSnippetClicked.bind(this, gist.id) }>
<div className='snippet-description'>{ this.renderSnippetDescription(highlightedDescription) }</div>
<div className='gist-tag-group'>{ filenames }</div>
</li>
)
})
return resultsJSXGroup
}
renderSearchModalBody () {
return (
<div>
<input
type="text"
className='search-box'
placeholder='Search for description, tags, file names...'
autoFocus
value={ this.state.inputValue }
onChange={ this.updateInputValue.bind(this) }
onKeyDown={ this.handleKeyDown.bind(this) }
onKeyUp={ this.queryInputValue.bind(this) }/>
<ul className='result-group'>
{ this.renderSearchResults() }
</ul>
</div>
)
}
render () {
return (
<div className='search-modal'>
<Modal.Dialog bsSize='large'>
<Modal.Body>
{ this.renderSearchModalBody() }
</Modal.Body>
</Modal.Dialog>
</div>
)
}
}
function mapStateToProps (state) {
return {
searchWindowStatus: state.authWindowStatus,
userSessionStatus: state.userSession.activeStatus,
gists: state.gists
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
selectGistTag: selectGistTag,
selectGist: selectGist,
fetchSingleGist: fetchSingleGist,
updateSearchWindowStatus: updateSearchWindowStatus,
updatescrollRequestStatus: updatescrollRequestStatus
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchPage)
================================================
FILE: app/containers/searchPage/index.scss
================================================
.search-modal {
.font-style-base {
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Droid Sans, sans-serif;
font-size: 13px;
}
.search-box {
@extend .font-style-base;
font-size: 16px;
width: 100%;
padding: 10px;
border: 0;
background: var(--bg-primary);
}
.search-box:focus {
outline:none;
}
.result-group {
max-height: 70vh;
list-style-type: none;
padding: 0px;
overflow: auto;
}
.search-result-item {
padding: 10px 10px;
border-top: solid 1px var(--border-color);
}
.search-result-item:hover {
background: rgba(0, 0, 0, 0.1);
cursor: pointer;
}
.search-result-item-selected {
@extend .search-result-item;
background: rgba(0, 0, 0, 0.15);
}
.list-group {
margin-bottom: 0px;
}
.list-group-item {
border-left: 0;
border-right: 0;
}
.list-group-item:first-child {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
}
.list-group-item:last-child {
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
.snippet-description {
@extend .font-style-base;
}
.not-found-msg {
@extend .font-style-base;
font-size: 15px;
padding: 10px;
color: var(--text-secondary);
}
.gist-tag-group {
margin-top: 10px;
}
.gist-tag {
display: inline;
margin-right: 10px;
font-size: 11px;
color: #4078C0;
}
.gist-tag-selected {
@extend .gist-tag;
border:solid 2px red;
}
.modal-button-base {
margin-top: 5px;
margin-bottom: 5px;
width: 100%;
}
.modal {
-webkit-user-select: none;
-webkit-app-region: drag;
background: rgba(0, 0, 0, 0.5);
}
.modal-content {
margin-top: 100px;
}
.tip {
@extend .font-style-base;
margin-top: -27px;
margin-bottom: 10px;
width: 100%;
text-align: right;
font-size: 10px;
color: var(--text-secondary);
font-style: italic;
}
.title-section {
@extend .font-style-base;
// font-size: 13px;
font-weight: bold;
margin-bottom: 10px;
}
.description-section {
@extend .font-style-base;
font-size: 12px;
color: var(--text-secondary);
}
.custom-tags-section {
@extend .font-style-base;
color: var(--text-secondary);
font-size: 10px;
margin-left: 2px;
margin-top: 3px;
}
}
================================================
FILE: app/containers/snippet/index.js
================================================
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { default as GistEditorForm, UPDATE_GIST } from '../gistEditorForm'
import { Panel, Modal, Button, ProgressBar, Collapse } from 'react-bootstrap'
import { remote, clipboard, ipcRenderer } from 'electron'
import Autolinker from 'autolinker'
import CodeArea from '../codeArea'
import HumanReadableTime from 'human-readable-time'
import Moment from 'moment'
import { notifySuccess, notifyFailure } from '../../utilities/notifier'
import React, { Component } from 'react'
import {
addLangPrefix as Prefixed,
descriptionParser,
parseCustomTags,
} from '../../utilities/parser'
import {
selectGistTag,
updateFileExpandStatus,
updateGistDeleteModeStatus,
updateGistEditModeStatus,
updateGistRawModal,
updateGistTags,
updateSingleGist,
} from '../../actions'
import {
DELETE_SINGLE_GIST,
EDIT_SINGLE_GIST,
getGitHubApi,
} from '../../utilities/githubApi'
import './index.scss'
import editIcon from './ei-edit.svg'
import eyeIcon from './ei-eye.svg'
import openInWebIcon from './ei-share.svg'
import secretIcon from './lock.svg'
import tagsIcon from './tags.svg'
import trashIcon from './ei-trash.svg'
const conf = remote.getGlobal('conf')
const logger = remote.getGlobal('logger')
const kIsExpanded = conf.get('snippet:expanded')
const kTabLength = conf.get('editor:tabSize')
class Snippet extends Component {
componentDidMount () {
ipcRenderer.on('edit-gist-renderer', () => {
this.showGistEditorModal()
})
ipcRenderer.on('exit-editor', () => {
this.closeGistEditorModal()
})
ipcRenderer.on('delete-gist', () => {
this.showDeleteModal()
})
}
componentWillUnmount () {
ipcRenderer.removeAllListeners('edit-gist-renderer')
ipcRenderer.removeAllListeners('exit-editor')
}
showDeleteModal () {
this.props.updateGistDeleteModeStatus('ON')
}
closeDeleteModal () {
this.props.updateGistDeleteModeStatus('OFF')
}
handleDeleteClicked () {
const { accessToken, activeGist } = this.props
getGitHubApi(DELETE_SINGLE_GIST)(
accessToken,
activeGist)
.catch(err => {
logger.error('Failed to delete the gist ' + activeGist)
logger.error(JSON.stringify(err))
notifyFailure('Deletion failed', 'Please check your network condition. 02')
})
.then(data => {
logger.info('The gist ' + activeGist + ' has been deleted.')
notifySuccess('The gist has been deleted')
// For performance purpose, we should perform an internal update, like what
// we're doing for creating/edit gists. However, since delete is an infrequent
// operation, we decide to just call the resync method and keep the logic
// simple.
this.props.reSyncUserGists()
})
.finally(() => {
this.closeDeleteModal()
})
}
renderDeleteModal () {
return (
<div className='static-modal'>
<Modal show={ this.props.gistDeleteModalStatus === 'ON' } bsSize='small' keyboard={ true }>
<Modal.Header>
<Modal.Title>Delete the gist?</Modal.Title>
</Modal.Header>
<Modal.Footer>
<Button onClick={ this.closeDeleteModal.bind(this) }>cancel</Button>
<Button
bsStyle='danger'
onClick={ this.handleDeleteClicked.bind(this) }>delete</Button>
</Modal.Footer>
</Modal>
</div>
)
}
showGistEditorModal () {
const { gists, activeGist, updateGistEditModeStatus } = this.props
if (gists && gists[activeGist] && gists[activeGist].details) {
updateGistEditModeStatus('ON')
}
}
closeGistEditorModal () {
this.props.updateGistEditModeStatus('OFF')
}
handleGistEditorFormSubmit (data) {
const { gists, activeGist, accessToken } = this.props
const description = data.description.trim()
const processedFiles = {}
data.gistFiles.forEach((file) => {
processedFiles[file.filename.trim()] = {
content: file.content
}
})
const activeSnippet = gists[activeGist]
for (const preFile in activeSnippet.details.files) {
if (!processedFiles[preFile]) {
processedFiles[preFile] = null
}
}
// In the past, we close the dialog after getting response from the server
// both for the “create” and “edit” actions. However, the state change for
// the “edit” action is less intensive than “create”, for example, the active
// language tag and the active gist are likely be the same as before.
// Therefore, we decide the close the dialog without waiting for the server’s
// response, which provides better user experience.
this.closeGistEditorModal()
return getGitHubApi(EDIT_SINGLE_GIST)(
accessToken,
activeGist,
description,
processedFiles)
.catch((err) => {
notifyFailure('Gist update failed')
logger.error(JSON.stringify(err))
})
.then((response) => {
this.updateGistsStoreWithUpdatedGist(response)
})
}
updateGistsStoreWithUpdatedGist (gistDetails) {
const {
gists, activeGist, gistTags, activeGistTag, updateSingleGist,
updateGistTags, selectGistTag, searchIndex
} = this.props
const gistId = gistDetails.id
const files = gistDetails.files
const activeSnippet = gists[activeGist]
const preLangs = activeSnippet.langs
const preCustomTags = parseCustomTags(descriptionParser(activeSnippet.brief.description).customTags)
// Adding files in an eidt could introduce some changes to the gistTags.
// 1) if a gist has a new language, we should add the gist id to this
// language tag, ie gistTags[language] 2) if the new language doesn't
// exist, we should add the new language to gistTags.
const newLangs = new Set()
let filenameRecords = ''
Object.keys(files).forEach(filename => {
// leave a space in between to help tokenization
filenameRecords += ', ' + filename
const file = files[filename]
const language = file.language || 'Other'
newLangs.add(language)
const prefixedLang = Prefixed(language)
if (Object.prototype.hasOwnProperty.call(gistTags, prefixedLang)) {
if (!gistTags[prefixedLang].includes(gistId)) {
gistTags[prefixedLang].unshift(gistId)
}
} else {
gistTags[prefixedLang] = []
gistTags[prefixedLang].unshift(gistId)
}
})
// Removing files in an eidt could introduce some changes to the gistTags.
// 1) if a gist no long has a language, we should remove the gist id from
// this language tag 2) if the updated language tag is empty, we should remove
// this tag at all.
preLangs.forEach(language => {
if (!newLangs.has(language)) {
const prefixedLang = Prefixed(language)
gistTags[prefixedLang] = gistTags[prefixedLang].filter(value => {
return value !== gistId
})
if (gistTags[prefixedLang].length === 0) {
delete gistTags[prefixedLang]
}
}
})
// We update the custom tags with the similar reasons mentioned above
const newCustomTags = parseCustomTags(descriptionParser(gistDetails.description).customTags)
newCustomTags.forEach(tag => {
if (Object.prototype.hasOwnProperty.call(gistTags, tag)) {
if (!gistTags[tag].includes(gistId)) {
gistTags[tag].unshift(gistId)
}
} else {
gistTags[tag] = []
gistTags[tag].unshift(gistId)
}
})
preCustomTags.forEach(tag => {
if (!newCustomTags.includes(tag)) {
gistTags[tag] = gistTags[tag].filter(value => {
return value !== gistId
})
}
if (gistTags[tag].length === 0) {
delete gistTags[tag]
}
})
const updatedGist = {}
updatedGist[gistId] = {
langs: newLangs,
brief: gistDetails,
details: gistDetails,
filename: filenameRecords
}
logger.info('[Dispatch] updateSingleGist')
updateSingleGist(updatedGist)
logger.info('[Dispatch] updateGistTags')
updateGistTags(gistTags)
// If the previous active language tag is no longer valid, for example,
// user deletes all cpp files inside a gist when C++ tag is the ative tag,
// or the gist array for the preivous active language tag is empty, we
// choose to fall back to 'All'.
if (!gistTags[activeGistTag] || !gistTags[activeGistTag].includes(gistId)) {
logger.info('[Dispatch] selectGistTag')
selectGistTag(Prefixed('All'))
}
// logger.info('[Dispatch] selectGist')
// this.props.selectGist(gistId)
// Update the search index
let langSearchRecords = ''
newLangs.forEach(lang => {
langSearchRecords += ',' + lang
})
searchIndex.updateFuseIndex({
id: gistId,
description: gistDetails.description,
language: langSearchRecords,
filename: filenameRecords
})
notifySuccess('Gist updated', HumanReadableTime(new Date()))
}
renderGistEditorModalBody (description, fileArray, isPrivate) {
const initialData = Object.assign({
description: description,
gists: fileArray,
private: isPrivate
})
return (
<GistEditorForm
initialData={ initialData }
formStyle = { UPDATE_GIST }
handleCancel = { this.closeGistEditorModal.bind(this) }
onSubmit={ this.handleGistEditorFormSubmit.bind(this) }/>
)
}
renderGistEditorModal (description, fileArray, isPrivate) {
return (
<Modal
bsSize='large'
dialogClassName='edit-modal'
animation={ false }
backdrop='static'
keyboard={ false }
show={ this.props.gistEditModalStatus === 'ON' }
onHide={ this.closeGistEditorModal.bind(this)}>
<Modal.Header closeButton>
<Modal.Title>Edit</Modal.Title>
</Modal.Header>
<Modal.Body>
{ this.renderGistEditorModalBody(description, fileArray, isPrivate) }
</Modal.Body>
</Modal>
)
}
closeRawModal () {
this.props.updateGistRawModal({
status: 'OFF',
file: null,
content: null,
link: null
})
}
handleCopyGistLinkClicked (snippet, file) {
const link = snippet.details.html_url + '#file-' + file.filename.replace(/\./g, '-').toLowerCase()
clipboard.writeText(link)
notifySuccess('Copied', 'The link has been copied to the clipboard.')
}
handleCopyGistFileClicked (gist) {
clipboard.writeText(gist.content)
notifySuccess('Copied', 'The content has been copied to the clipboard.')
}
showRawModalModal (gist) {
this.props.updateGistRawModal({
status: 'ON',
file: gist.filename,
content: gist.content,
link: gist.raw_url
})
}
renderRawModal () {
const { gistRawModal } = this.props
return (
<Modal
className='raw-modal'
show={ gistRawModal.status === 'ON' }
onHide={ this.closeRawModal.bind(this) }>
<Modal.Header closeButton>
<Modal.Title>
{ gistRawModal.file }
<a className='copy-raw-link' href='#' onClick={ this.handleCopyRawLinkClicked.bind(this, gistRawModal.link) }>LINK</a>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<textarea
ref='rawModalText'
className='code-area-raw'
defaultValue={ gistRawModal.content }
onDoubleClick={ this.selectText.bind(this) } />
</Modal.Body>
</Modal>
)
}
selectText () {
this.refs.rawModalText.select()
}
handleCopyRawLinkClicked (url) {
clipboard.writeText(url)
notifySuccess('Copied', 'The raw file link has been copied to the clipboard.')
}
renderPanelHeader (activeSnippet) {
return (
<div className='header-table'>
<div className='line'>
<div className='header-title'>
{ activeSnippet.brief.public
? 'public gist'
: [
<div key='icon'className='secret-icon' dangerouslySetInnerHTML={{ __html: secretIcon }} />,
<span key='description' >secret gist</span>
]
}
</div>
<div className='header-controls'>
<a className='snippet-control'
title='Edit'
href='#'
onClick={ this.showGistEditorModal.bind(this) }>
<div dangerouslySetInnerHTML={{ __html: editIcon }} />
</a>
<a className='snippet-control'
title='Open in Web'
href={ activeSnippet.brief.html_url }>
<div dangerouslySetInnerHTML={{ __html: openInWebIcon }} />
</a>
<a className='snippet-control'
title='Revisions'
href={ activeSnippet.brief.html_url + '/revisions' }>
<div dangerouslySetInnerHTML={{ __html: eyeIcon }} />
</a>
{
this.props.immersiveMode === 'OFF'
? <a className='snippet-control'
title='Delete'
href='#'
onClick={ this.showDeleteModal.bind(this) }>
<div dangerouslySetInnerHTML={{ __html: trashIcon }} />
</a>
: null
}
</div>
</div>
</div>
)
}
renderSnippetDescription (gist) {
const { title, description, customTags } = descriptionParser(gist.brief.description)
const htmlForDescriptionSection = []
if (title.length > 0) {
htmlForDescriptionSection.push(<div className='title-section' key='title'>{ title }</div>)
}
htmlForDescriptionSection.push(
<div className='description-section' key='description'
dangerouslySetInnerHTML={ {
__html: Autolinker.link(description, {
stripPrefix: {
scheme: true,
www: true
},
newWindow: false
})
} }/>
)
htmlForDescriptionSection.push(
<div className='custom-tags-section' key='customTags'>
{ customTags.length > 0
? <span className='custom-tags'>
<div
className='custom-tags-icon'
dangerouslySetInnerHTML={{ __html: tagsIcon }} />
<span>{ customTags.substring('#tags:'.length) }</span>
</span>
: null }
<span className='update-date'>
{ 'Last active ' + Moment(gist.brief.updated_at).fromNow() }
</span>
</div>)
return (
<div>
{ htmlForDescriptionSection }
</div>
)
}
handleCollapseClicked (filename) {
const { activeGist, fileExpandStatus, updateFileExpandStatus } = this.props
const key = activeGist + '-' + filename
if (fileExpandStatus[key] === undefined) {
// If the file is clicked for the first time, it has no records in the
// fileExpandStatus list. Therefore, its value is undefined. We consider
// it in the default status(either expanded or collapsed, depending on
// settings in .leptonrc).
fileExpandStatus[key] = !kIsExpanded
} else {
fileExpandStatus[key] = !fileExpandStatus[key]
}
updateFileExpandStatus(fileExpandStatus)
}
render () {
const { gists, activeGist, fileExpandStatus } = this.props
const activeSnippet = gists[activeGist]
if (!activeSnippet) return null
const fileHtmlArray = []
const fileArray = []
if (activeSnippet.details) {
const fileList = activeSnippet.details.files
for (const key in fileList) {
const gistFile = fileList[key]
fileArray.push(Object.assign({
filename: gistFile.filename,
content: gistFile.content
}))
const expandStatusKey = activeGist + '-' + key
// undefined should be treated as the default value as explained above
let isExpanded = kIsExpanded
if (fileExpandStatus[expandStatusKey] === false) {
isExpanded = false
} else if (fileExpandStatus[expandStatusKey] === true) {
isExpanded = true
}
fileHtmlArray.push(
<div key={ key }>
<hr/>
<div className={ gistFile.language === 'Markdown' ? 'file-header-md' : 'file-header' }>
<a href='#'
className={isExpanded ? 'file-expand is-expanded' : 'file-expand'}
onClick={ this.handleCollapseClicked.bind(this, key) }>
<span>{ gistFile.filename }</span>
</a>
<div className='file-header-controls'>
<a
href='#'
className='file-header-control'
onClick={ this.handleCopyGistLinkClicked.bind(this, activeSnippet, gistFile) }>
SHARE
</a>
<a
href='#'
className='file-header-control'
onClick={ this.showRawModalModal.bind(this, gistFile) }>
RAW
</a>
<a
href='#'
className='file-header-control'
onClick={ this.handleCopyGistFileClicked.bind(this, gistFile) }>
COPY
</a>
</div>
</div>
<Collapse in={ isExpanded }>
<div className='collapsable-code-area'>
<CodeArea filename={gistFile.filename} content={gistFile.content} language={gistFile.language} kTabLength={kTabLength}/>
</div>
</Collapse>
</div>
)
}
}
return (
<div className='snippet-box'>
<Panel className='snippet-code'
bsStyle={ activeSnippet.brief.public ? 'default' : 'danger' }>
<Panel.Heading>{ this.renderPanelHeader(activeSnippet) }</Panel.Heading>
<Panel.Body>
<div className='snippet-description'>{ this.renderSnippetDescription(activeSnippet) }</div>
{ activeSnippet.details
? null
: <ProgressBar className='snippet-progressbar' active now={ 100 }/> }
{ this.renderGistEditorModal(activeSnippet.brief.description, fileArray, !activeSnippet.brief.public) }
{ this.renderRawModal() }
{ this.renderDeleteModal() }
{ fileHtmlArray }
</Panel.Body>
</Panel>
</div>
)
}
}
function mapStateToProps (state) {
return {
gists: state.gists,
activeGistTag: state.activeGistTag,
activeGist: state.activeGist,
userSession: state.userSession,
accessToken: state.accessToken,
gistTags: state.gistTags,
immersiveMode: state.immersiveMode,
gistRawModal: state.gistRawModal,
gistEditModalStatus: state.gistEditModalStatus,
gistDeleteModalStatus: state.gistDeleteModalStatus,
fileExpandStatus: state.fileExpandStatus
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
updateSingleGist: updateSingleGist,
updateGistTags: updateGistTags,
selectGistTag: selectGistTag,
updateGistEditModeStatus: updateGistEditModeStatus,
updateGistDeleteModeStatus: updateGistDeleteModeStatus,
updateGistRawModal: updateGistRawModal,
updateFileExpandStatus: updateFileExpandStatus
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Snippet)
================================================
FILE: app/containers/snippet/index.scss
================================================
.font-style-base {
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Droid Sans, sans-serif;
font-size: 14px;
}
.font-style-md {
font-family: Helvetica, Arial, sans-serif;
font-size: 15px;
}
.snippet-box {
.header-table {
width: 100%;
display: table;
}
.line {
display: flex;
justify-content: space-between;
align-items: center;
}
.welcome-section {
@extend .font-style-base;
}
.header-title {
@extend .font-style-base;
font-size: 13px;
text-transform: uppercase;
& > span {
vertical-align: middle;
}
}
.secret-icon {
display: inline-block;
vertical-align: middle;
margin-right: 4px;
position: relative;
fill: currentColor;
width: 11px;
svg {
display: block;
}
}
.header-controls {
display: flex;
align-items: center;
}
.snippet-control {
@extend .font-style-base;
margin-left: 15px;
height: 20px;
width: 20px;
display: block;
position: relative;
transition: opacity .15s;
opacity: .7;
&:hover {
opacity: 1;
}
svg {
display: block;
position: absolute;
top: 50%;
left: 50%;
height: 28px;
width: 28px;
transform: translate(-50%, -50%);
fill: var(--text-primary);
}
}
.customized-button {
@extend .font-style-base;
margin: 5px 5px 0px 10px;
}
.customized-button {
@extend .font-style-base;
margin: 5px 5px 0px 10px;
}
.snippet-code {
min-height: 100vh;
border-color: var(--border-color);
border-radius: 0;
border-bottom: none;
margin-bottom: 0;
& > .panel-heading {
position: absolute;
top: 0;
left: 1px;
width: 100%;
z-index: 5;
background-color: var(--bg-primary);
border: none!important;
color: inherit;
padding: 12px 15px;
&:after {
content: '';
position: absolute;
height: 7px;
bottom: -7px;
left: 0;
right: 0;
box-shadow: var(--shadow-color) 0 7px 7px -7px inset;
}
}
}
.panel-body {
margin-top: 55px;
padding: 0;
}
.snippet-description {
@extend .font-style-base;
padding: 0 15px 7px;
}
.code-table {
font-family: Consolas, Menlo, Monaco, "Courier New", monospace;
font-size: 13px !important;
color: var(--text-code-block);
}
.code-area {
@extend .font-style-base;
border: 0px;
border-bottom: 1px solid var(--border-color);
overflow: hidden;
}
pre {
padding: 10px 12px 10px 2px;
background-color: var(--bg-primary);
border: none;
border-radius: 0;
font-size: 13px;
margin: 0 10px;
}
.file-header {
@extend .font-style-base;
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 15px;
background-color: var(--bg-secondary);
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
}
.file-expand {
font-size: 15px;
transition: opacity .15s;
&:hover {
opacity: .65;
}
&::before {
content: '';
display: inline-block;
vertical-align: middle;
position: relative;
width: 7px;
height: 7px;
margin-right: 8px;
border-width: 0 0 1px 1px;
border-color: currentColor;
border-style: solid;
transform: rotate(-135deg);
transition: transform .12s ease-in-out;
}
&.is-expanded::before {
transform: translateY(-2px) rotate(-45deg);
}
span {
display: inline-block;
vertical-align: middle;
}
}
.file-header-control {
font-size: 12px;
transition: opacity .15s;
&:not(:last-child) {
margin-right: 10px;
}
&:hover {
opacity: .65;
}
}
.file-header-md {
@extend .file-header;
// background: rgba(0,0,0,0.03);
// border: rgb(204,204,204) 1px solid;
// border-radius: 5px 5px 0px 0px;
// padding: 10px 10px 10px 10px;
// margin-bottom: -1px;
}
.customized-button-file-header {
@extend .customized-button;
margin-top: 2px;
}
.title-section {
@extend .font-style-base;
font-size: 25px;
font-weight: normal;
margin-bottom: 7px;
padding-right: 15px;
margin-bottom: 3px;
}
.description-section {
@extend .font-style-base;
color: var(--text-secondary);
a {
color: rgb(3, 123, 183);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
}
.custom-tags-section {
margin-top: 15px;
&:after {
content: '';
display: table;
clear: both;
}
}
.custom-tags,
.update-date {
@extend .font-style-base;
color: var(--text-secondary);
font-size: 12px;
font-style: italic;
}
.custom-tags {
float: left;
span {
display: inline-block;
vertical-align: middle;
}
}
.custom-tags-icon {
display: inline-block;
vertical-align: middle;
height: 16px;
width: 16px;
margin-right: 2px;
position: relative;
}
.update-date {
float: right;
}
.snippet-progressbar {
height: 3px;
margin-top: 20px;
}
.line-number {
text-align: right;
padding-right: 20px;
display: inline-block;
color: var(--text-secondary);
opacity: .5;
width: 42px;
font-size: 12px;
font-style: normal;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
.markdown-section {
@extend .font-style-md;
padding: 15px 20px;
}
hr {
margin-top: 0 !important;
margin-bottom: 7px !important;
border: none !important;
}
}
.edit-modal {
width: 98vw;
}
.raw-modal {
.modal-dialog {
width: 98vw !important;
}
.copy-raw-link {
@extend .font-style-base;
margin-top: 2px;
margin-right: 30px;
float: right;
}
.modal-content {
@extend .font-style-base;
min-height: 300px;
white-space: nowrap;
overflow: auto;
}
.code-area-raw {
@extend .font-style-base;
width: 100%;
min-height: 550px;
white-space: pre-wrap;
border: 0px;
background: var(--bg-primary);
}
}
// CSS technique to disable the line numbers from being selected
// http://codepen.io/danoc/pen/ByGKZv
[data-pseudo-content]::before,
[data-pseudo-content--before]::before,
[data-pseudo-content--after]::after {
content: attr(data-pseudo-content);
}
================================================
FILE: app/containers/snippetPanel/index.js
================================================
import { connect } from 'react-redux'
import { Well } from 'react-bootstrap'
import React, { Component } from 'react'
import Snippet from '../snippet'
import './index.scss'
class SnippetPanel extends Component {
renderEmptySnippetSection () {
// This happens when the user has no gists
return (
<Well className='welcome-section'>Click <b>#new</b> on the left panel to create a gist.</Well>
)
}
renderNormalSnippetSection () {
const { gists, activeGist, searchIndex, reSyncUserGists } = this.props
return (
<Snippet
searchIndex = { searchIndex }
reSyncUserGists={ reSyncUserGists }
snippet={ gists[activeGist] } />
)
}
render () {
const { gists, activeGist, immersiveMode } = this.props
return (
<div className={ immersiveMode === 'ON' ? 'snippet-panel-immersive' : 'snippet-panel' }>
<div className='snippet-panel-content'>
{ !gists || !activeGist || !gists[activeGist]
? this.renderEmptySnippetSection()
: this.renderNormalSnippetSection() }
</div>
</div>
)
}
}
function mapStateToProps (state) {
return {
activeGist: state.activeGist,
gists: state.gists,
immersiveMode: state.immersiveMode
}
}
export default connect(mapStateToProps)(SnippetPanel)
================================================
FILE: app/containers/snippetPanel/index.scss
================================================
.Pane2 {
overflow: hidden;
}
.snippet-panel {
float: right;
width: 100%;
overflow: overlay;
height: 100vh;
}
.snippet-panel-immersive {
@extend .snippet-panel;
width: 100%;
.font-style-base {
font-family: Consolas, Menlo, Monaco, "Courier New", monospace;
font-size: 13px;
}
.welcome-section {
@extend .font-style-base;
font-size: 15px;
text-align: center;
height: 100vh;
}
}
.snippet-panel {
.font-style-base {
font-family: Consolas, Menlo, Monaco, "Courier New", monospace;
font-size: 13px;
}
.welcome-section {
@extend .font-style-base;
font-size: 15px;
text-align: center;
height: 100vh;
}
}
================================================
FILE: app/containers/userPanel/index.js
================================================
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { default as GistEditorForm, NEW_GIST } from '../gistEditorForm'
import { Image, Modal, Button, ProgressBar } from 'react-bootstrap'
import { remote, ipcRenderer } from 'electron'
import HumanReadableTime from 'human-readable-time'
import { notifySuccess, notifyFailure } from '../../utilities/notifier'
import React, { Component } from 'react'
import {
addLangPrefix as Prefixed,
descriptionParser,
parseCustomTags,
} from '../../utilities/parser'
import {
logoutUserSession,
removeAccessToken,
selectGist,
selectGistTag,
updateGistNewModeStatus,
updateGistTags,
updateLogoutModalStatus,
updateSingleGist,
} from '../../actions/index'
import {
CREATE_SINGLE_GIST,
getGitHubApi,
} from '../../utilities/githubApi'
import './index.scss'
import dojocatImage from '../../utilities/octodex/dojocat.jpg'
import logoutIcon from './logout.svg'
import newIcon from './new.svg'
import privateinvestocatImage from '../../utilities/octodex/privateinvestocat.jpg'
import syncIcon from './sync.svg'
const conf = remote.getGlobal('conf')
const logger = remote.getGlobal('logger')
let defaultImage = dojocatImage
if (conf.get('enterprise:enable')) {
defaultImage = privateinvestocatImage
if (conf.get('enterprise:avatarUrl')) {
defaultImage = conf.get('enterprise:avatarUrl')
}
}
const kIsPrivate = conf.get('snippet:newSnippetPrivate')
const hideProfilePhoto = conf.get('userPanel:hideProfilePhoto')
class UserPanel extends Component {
componentDidMount () {
ipcRenderer.on('new-gist-renderer', () => {
this.handleNewGistClicked()
})
ipcRenderer.on('exit-editor', () => {
this.closeGistEditorModal()
})
ipcRenderer.on('sync-gists', () => {
this.handleSyncClicked()
})
}
componentWillUnmount () {
ipcRenderer.removeAllListeners('new-gist-renderer')
}
handleCreateSingleGist (data) {
const isPublic = data.private === undefined ? true : !data.private
const description = data.description.trim()
const processedFiles = {}
data.gistFiles.forEach((file) => {
processedFiles[file.filename.trim()] = {
content: file.content
}
})
return getGitHubApi(CREATE_SINGLE_GIST)(this.props.accessToken, description, processedFiles, isPublic)
.catch((err) => {
notifyFailure('Gist creation failed')
logger.error(JSON.stringify(err))
})
.then((response) => {
this.updateGistsStoreWithNewGist(response)
})
.finally(() => {
this.closeGistEditorModal()
})
}
updateGistsStoreWithNewGist (gistDetails) {
const {
gistTags,
updateSingleGist,
updateGistTags,
selectGistTag,
selectGist,
searchIndex
} = this.props
const gistId = gistDetails.id
const files = gistDetails.files
// update the language tags
const langs = new Set()
let filenameRecords = ''
gistTags[Prefixed('All')].unshift(gistId)
Object.keys(files).forEach(filename => {
// leave a space in between to help tokenization
filenameRecords += ', ' + filename
const language = files[filename].language || 'Other'
langs.add(language)
const prefixedLang = Prefixed(language)
if (Object.prototype.hasOwnProperty.call(gistTags, prefixedLang)) {
gistTags[prefixedLang].unshift(gistId)
} else {
gistTags[prefixedLang] = []
gistTags[prefixedLang].unshift(gistId)
}
})
// update the custom tags
const customTags = parseCustomTags(descriptionParser(gistDetails.description).customTags)
customTags.forEach(tag => {
if (Object.prototype.hasOwnProperty.call(gistTags, tag)) {
gistTags[tag].unshift(gistDetails.id)
} else {
gistTags[tag] = []
gistTags[tag].unshift(gistDetails.id)
}
})
const newGist = {}
newGist[gistId] = {
langs: langs,
brief: gistDetails,
details: gistDetails
}
logger.info('[Dispatch] updateSingleGist')
updateSingleGist(newGist)
logger.info('[Dispatch] updateGistTags')
updateGistTags(gistTags)
logger.info('[Dispatch] selectGistTag')
selectGistTag(Prefixed('All'))
logger.info('[Dispatch] selectGist')
selectGist(gistId)
let langSearchRecords = ''
langs.forEach(lang => {
langSearchRecords += ',' + lang
})
// update the search index
searchIndex.addToFuseIndex({
id: gistId,
description: gistDetails.description,
language: langSearchRecords,
filename: filenameRecords
})
notifySuccess('Gist created', HumanReadableTime(new Date()))
}
renderGistEditorModalBody () {
const initialData = {
description: '',
private: kIsPrivate,
gists: [
{ filename: '', content: '' }
]
}
return (
<GistEditorForm
initialData={ initialData }
formStyle={ NEW_GIST }
handleCancel = { this.closeGistEditorModal.bind(this) }
onSubmit={ this.handleCreateSingleGist.bind(this) }></GistEditorForm>
)
}
renderGistEditorModal () {
return (
<Modal
bsSize='large'
dialogClassName='new-modal'
animation={ false }
backdrop='static'
keyboard={ false }
show={ this.props.gistNewModalStatus === 'ON' }
onHide={ this.closeGistEditorModal.bind(this)}>
<Modal.Header closeButton>
<Modal.Title>New</Modal.Title>
</Modal.Header>
<Modal.Body>
{ this.renderGistEditorModalBody.bind(this)() }
</Modal.Body>
</Modal>
)
}
renderInSection () {
return (
<div>
{ this.renderGistEditorModal() }
<a href='#'
className='user-panel-button'
onClick={ this.handleLogoutClicked.bind(this) }>
<div
className='user-panel-icon'
dangerouslySetInnerHTML={{ __html: logoutIcon }} />
<span>Logout</span>
</a>
<br/>
<a href='#'
className='user-panel-button'
onClick={ this.handleNewGistClicked.bind(this) }>
<div
className='user-panel-icon'
dangerouslySetInnerHTML={{ __html: newIcon }} />
<span>New</span>
</a>
<br/>
<a href='#'
className='user-panel-button'
onClick={ this.handleSyncClicked.bind(this) }>
<div
className='user-panel-icon'
dangerouslySetInnerHTML={{ __html: syncIcon }} />
<span>Sync</span>
</a>
<div className='customized-tag-small'>{ this.props.syncTime }</div>
</div>
)
}
handleLogoutClicked () {
this.props.updateLogoutModalStatus('ON')
}
handleNewGistClicked () {
this.props.updateGistNewModeStatus('ON')
}
closeGistEditorModal () {
this.props.updateGistNewModeStatus('OFF')
}
handleSyncClicked () {
this.props.reSyncUserGists()
}
handleLogoutModalCancelClicked () {
this.props.updateLogoutModalStatus('OFF')
}
handleLogoutModalConfirmClicked () {
logger.info('[Dispatch] logoutUserSession')
this.props.updateLogoutModalStatus('OFF')
this.props.logoutUserSession()
this.props.updateLocalStorage({
token: null,
profile: null
})
removeAccessToken()
remote.getCurrentWindow().setTitle('Lepton') // update the app title
ipcRenderer.send('session-destroyed')
}
renderProfile () {
const { profile, activeStatus } = this.props.userSession
if (hideProfilePhoto || !profile || activeStatus === 'INACTIVE') {
return
}
let avatarUrl = profile.avatar_url
if (conf.get('enterprise:enable')) {
avatarUrl = defaultImage
}
return (
<div>
<figure className="sticker-img">
<Image
className='profile-image-section'
src={ avatarUrl }/>
<div>
<div className='profile-username-section'>
<h5><span>{ this.props.userSession.profile.login }</span></h5>
</div>
<div className="curl"></div>
<a href={ this.props.userSession.profile.html_url }></a>
</div>
</figure>
</div>
)
}
renderLogoutConfirmationModal () {
return (
<div className='static-modal'>
<Modal show={ this.props.logoutModalStatus === 'ON' } bsSize='small'>
<Modal.Header>
<Modal.Title>Confirm logout?</Modal.Title>
</Modal.Header>
<Modal.Footer>
<Button onClick={ this.handleLogoutModalCancelClicked.bind(this) }>cancel</Button>
<Button
bsStyle='danger'
onClick={ this.handleLogoutModalConfirmClicked.bind(this) }>logout</Button>
</Modal.Footer>
</Modal>
</div>
)
}
render () {
return (
<div className='user-panel'>
<div>
{ this.renderProfile() }
{ this.props.gistSyncStatus === 'IN_PROGRESS'
? <ProgressBar className='resync-progress-bar' active now={ 100 }/>
: null }
</div>
{ this.renderInSection() }
{ this.renderLogoutConfirmationModal() }
</div>
)
}
}
function mapStateToProps (state) {
return {
userSession: state.userSession,
syncTime: state.syncTime,
accessToken: state.accessToken,
gistTags: state.gistTags,
gistSyncStatus: state.gistSyncStatus,
logoutModalStatus: state.logoutModalStatus,
gistNewModalStatus: state.gistNewModalStatus
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
logoutUserSession: logoutUserSession,
updateSingleGist: updateSingleGist,
updateGistTags: updateGistTags,
selectGistTag: selectGistTag,
selectGist: selectGist,
updateGistNewModeStatus: updateGistNewModeStatus,
updateLogoutModalStatus: updateLogoutModalStatus
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(UserPanel)
================================================
FILE: app/containers/userPanel/index.scss
================================================
.user-panel {
.font-style-base {
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Droid Sans, sans-serif;
font-size: 13px;
}
.profile-image-section {
max-width: 100%;
}
.profile-image-section:hover {
cursor: pointer;
}
.theme-toggle {
color: var(--text-invert);
background-color: var(--accent-regular);
border: 0;
outline: 0;
width: 100%;
padding: 5px 15px;
}
/* wrap words if username cannot fit into one line */
.profile-username-section {
word-wrap: break-word;
line-height: 1em;
max-width: 2em;
}
.resync-progress-bar {
width: 100%;
height: 3px;
margin-bottom: 0;
}
.user-panel-icon {
display: inline-block;
vertical-align: middle;
position: relative;
bottom: 1px;
height: 30px;
width: 30px;
margin-left: -3px;
fill: currentColor;
}
.user-panel-button {
@extend .font-style-base;
font-size: 15px;
margin-top: 8px;
color: inherit;
opacity: .95;
transition: opacity .15s;
&:hover {
opacity: .7;
}
}
.customized-tag-small {
@extend .font-style-base;
margin: 3px;
font-size: 11px;
color: var(--text-secondary);
}
}
.new-modal {
width: 98vw;
}
/* Global setting affecting all dialogs (NEW/EDIT) if not overwritten. */
.modal {
-webkit-app-region: drag;
background: rgba(0, 0, 0, 0.1);
}
/* Stikcer peeling effect for profile image */
/* http://littlesnippets.net/6-css-image-hover-effects-using-page-curls/ */
figure.sticker-img {
font-family: 'Raleway', Arial, sans-serif;
color: #fff;
position: relative;
float: left;
overflow: hidden;
min-width: 125px;
max-width: 125px;
max-height: 125px;
width: 100%;
text-align: left;
border-radius: 5px;
margin: 3px 0 5px;
}
figure.sticker-img * {
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
figure.sticker-img img {
opacity: 1;
width: 100%;
-webkit-transition: all 0.35s;
transition: all 0.35s;
}
figure.sticker-img > div {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
figure.sticker-img > div::before {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: '';
opacity: 0;
-webkit-transition: opacity 0.4s;
transition: opacity 0.4s;
-webkit-transition-delay: 0.3s;
transition-delay: 0.3s;
background-image: linear-gradient(45deg, #000000 0%, rgba(0, 0, 0, 0.2) 40%, rgba(0, 0, 0, 0.8));
}
figure.sticker-img h5 {
font-size: 0.75em;
width: 100%;
word-spacing: -0.1em;
font-weight: 100;
text-transform: uppercase;
top: 0;
opacity: 0;
padding: 5px;
margin: 0;
position: absolute;
color: transparent;
text-shadow: 0 0 10px #ffffff;
text-align: right;
-webkit-transition: all 0.4s;
transition: all 0.4s;
-webkit-transition-delay: 0s;
transition-delay: 0s;
}
figure.sticker-img h5 span {
font-weight: 800;
}
figure.sticker-img i {
display: inline-block;
font-size: 20px;
color: #ffffff;
padding: 3px 8px;
position: absolute;
bottom: 0px;
left: 0px;
opacity: 0;
z-index: 1;
-webkit-transition: 0.05s linear;
transition: 0.05s linear;
-webkit-transition-delay: 0.1s;
transition-delay: 0.1s;
}
figure.sticker-img .curl {
width: 0px;
height: 0px;
position: absolute;
bottom: 0;
left: 0;
background: linear-gradient(225deg, #ffffff, #f3f3f3 20%, #bbbbbb 38%, #aaaaaa 44%, #888888 50%, rgba(0, 0, 0, 0.7) 50%, rgba(0, 0, 0, 0.4) 60%, rgba(0, 0, 0, 0.3));
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
transition: all .4s ease;
}
figure.sticker-img .curl:before,
figure.sticker-img .curl:after {
content: '';
position: absolute;
z-index: -1;
left: 12%;
bottom: 6%;
width: 70%;
max-width: 300px;
max-height: 100px;
height: 55%;
box-shadow: 0 12px 15px rgba(0, 0, 0, 0.3);
transform: skew(-10deg) rotate(-6deg);
}
figure.sticker-img .curl:after {
left: auto;
right: 6%;
bottom: auto;
top: 14%;
transform: skew(-15deg) rotate(-84deg);
}
figure.sticker-img a {
left: 0;
right: 0;
top: 0;
bottom: 0;
position: absolute;
color: #ffffff;
}
figure.sticker-img:hover > div h5,
figure.sticker-img.hover > div h5 {
opacity: 1;
text-shadow: 0 0 0px #ffffff;
-webkit-transition-delay: 0.3s;
transition-delay: 0.3s;
}
figure.sticker-img:hover > div::before,
figure.sticker-img.hover > div::before {
opacity: 1;
-webkit-transition-delay: 0s;
transition-delay: 0s;
}
figure.sticker-img:hover i,
figure.sticker-img.hover i {
opacity: 0.7;
-webkit-transition-delay: 0.15s;
transition-delay: 0.15s;
}
figure.sticker-img:hover .curl,
figure.sticker-img.hover .curl {
width: 50px;
height: 50px;
-webkit-transition-delay: 0s;
transition-delay: 0s;
}
================================================
FILE: app/index.js
================================================
import { ipcRenderer } from 'electron'
import React from 'react'
import ReactDom from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import electronLocalStorage from 'electron-json-storage-sync'
import './utilities/vendor/bootstrap/css/bootstrap.css'
import AppContainer from './containers/appContainer'
import HumanReadableTime from 'human-readable-time'
import SearchIndex from './utilities/search'
import Store from './utilities/store'
import {
addLangPrefix as Prefixed,
parseCustomTags,
descriptionParser
} from './utilities/parser'
import {
getGitHubApi,
GET_ALL_GISTS,
GET_USER_PROFILE,
EXCHANGE_ACCESS_TOKEN
} from './utilities/githubApi'
import RootReducer from './reducers'
import {
updateGists,
updateSyncTime,
updateGistTags,
selectGistTag,
updateAccessToken,
updateUserSession,
fetchSingleGist,
selectGist,
updateAuthWindowStatus,
updateGistSyncStatus,
updateSearchWindowStatus,
updateUpdateAvailableBarStatus,
updateNewVersionInfo,
updateImmersiveModeStatus,
updateAboutModalStatus,
updateDashboardModalStatus,
updatePinnedTags
} from './actions/index'
import { notifySuccess, notifyFailure } from './utilities/notifier'
const remote = require('@electron/remote')
const logger = remote.getGlobal('logger')
let Account = null
try {
Account = require('../configs/account')
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') throw e
Account = require('../configs/accountDummy')
}
// First instantiate the class
const localPref = new Store({
// We'll call our data file 'user-preferences'
configName: 'user-preferences',
defaults: {}
})
const CONFIG_OPTIONS = {
client_id: Account.client_id,
client_secret: Account.client_secret,
scopes: ['gist']
}
let preSyncSnapshot = {
activeGistTag: null,
activeGist: null
}
function launchAuthWindow (token) {
logger.debug(`-----> Inside launchAuthWindow with token ${token}`)
if (token) {
logger.debug('-----> calling initUserSession with cached token ' + token)
initUserSession(token)
return
}
const webPreferences = {
nodeIntegration: false,
spellcheck: false
}
let authWindow = new remote.BrowserWindow({
parent: remote.getGlobal('mainWindow'),
width: 400,
height: 600,
show: false,
webPreferences
})
const githubUrl = 'https://github.com/login/oauth/authorize?'
const authUrl = githubUrl + 'client_id=' + CONFIG_OPTIONS.client_id + '&scope=' + CONFIG_OPTIONS.scopes
const options = { extraHeaders: 'pragma: no-cache\n' }
logger.debug('loading authUrl ' + authUrl)
authWindow.loadURL(authUrl, options)
authWindow.show()
updateAuthWindowStatusOn()
function handleCallback (url) {
const rawCode = /code=([^&]*)/.exec(url) || null
const code = (rawCode && rawCode.length > 1) ? rawCode[1] : null
const error = /\?error=(.+)$/.exec(url)
if (code || error) {
// Close the browser if code found or error
authWindow.webContents.session.clearStorageData([], () => {})
authWindow.destroy()
updateAuthWindowStatusOff()
}
// If there is a code, proceed to get token from github
if (code) {
logger.info('[Dispatch] updateUserSession IN_PROGRESS')
reduxStore.dispatch(updateUserSession({ activeStatus: 'IN_PROGRESS' }))
getGitHubApi(EXCHANGE_ACCESS_TOKEN)(
CONFIG_OPTIONS.client_id, CONFIG_OPTIONS.client_secret, code)
.then((payload) => {
logger.debug('-----> calling initUserSession with new token ' + payload.access_token)
return initUserSession(payload.access_token)
})
.catch((err) => {
logger.error('Failed: ' + JSON.stringify(err.error))
notifyFailure('Sync failed', 'Please check your network condition. 03')
})
} else if (error) {
logger.error('Oops! Something went wrong and we couldn\'t' +
'log you in using Github. Please try again.')
}
}
// Handle the response from GitHub - See Update from 4/12/2015
authWindow.webContents.on('will-navigate', (event, url) => {
handleCallback(url)
})
authWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
handleCallback(newUrl)
})
// Reset the authWindow on close
authWindow.on('close', () => {
updateAuthWindowStatusOff()
authWindow = null
}, false)
}
function setSyncTime (time) {
logger.info('[Dispatch] updateSyncTime')
reduxStore.dispatch(updateSyncTime(time))
}
function initAccessToken (token) {
logger.info('[Dispatch] updateAccessToken')
reduxStore.dispatch(updateAccessToken(token))
}
function updateAuthWindowStatusOn () {
logger.info('[Dispatch] updateAuthWindowStatus ON')
reduxStore.dispatch(updateAuthWindowStatus('ON'))
}
function updateAuthWindowStatusOff () {
logger.info('[Dispatch] updateAuthWindowStatus OFF')
reduxStore.dispatch(updateAuthWindowStatus('OFF'))
}
/** Start: Language tags management **/
function updateGistTagsAfterSync (gistTags) {
logger.info('[Dispatch] updateGistTags')
reduxStore.dispatch(updateGistTags(gistTags))
}
/** End: Language tags management **/
/** Start: Active language tag management **/
function getEffectiveActiveGistTagAfterSync (gistTags, newActiveTag) {
// The active language tag could be invalid if the specific language tag no
// long exists after synchronization. However, if it is still valid, we should
// keep it.
if (!gistTags || !gistTags[preSyncSnapshot.activeGistTag]) {
return newActiveTag
}
return preSyncSnapshot.activeGistTag
}
function updateActiveGistTagAfterSync (gistTags, newActiveTagCandidate) {
// The active language tag could be invalid if the specific language tag no
// long exists after synchronization. We should get the effective active tag
// by calling getEffectiveActiveGistTagAfterSync()
const effectiveGistTag = getEffectiveActiveGistTagAfterSync(gistTags, newActiveTagCandidate)
if (effectiveGistTag !== preSyncSnapshot.activeGistTag) {
logger.info('[Dispatch] selectGistTag')
reduxStore.dispatch(selectGistTag(newActiveTagCandidate))
}
}
/** End: Active language tag management **/
/** Start: Active gist management **/
function updateActiveGistBase (gists, activeGist) {
if (!gists || !activeGist) {
// user has no gists
return
}
if (!gists[activeGist].details) {
logger.info('[Dispatch] fetchSingleGist ' + activeGist)
reduxStore.dispatch(fetchSingleGist(gists[activeGist], activeGist))
}
logger.info('[Dispatch] selectGist ' + activeGist)
reduxStore.dispatch(selectGist(activeGist))
}
function updateActiveGistAfterSync (gists, gistTags, newActiveTagCandidate) {
let activeGist = preSyncSnapshot.activeGist
if (!activeGist || !gists[activeGist]) {
// If the previous active gist is not set or is deleted, we should reset it.
const effectiveGistTag = getEffectiveActiveGistTagAfterSync(gistTags, newActiveTagCandidate)
const gistListForActiveGistTag = gistTags[effectiveGistTag]
activeGist = gistListForActiveGistTag[0] // reset the active gist
}
updateActiveGistBase(gists, activeGist)
}
function updateActiveGistAfterClicked (gists, gistTags, newActiveTag) {
const gistListForActiveGistTag = gistTags[newActiveTag]
const activeGist = gistListForActiveGistTag[0] // reset the active gist
updateActiveGistBase(gists, activeGist)
}
/** End: Active gist management **/
/** Start: User gists management **/
function updateGistStoreAfterSync (gists) {
logger.info('[Dispatch] updateGists')
reduxStore.dispatch(updateGists(gists))
}
function takeSyncSnapshot () {
const state = reduxStore.getState()
preSyncSnapshot = {
activeGistTag: state.activeGistTag,
activeGist: state.activeGist
}
}
function clearSyncSnapshot () {
preSyncSnapshot = {
activeGistTag: Prefixed('All'),
activeGist: null
}
}
function reSyncUserGists () {
const { userSession, accessToken } = reduxStore.getState()
updateUserGists(userSession.profile.login, accessToken)
}
function updateUserGists (userLoginId, token) {
reduxStore.dispatch(updateGistSyncStatus('IN_PROGRESS'))
return getGitHubApi(GET_ALL_GISTS)(token, userLoginId)
.then((gistList) => {
const preGists = reduxStore.getState().gists
const gists = {}
const rawGistTags = {}
const activeTagCandidate = Prefixed('All')
rawGistTags[Prefixed('All')] = new Set()
const gistTags = {}
const fuseSearchIndex = []
gistList.forEach((gist) => {
const langs = new Set()
let filenameRecords = ''
Object.keys(gist.files).forEach(filename => {
// leave a space in between to help tokenization
filenameRecords += ', ' + filename
const file = gist.files[filename]
const language = file.language || 'Other'
langs.add(language)
rawGistTags[Prefixed('All')].add(gist.id)
// update the language tags
const prefixedLang = Prefixed(language)
if (Object.prototype.hasOwnProperty.call(rawGistTags, prefixedLang)) {
rawGistTags[prefixedLang].add(gist.id)
} else {
rawGistTags[prefixedLang] = new Set()
rawGistTags[prefixedLang].add(gist.id)
}
// update the custom tags
const customTags = parseCustomTags(descriptionParser(gist.description).customTags)
customTags.forEach(tag => {
if (Object.prototype.hasOwnProperty.call(rawGistTags, tag)) {
rawGistTags[tag].add(gist.id)
} else {
rawGistTags[tag] = new Set()
rawGistTags[tag].add(gist.id)
}
})
})
gists[gist.id] = {
langs: langs,
brief: gist,
details: null
}
// Keep the date for the unchanged gist, so that user doesn't need
// to resync.
const preGist = preGists[gist.id]
if (preGist && preGist.details && preGist.details.updated_at === gist.updated_at) {
gists[gist.id] = Object.assign(gists[gist.id], { details: preGist.details })
}
let langSearchRecords = ''
langs.forEach(lang => {
langSearchRecords += ',' + lang
})
// Update the SearchIndex
fuseSearchIndex.push({
id: gist.id,
description: gist.description,
language: langSearchRecords,
filename: filenameRecords
})
}) // gistList.forEach
SearchIndex.resetFuseIndex(fuseSearchIndex)
for (const language in rawGistTags) {
// Save the gist ids in an Array rather than a Set, which facilitate
// many operations later, like displaying the gist id from an Array
gistTags[language] = [...rawGistTags[language]]
}
// take the state snapshot at this moment
takeSyncSnapshot()
// refresh the redux state
const humanReadableSyncTime = HumanReadableTime(new Date())
setSyncTime(humanReadableSyncTime)
updateGistStoreAfterSync(gists)
updateGistTagsAfterSync(gistTags)
updateActiveGistTagAfterSync(gistTags, activeTagCandidate)
updateActiveGistAfterSync(gists, gistTags, activeTagCandidate)
// clean up the snapshot for the previous state
clearSyncSnapshot()
notifySuccess('Sync succeeds', humanReadableSyncTime)
reduxStore.dispatch(updateGistSyncStatus('DONE'))
})
.catch(err => {
notifyFailure('Sync failed', 'Please check your network condition. 04')
logger.error('The request has failed: ' + err)
reduxStore.dispatch(updateGistSyncStatus('DONE'))
throw err
})
}
/** End: User gists management **/
/** Start: User session management **/
function initUserSession (token) {
logger.debug(`-----> Inside initUserSession with access token ${token}`)
reduxStore.dispatch(updateUserSession({ activeStatus: 'IN_PROGRESS' }))
initAccessToken(token)
let newProfile = null
getGitHubApi(GET_USER_PROFILE)(token)
.then((profile) => {
logger.debug('-----> from GET_USER_PROFILE with profile ' + JSON.stringify(profile))
newProfile = profile
return updateUserGists(profile.login, token)
})
.then(() => {
logger.debug('-----> before updateLocalStorage')
updateLocalStorage({
token: token,
profile: newProfile.login
})
logger.debug('-----> after updateLocalStorage')
logger.debug('-----> before syncLocalPref')
syncLocalPref(newProfile.login)
logger.debug('-----> after syncLocalPref')
remote.getCurrentWindow().setTitle(`${newProfile.login} | Lepton`) // update the app title
logger.info('[Dispatch] updateUserSession ACTIVE')
reduxStore.dispatch(updateUserSession({ activeStatus: 'ACTIVE', profile: newProfile }))
ipcRenderer.send('session-ready')
})
.catch((err) => {
logger.debug('-----> Failure with ' + JSON.stringify(err))
logger.error('The request has failed: \n' + JSON.stringify(err))
if (err.statusCode === 401) {
logger.info('[Dispatch] updateUserSession EXPIRED')
reduxStore.dispatch(updateUserSession({ activeStatus: 'EXPIRED' }))
} else {
logger.info('[Dispatch] updateUserSession INACTIVE')
reduxStore.dispatch(updateUserSession({ activeStatus: 'INACTIVE' }))
}
notifyFailure('Sync failed', 'Please check your network condition. 00')
})
}
/** End: User session management **/
/** Start: Local storage management **/
function updateLocalStorage (data) {
try {
logger.debug(`-----> Caching token ${data.token}`)
let rst = electronLocalStorage.set('token', data.token)
logger.debug(`-----> [${rst.status}] Cached token ${data.token}`)
logger.debug(`-----> Caching profile ${data.profile}`)
rst = electronLocalStorage.set('profile', data.profile)
logger.debug(`-----> [${rst.status}] Cached profile ${data.profile}`)
logger.debug('-----> User info is cached.')
} catch (e) {
logger.error(`-----> Failed to cache user info. ${JSON.stringify(e)}`)
}
}
function getCachedUserInfo () {
logger.debug('-----> Inside getCachedUserInfo')
const cachedProfile = electronLocalStorage.get('profile')
logger.debug(`-----> [${cachedProfile.status}] cachedProfile is ${cachedProfile.data}`)
const cachedToken = electronLocalStorage.get('token')
logger.debug(`-----> [${cachedToken.status}] cachedToken is ${cachedToken.data}`)
if (cachedProfile.status && cachedToken.status) {
return {
token: cachedToken.data,
profile: cachedProfile.data
}
}
return null
}
function syncLocalPref (userName) {
logger.debug(`-----> Inside syncLocalPref with userName ${userName}`)
let pinnedTags = []
if (localPref && localPref.get(userName) && localPref.get(userName).pinnedTags) {
pinnedTags = localPref.get(userName).pinnedTags
}
logger.debug(`-----> pinnedTags are ${JSON.stringify(pinnedTags)}`)
logger.info('[Dispatch] updatePinnedTags')
reduxStore.dispatch(updatePinnedTags(pinnedTags))
}
/** End: Local storage management **/
/** Start: Response to main process events **/
function allDialogsClosed (dialogsStatus) {
return dialogsStatus.every(status => status === 'OFF')
}
ipcRenderer.on('search-gist', data => {
const state = reduxStore.getState()
const {
immersiveMode,
gistRawModal,
searchWindowStatus,
gistEditModalStatus,
gistNewModalStatus,
aboutModalStatus,
gistDeleteModalStatus,
logoutModalStatus
} = state
// FIXME: This should be able to extracted to the allDialogsClosed method.
const dialogs = [
immersiveMode,
gistRawModal.status,
gistEditModalStatus,
gistNewModalStatus,
aboutModalStatus,
gistDeleteModalStatus,
logoutModalStatus]
if (allDialogsClosed(dialogs)) {
const preStatus = searchWindowStatus
const newStatus = preStatus === 'ON' ? 'OFF' : 'ON'
reduxStore.dispatch(updateSearchWindowStatus(newStatus))
}
})
ipcRenderer.on('dashboard', data => {
const state = reduxStore.getState()
const {
immersiveMode,
gistRawModal,
searchWindowStatus,
aboutModalStatus,
dashboardModalStatus,
gistEditModalStatus,
gistNewModalStatus,
gistDeleteModalStatus,
logoutModalStatus
} = state
// FIXME: This should be able to extracted to the allDialogsClosed method.
const dialogs = [
aboutModalStatus,
immersiveMode,
gistRawModal.status,
gistEditModalStatus,
searchWindowStatus,
gistNewModalStatus,
gistDeleteModalStatus,
logoutModalStatus]
if (allDialogsClosed(dialogs)) {
const preStatus = dashboardModalStatus
const newStatus = preStatus === 'ON' ? 'OFF' : 'ON'
reduxStore.dispatch(updateDashboardModalStatus(newStatus))
}
})
ipcRenderer.on('about-page', data => {
const state = reduxStore.getState()
const {
immersiveMode,
gistRawModal,
searchWindowStatus,
aboutModalStatus,
gistEditModalStatus,
gistNewModalStatus,
gistDeleteModalStatus,
logoutModalStatus
} = state
// FIXME: This should be able to extracted to the allDialogsClosed method.
const dialogs = [
immersiveMode,
gistRawModal.status,
gistEditModalStatus,
searchWindowStatus,
gistNewModalStatus,
gistDeleteModalStatus,
logoutModalStatus]
if (allDialogsClosed(dialogs)) {
const preStatus = aboutModalStatus
const newStatus = preStatus === 'ON' ? 'OFF' : 'ON'
reduxStore.dispatch(updateAboutModalStatus(newStatus))
}
})
ipcRenderer.on('new-gist', data => {
const state = reduxStore.getState()
const {
immersiveMode,
gistRawModal,
searchWindowStatus,
aboutModalStatus,
gistNewModalStatus,
gistEditModalStatus,
gistDeleteModalStatus,
logoutModalStatus
} = state
// FIXME: This should be able to extracted to the allDialogsClosed method.
const dialogs = [
immersiveMode,
gistRawModal.status,
searchWindowStatus,
aboutModalStatus,
gistNewModalStatus,
gistEditModalStatus,
gistDeleteModalStatus,
logoutModalStatus]
if (allDialogsClosed(dialogs)) {
ipcRenderer.emit('new-gist-renderer')
}
})
ipcRenderer.on('edit-gist', data => {
const state = reduxStore.getState()
const {
gistRawModal,
searchWindowStatus,
aboutModalStatus,
gistNewModalStatus,
gistEditModalStatus,
gistDeleteModalStatus,
logoutModalStatus
} = state
// FIXME: This should be able to extracted to the allDialogsClosed method.
const dialogs = [
gistRawModal.status,
gistNewModalStatus,
gistEditModalStatus,
searchWindowStatus,
aboutModalStatus,
gistDeleteModalStatus,
logoutModalStatus]
if (allDialogsClosed(dialogs)) {
ipcRenderer.emit('edit-gist-renderer')
}
})
ipcRenderer.on('delete-gist-check', data => {
const state = reduxStore.getState()
const {
gistRawModal,
searchWindowStatus,
aboutModalStatus,
gistNewModalStatus,
gistEditModalStatus,
gistDeleteModalStatus,
dashboardModalStatus,
logoutModalStatus
} = state
// FIXME: This should be able to extracted to the allDialogsClosed method.
const dialogs = [
gistRawModal.status,
gistNewModalStatus,
gistEditModalStatus,
searchWindowStatus,
aboutModalStatus,
gistDeleteModalStatus,
logoutModalStatus,
dashboardModalStatus]
if (allDialogsClosed(dialogs)) {
ipcRenderer.emit('delete-gist')
}
})
ipcRenderer.on('immersive-mode', data => {
const state = reduxStore.getState()
const {
searchWindowStatus,
aboutModalStatus,
immersiveMode,
gistRawModal,
gistEditModalStatus,
gistNewModalStatus,
gistDeleteModalStatus,
logoutModalStatus
} = state
const dialogs = [
searchWindowStatus,
aboutModalStatus,
gistRawModal.status,
gistEditModalStatus,
gistNewModalStatus,
gistDeleteModalStatus,
logoutModalStatus]
if (allDialogsClosed(dialogs)) {
const preStatus = immersiveMode
const newStatus = preStatus === 'ON' ? 'OFF' : 'ON'
reduxStore.dispatch(updateImmersiveModeStatus(newStatus))
}
})
ipcRenderer.on('back-to-normal-mode', data => {
const state = reduxStore.getState()
const { gistRawModal, gistEditModalStatus } = state
const dialogs = [gistRawModal.status, gistEditModalStatus]
if (allDialogsClosed(dialogs)) {
reduxStore.dispatch(updateImmersiveModeStatus('OFF'))
}
reduxStore.dispatch(updateAboutModalStatus('OFF'))
})
ipcRenderer.on('update-available', payload => {
const newVersionInfo = remote.getGlobal('newVersionInfo')
if (electronLocalStorage.get('skipped-version').data === newVersionInfo.version) return
reduxStore.dispatch(updateNewVersionInfo(newVersionInfo))
reduxStore.dispatch(updateUpdateAvailableBarStatus('ON'))
})
/** End: Response to main process events **/
// Start
const reduxStore = createStore(
RootReducer,
applyMiddleware(thunk)
)
ReactDom.render(
<Provider store = { reduxStore }>
<AppContainer
searchIndex = { SearchIndex }
localPref = { localPref }
updateLocalStorage = { updateLocalStorage }
loggedInUserInfo = { getCachedUserInfo() }
launchAuthWindow = { launchAuthWindow }
reSyncUserGists = { reSyncUserGists }
updateAboutModalStatus = { updateAboutModalStatus }
updateDashboardModalStatus = { updateDashboardModalStatus }
updateActiveGistAfterClicked = { updateActiveGistAfterClicked } />
</Provider>,
document.getElementById('container')
)
================================================
FILE: app/reducers/index.js
================================================
import { combineReducers } from 'redux'
import { reducer as form } from 'redux-form'
import aboutModalStatus from './reducer_about_modal'
import accessToken from './reducer_token'
import activeGist from './reducer_active_gist'
import activeGistTag from './reducer_active_gist_tag'
import authWindowStatus from './reducer_auth_window_status'
import dashboardModalStatus from './reducer_dashboard_modal'
import fileExpandStatus from './reducer_file_expand_status'
import gistDeleteModalStatus from './reducer_gist_delete_modal'
import gistEditModalStatus from './reducer_gist_edit_modal'
import gistNewModalStatus from './reducer_gist_new_modal'
import gistRawModal from './reducer_gist_raw_modal'
import gists from './reducer_gists'
import gistSyncStatus from './reducer_gist_sync_status'
import gistTags from './reducer_gist_tags'
import immersiveMode from './reducer_immersive_mode'
import logoutModalStatus from './reducer_logout_modal'
import newVersionInfo from './reducer_new_version_info'
import pinnedTags from './reducer_pinned_tags'
import pinnedTagsModalStatus from './reducer_pinned_tags_selections_modal'
import scrollRequestStatus from './reducer_scroll_request_status'
import searchWindowStatus from './reducer_search_window_status'
import syncTime from './reducer_sync_time'
import updateAvailableBarStatus from './reducer_update_available_bar_status'
import userSession from './reducer_user_session'
const rootReducer = combineReducers({
aboutModalStatus,
accessToken,
activeGist,
activeGistTag,
authWindowStatus,
dashboardModalStatus,
fileExpandStatus,
form,
gistDeleteModalStatus,
gistEditModalStatus,
gistNewModalStatus,
gistRawModal,
gists,
gistSyncStatus,
gistTags,
immersiveMode,
logoutModalStatus,
newVersionInfo,
pinnedTags,
pinnedTagsModalStatus,
scrollRequestStatus,
searchWindowStatus,
syncTime,
updateAvailableBarStatus,
userSession,
})
export default rootReducer
================================================
FILE: app/reducers/reducer_about_modal.js
================================================
import { UPDATE_ABOUT_MODAL_STATUS } from '../actions'
export default function (state = 'OFF', action) {
switch (action.type) {
case UPDATE_ABOUT_MODAL_STATUS:
return action.payload
default:
}
return state
}
================================================
FILE: app/reducers/reducer_active_gist.js
================================================
import { SELECT_GIST, LOGOUT_USER_SESSION } from '../actions'
export default function (state = null, action) {
switch (action.type) {
case SELECT_GIST:
return action.payload
case LOGOUT_USER_SESSION:
return null
default:
}
return state
}
================================================
FILE: app/reducers/reducer_active_gist_tag.js
================================================
import { SELECT_GIST_TAG, LOGOUT_USER_SESSION } from '../actions'
import { addLangPrefix as Prefixed } from '../utilities/parser'
export default function (state = Prefixed('All'), action) {
switch (action.type) {
case SELECT_GIST_TAG:
return action.payload
case LOGOUT_USER_SESSION:
return Prefixed('All')
default:
}
return state
}
================================================
FILE: app/reducers/reducer_auth_window_status.js
====
gitextract_qzfayoof/ ├── .all-contributorsrc ├── .eslintignore ├── .eslintrc.js ├── .github/ │ └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CLAUDE.md ├── LICENSE ├── README.md ├── app/ │ ├── actions/ │ │ └── index.js │ ├── containers/ │ │ ├── aboutPage/ │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── appContainer/ │ │ │ ├── index.js │ │ │ ├── index.scss │ │ │ └── scrollbar.scss │ │ ├── codeArea/ │ │ │ ├── index.js │ │ │ ├── jupyterNotebook.scss │ │ │ └── markdown.scss │ │ ├── dashboard/ │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── gistEditor/ │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── gistEditorForm/ │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── loginPage/ │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── navigationPanel/ │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── navigationPanelDetails/ │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── searchPage/ │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── snippet/ │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── snippetPanel/ │ │ │ ├── index.js │ │ │ └── index.scss │ │ └── userPanel/ │ │ ├── index.js │ │ └── index.scss │ ├── index.js │ ├── reducers/ │ │ ├── index.js │ │ ├── reducer_about_modal.js │ │ ├── reducer_active_gist.js │ │ ├── reducer_active_gist_tag.js │ │ ├── reducer_auth_window_status.js │ │ ├── reducer_dashboard_modal.js │ │ ├── reducer_file_expand_status.js │ │ ├── reducer_gist_delete_modal.js │ │ ├── reducer_gist_edit_modal.js │ │ ├── reducer_gist_new_modal.js │ │ ├── reducer_gist_raw_modal.js │ │ ├── reducer_gist_sync_status.js │ │ ├── reducer_gist_tags.js │ │ ├── reducer_gists.js │ │ ├── reducer_immersive_mode.js │ │ ├── reducer_logout_modal.js │ │ ├── reducer_new_version_info.js │ │ ├── reducer_pinned_tags.js │ │ ├── reducer_pinned_tags_selections_modal.js │ │ ├── reducer_scroll_request_status.js │ │ ├── reducer_search_window_status.js │ │ ├── reducer_sync_time.js │ │ ├── reducer_token.js │ │ ├── reducer_update_available_bar_status.js │ │ └── reducer_user_session.js │ └── utilities/ │ ├── githubApi/ │ │ └── index.js │ ├── jupyterNotebook/ │ │ └── index.js │ ├── markdown/ │ │ └── index.js │ ├── menu/ │ │ └── mainMenu.js │ ├── notifier/ │ │ └── index.js │ ├── parser/ │ │ └── index.js │ ├── search/ │ │ └── index.js │ ├── store/ │ │ └── index.js │ ├── themeManager/ │ │ ├── index.js │ │ └── themes/ │ │ ├── darkTheme.json │ │ └── lightTheme.json │ └── vendor/ │ ├── bootstrap/ │ │ └── css/ │ │ ├── bootstrap-theme.css │ │ └── bootstrap.css │ ├── highlightJS/ │ │ └── styles/ │ │ ├── agate.css │ │ ├── androidstudio.css │ │ ├── arduino-light.css │ │ ├── arta.css │ │ ├── ascetic.css │ │ ├── atelier-cave-dark.css │ │ ├── atelier-cave-light.css │ │ ├── atelier-dune-dark.css │ │ ├── atelier-dune-light.css │ │ ├── atelier-estuary-dark.css │ │ ├── atelier-estuary-light.css │ │ ├── atelier-forest-dark.css │ │ ├── atelier-forest-light.css │ │ ├── atelier-heath-dark.css │ │ ├── atelier-heath-light.css │ │ ├── atelier-lakeside-dark.css │ │ ├── atelier-lakeside-light.css │ │ ├── atelier-plateau-dark.css │ │ ├── atelier-plateau-light.css │ │ ├── atelier-savanna-dark.css │ │ ├── atelier-savanna-light.css │ │ ├── atelier-seaside-dark.css │ │ ├── atelier-seaside-light.css │ │ ├── atelier-sulphurpool-dark.css │ │ ├── atelier-sulphurpool-light.css │ │ ├── atom-one-dark.css │ │ ├── atom-one-light.css │ │ ├── brown-paper.css │ │ ├── codepen-embed.css │ │ ├── color-brewer.css │ │ ├── darcula.css │ │ ├── dark.css │ │ ├── darkula.css │ │ ├── default.css │ │ ├── docco.css │ │ ├── dracula.css │ │ ├── far.css │ │ ├── foundation.css │ │ ├── github-gist.css │ │ ├── github.css │ │ ├── googlecode.css │ │ ├── grayscale.css │ │ ├── gruvbox-dark.css │ │ ├── gruvbox-light.css │ │ ├── hopscotch.css │ │ ├── hybrid.css │ │ ├── idea.css │ │ ├── ir-black.css │ │ ├── kimbie.dark.css │ │ ├── kimbie.light.css │ │ ├── magula.css │ │ ├── mono-blue.css │ │ ├── monokai-sublime.css │ │ ├── monokai.css │ │ ├── obsidian.css │ │ ├── ocean.css │ │ ├── paraiso-dark.css │ │ ├── paraiso-light.css │ │ ├── pojoaque.css │ │ ├── purebasic.css │ │ ├── qtcreator_dark.css │ │ ├── qtcreator_light.css │ │ ├── railscasts.css │ │ ├── rainbow.css │ │ ├── school-book.css │ │ ├── solarized-dark.css │ │ ├── solarized-light.css │ │ ├── sunburst.css │ │ ├── tomorrow-night-blue.css │ │ ├── tomorrow-night-bright.css │ │ ├── tomorrow-night-eighties.css │ │ ├── tomorrow-night.css │ │ ├── tomorrow.css │ │ ├── vs.css │ │ ├── xcode.css │ │ ├── xt256.css │ │ └── zenburn.css │ └── prism/ │ └── prism.scss ├── build/ │ └── icon.icns ├── configs/ │ ├── accountDummy.js │ └── defaultConfig.js ├── docs/ │ ├── css/ │ │ └── freelancer.css │ ├── index.html │ ├── js/ │ │ ├── contact_me.js │ │ ├── freelancer.js │ │ └── jqBootstrapValidation.js │ └── vendor/ │ ├── bootstrap/ │ │ ├── css/ │ │ │ └── bootstrap.css │ │ └── js/ │ │ └── bootstrap.js │ ├── font-awesome/ │ │ ├── css/ │ │ │ └── font-awesome.css │ │ ├── fonts/ │ │ │ └── FontAwesome.otf │ │ ├── less/ │ │ │ ├── animated.less │ │ │ ├── bordered-pulled.less │ │ │ ├── core.less │ │ │ ├── fixed-width.less │ │ │ ├── font-awesome.less │ │ │ ├── icons.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── mixins.less │ │ │ ├── path.less │ │ │ ├── rotated-flipped.less │ │ │ ├── screen-reader.less │ │ │ ├── stacked.less │ │ │ └── variables.less │ │ └── scss/ │ │ ├── _animated.scss │ │ ├── _bordered-pulled.scss │ │ ├── _core.scss │ │ ├── _fixed-width.scss │ │ ├── _icons.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _path.scss │ │ ├── _rotated-flipped.scss │ │ ├── _screen-reader.scss │ │ ├── _stacked.scss │ │ ├── _variables.scss │ │ └── font-awesome.scss │ └── jquery/ │ └── jquery.js ├── index.html ├── license.json ├── main.js ├── package.json └── webpack.config.js
SYMBOL INDEX (389 symbols across 26 files)
FILE: app/actions/index.js
constant UPDATE_USER_SESSION (line 6) | const UPDATE_USER_SESSION = 'UPDATE_USER_SESSION'
constant LOGOUT_USER_SESSION (line 7) | const LOGOUT_USER_SESSION = 'LOGOUT_USER_SESSION'
constant UPDATE_ACCESS_TOKEN (line 8) | const UPDATE_ACCESS_TOKEN = 'UPDATE_ACCESS_TOKEN'
constant REMOVE_ACCESS_TOKEN (line 9) | const REMOVE_ACCESS_TOKEN = 'REMOVE_ACCESS_TOKEN'
constant UPDATE_GISTS (line 10) | const UPDATE_GISTS = 'UPDATE_GISTS'
constant UPDATE_SYNC_TIME (line 11) | const UPDATE_SYNC_TIME = 'UPDATE_SYNC_TIME'
constant UPDATE_SINGLE_GIST (line 12) | const UPDATE_SINGLE_GIST = 'UPDATE_SINGLE_GIST'
constant UPDATE_GIST_TAGS (line 13) | const UPDATE_GIST_TAGS = 'UPDATE_GIST_TAGS'
constant UPDATE_PINNED_TAGS (line 14) | const UPDATE_PINNED_TAGS = 'UPDATE_PINNED_TAGS'
constant SELECT_GIST_TAG (line 15) | const SELECT_GIST_TAG = 'SELECT_GIST_TAG'
constant SELECT_GIST (line 16) | const SELECT_GIST = 'SELECT_GIST'
constant UPDATE_AUTHWINDOW_STATUS (line 17) | const UPDATE_AUTHWINDOW_STATUS = 'UPDATE_AUTHWINDOW_STATUS'
constant UPDATE_GIST_SYNC_STATUS (line 18) | const UPDATE_GIST_SYNC_STATUS = 'UPDATE_GIST_SYNC_STATUS'
constant UPDATE_SEARCHWINDOW_STATUS (line 19) | const UPDATE_SEARCHWINDOW_STATUS = 'UPDATE_SEARCHWINDOW_STATUS'
constant UPDATE_SCROLL_REQUEST_STATUS (line 20) | const UPDATE_SCROLL_REQUEST_STATUS = 'UPDATE_SCROLL_REQUEST_STATUS'
constant UPDATE_UPDATEAVAILABLEBAR_STATUS (line 21) | const UPDATE_UPDATEAVAILABLEBAR_STATUS = 'UPDATE_UPDATEAVAILABLEBAR_STATUS'
constant UPDATE_NEW_VERSION_INFO (line 22) | const UPDATE_NEW_VERSION_INFO = 'UPDATE_NEW_VERSION_INFO'
constant UPDATE_IMMERSIVE_MODE_STATUS (line 23) | const UPDATE_IMMERSIVE_MODE_STATUS = 'UPDATE_IMMERSIVE_MODE_STATUS'
constant UPDATE_LOGOUT_MODAL_STATUS (line 24) | const UPDATE_LOGOUT_MODAL_STATUS = 'UPDATE_LOGOUT_MODAL_STATUS'
constant UPDATE_GIST_RAW_MODAL (line 25) | const UPDATE_GIST_RAW_MODAL = 'UPDATE_GIST_RAW_MODAL'
constant UPDATE_GIST_EDIT_MODAL (line 26) | const UPDATE_GIST_EDIT_MODAL = 'UPDATE_GIST_EDIT_MODAL'
constant UPDATE_GIST_NEW_MODAL (line 27) | const UPDATE_GIST_NEW_MODAL = 'UPDATE_GIST_NEW_MODAL'
constant UPDATE_GIST_DELETE_MODAL_STATUS (line 28) | const UPDATE_GIST_DELETE_MODAL_STATUS = 'UPDATE_GIST_DELETE_MODAL_STATUS'
constant UPDATE_PINNED_TAGS_MODAL_STATUS (line 29) | const UPDATE_PINNED_TAGS_MODAL_STATUS = 'UPDATE_PINNED_TAGS_MODAL_STATUS'
constant UPDATE_FILE_EXPAND_STATUS (line 30) | const UPDATE_FILE_EXPAND_STATUS = 'UPDATE_FILE_EXPAND_STATUS'
constant UPDATE_DASHBOARD_MODAL_STATUS (line 31) | const UPDATE_DASHBOARD_MODAL_STATUS = 'UPDATE_DASHBOARD_MODAL_STATUS'
constant UPDATE_ABOUT_MODAL_STATUS (line 32) | const UPDATE_ABOUT_MODAL_STATUS = 'UPDATE_ABOUT_MODAL_STATUS'
function updateDashboardModalStatus (line 34) | function updateDashboardModalStatus (status) {
function updateAboutModalStatus (line 41) | function updateAboutModalStatus (status) {
function updateFileExpandStatus (line 48) | function updateFileExpandStatus (status) {
function updatePinnedTagsModalStatus (line 55) | function updatePinnedTagsModalStatus (status) {
function updateGistDeleteModeStatus (line 62) | function updateGistDeleteModeStatus (status) {
function updateGistEditModeStatus (line 69) | function updateGistEditModeStatus (status) {
function updateGistNewModeStatus (line 76) | function updateGistNewModeStatus (status) {
function updateGistRawModal (line 83) | function updateGistRawModal (modalInfo) {
function updateImmersiveModeStatus (line 90) | function updateImmersiveModeStatus (status) {
function updateNewVersionInfo (line 97) | function updateNewVersionInfo (status) {
function updateUpdateAvailableBarStatus (line 104) | function updateUpdateAvailableBarStatus (status) {
function updateGistSyncStatus (line 111) | function updateGistSyncStatus (status) {
function updateAuthWindowStatus (line 118) | function updateAuthWindowStatus (status) {
function updateSearchWindowStatus (line 125) | function updateSearchWindowStatus (status) {
function updatescrollRequestStatus (line 132) | function updatescrollRequestStatus (status) {
function updateAccessToken (line 139) | function updateAccessToken (token) {
function removeAccessToken (line 146) | function removeAccessToken () {
function updateUserSession (line 153) | function updateUserSession (session) {
function logoutUserSession (line 160) | function logoutUserSession () {
function updateGists (line 167) | function updateGists (gists) {
function updateSyncTime (line 174) | function updateSyncTime (time) {
function updateSingleGist (line 181) | function updateSingleGist (gist) {
function updateGistTags (line 188) | function updateGistTags (tags) {
function updatePinnedTags (line 195) | function updatePinnedTags (tags) {
function selectGistTag (line 202) | function selectGistTag (tag) {
function selectGist (line 209) | function selectGist (id) {
function updateLogoutModalStatus (line 216) | function updateLogoutModalStatus (status) {
function fetchSingleGist (line 223) | function fetchSingleGist (oldGist, id) {
FILE: app/containers/aboutPage/index.js
class AboutPage (line 21) | class AboutPage extends Component {
method openFileInEditor (line 22) | openFileInEditor (filePath) {
method renderAboutSection (line 29) | renderAboutSection () {
method renderSettingModalBody (line 98) | renderSettingModalBody () {
method handleCloseButtonClicked (line 106) | handleCloseButtonClicked () {
method render (line 111) | render () {
function mapStateToProps (line 129) | function mapStateToProps (state) {
function mapDispatchToProps (line 135) | function mapDispatchToProps (dispatch) {
FILE: app/containers/appContainer/index.js
class AppContainer (line 25) | class AppContainer extends Component {
method renderAboutPage (line 26) | renderAboutPage () {
method renderDashboard (line 33) | renderDashboard () {
method renderSearchPage (line 40) | renderSearchPage () {
method dismissUpdateAlert (line 51) | dismissUpdateAlert () {
method handleDownloadClicked (line 56) | handleDownloadClicked () {
method handleReleaseNotesClicked (line 62) | handleReleaseNotesClicked () {
method handleSkipClicked (line 67) | handleSkipClicked () {
method renderUpdateAlert (line 73) | renderUpdateAlert () {
method renderActiveNormalSection (line 93) | renderActiveNormalSection () {
method renderActiveImmersiveSection (line 124) | renderActiveImmersiveSection () {
method renderActiveSection (line 133) | renderActiveSection () {
method renderInactiveSection (line 144) | renderInactiveSection () {
method render (line 153) | render () {
function mapStateToProps (line 165) | function mapStateToProps (state) {
function mapDispatchToProps (line 177) | function mapDispatchToProps (dispatch) {
FILE: app/containers/codeArea/index.js
class CodeArea (line 27) | class CodeArea extends Component {
method createJupyterNotebookCodeBlock (line 28) | createJupyterNotebookCodeBlock (content, language, kTabLength) {
method createMarkdownCodeBlock (line 39) | createMarkdownCodeBlock (content) {
method createHighlightedCodeBlock (line 43) | createHighlightedCodeBlock (content, language, kTabLength) {
method adaptedLanguage (line 73) | adaptedLanguage (filename, lang) {
method renderCodeArea (line 111) | renderCodeArea (filename, content, lang, kTabLength) {
method render (line 130) | render () {
FILE: app/containers/dashboard/index.js
class Dashboard (line 12) | class Dashboard extends Component {
method renderDashboardSection (line 13) | renderDashboardSection () {
method buildRadarChart (line 38) | buildRadarChart (data, labels) {
method renderCompliments (line 69) | renderCompliments (lang) {
method renderSettingModalBody (line 79) | renderSettingModalBody () {
method handleCloseButtonClicked (line 87) | handleCloseButtonClicked () {
method render (line 92) | render () {
function mapStateToProps (line 110) | function mapStateToProps (state) {
function mapDispatchToProps (line 117) | function mapDispatchToProps (dispatch) {
FILE: app/containers/gistEditor/index.js
class GistEditor (line 166) | class GistEditor extends Component {
method componentDidMount (line 167) | componentDidMount () {
method componentDidUpdate (line 176) | componentDidUpdate (prevProps, prevState) {
method setMode (line 184) | setMode (filename) {
method render (line 189) | render () {
FILE: app/containers/gistEditorForm/index.js
constant NEW_GIST (line 13) | const NEW_GIST = 'NEW_GIST'
constant UPDATE_GIST (line 14) | const UPDATE_GIST = 'UPDATE_GIST'
class GistEditorFormImpl (line 25) | class GistEditorFormImpl extends Component {
method componentWillMount (line 26) | componentWillMount () {
method componentDidMount (line 34) | componentDidMount () {
method componentWillUnmount (line 40) | componentWillUnmount () {
method shortcutSubmit (line 44) | shortcutSubmit () {
method render (line 50) | render () {
function renderGistFileHeader (line 142) | function renderGistFileHeader (member, fields, index) {
FILE: app/containers/loginPage/index.js
class LoginPage (line 18) | class LoginPage extends Component {
method constructor (line 19) | constructor (props) {
method componentWillMount (line 27) | componentWillMount () {
method componentWillUnmount (line 41) | componentWillUnmount () {
method handleLoginClicked (line 46) | handleLoginClicked () {
method handleContinueButtonClicked (line 52) | handleContinueButtonClicked (token) {
method handleTokenLoginButtonClicked (line 56) | handleTokenLoginButtonClicked (token) {
method handleLoginModeSwitched (line 62) | handleLoginModeSwitched () {
method renderControlSection (line 74) | renderControlSection () {
method updateInputValue (line 129) | updateInputValue (evt) {
method renderCredentialLoginSection (line 135) | renderCredentialLoginSection (authWindowStatus, userSessionStatus) {
method renderTokenLoginSection (line 155) | renderTokenLoginSection (showLoginSwitch, userSessionStatus) {
method renderLoginModalBody (line 183) | renderLoginModalBody () {
method renderAvatar (line 192) | renderAvatar () {
method render (line 221) | render () {
function mapStateToProps (line 237) | function mapStateToProps (state) {
FILE: app/containers/navigationPanel/index.js
class NavigationPanel (line 23) | class NavigationPanel extends Component {
method constructor (line 24) | constructor (props) {
method handleClicked (line 44) | handleClicked (key) {
method renderPinnedTags (line 50) | renderPinnedTags () {
method renderLangTags (line 70) | renderLangTags () {
method renderCustomTags (line 93) | renderCustomTags () {
method handleConfigurePinnedTagClicked (line 116) | handleConfigurePinnedTagClicked () {
method handleSectionClick (line 125) | handleSectionClick (index) {
method renderTagSection (line 141) | renderTagSection () {
method handleTagInPinnedTagsModalClicked (line 212) | handleTagInPinnedTagsModalClicked (tag) {
method closePinnedTagsModal (line 222) | closePinnedTagsModal () {
method renderAllTagsForPin (line 229) | renderAllTagsForPin () {
method handlePinnedTagSaved (line 272) | handlePinnedTagSaved () {
method renderPinnedTagsModal (line 286) | renderPinnedTagsModal () {
method render (line 308) | render () {
function mapStateToProps (line 335) | function mapStateToProps (state) {
function mapDispatchToProps (line 346) | function mapDispatchToProps (dispatch) {
FILE: app/containers/navigationPanelDetails/index.js
class NavigationPanelDetails (line 13) | class NavigationPanelDetails extends Component {
method componentDidUpdate (line 14) | componentDidUpdate () {
method handleClicked (line 25) | handleClicked (gistId) {
method decideSnippetListItemClass (line 37) | decideSnippetListItemClass (gistId) {
method renderSnippetThumbnails (line 48) | renderSnippetThumbnails () {
method render (line 107) | render () {
function mapStateToProps (line 122) | function mapStateToProps (state) {
function mapDispatchToProps (line 132) | function mapDispatchToProps (dispatch) {
FILE: app/containers/searchPage/index.js
class SearchPage (line 22) | class SearchPage extends Component {
method constructor (line 23) | constructor (props) {
method componentWillMount (line 32) | componentWillMount () {
method selectPreGist (line 37) | selectPreGist () {
method selectNextGist (line 49) | selectNextGist () {
method selectCurrentGist (line 61) | selectCurrentGist () {
method handleKeyDown (line 68) | handleKeyDown (e) {
method handleSnippetClicked (line 85) | handleSnippetClicked (gistId) {
method updateInputValue (line 110) | updateInputValue (evt) {
method queryInputValue (line 117) | queryInputValue (evt) {
method renderSnippetDescription (line 127) | renderSnippetDescription (rawDescription) {
method renderSearchResults (line 143) | renderSearchResults () {
method renderSearchModalBody (line 189) | renderSearchModalBody () {
method render (line 208) | render () {
function mapStateToProps (line 221) | function mapStateToProps (state) {
function mapDispatchToProps (line 229) | function mapDispatchToProps (dispatch) {
FILE: app/containers/snippet/index.js
class Snippet (line 47) | class Snippet extends Component {
method componentDidMount (line 48) | componentDidMount () {
method componentWillUnmount (line 60) | componentWillUnmount () {
method showDeleteModal (line 65) | showDeleteModal () {
method closeDeleteModal (line 69) | closeDeleteModal () {
method handleDeleteClicked (line 73) | handleDeleteClicked () {
method renderDeleteModal (line 98) | renderDeleteModal () {
method showGistEditorModal (line 116) | showGistEditorModal () {
method closeGistEditorModal (line 123) | closeGistEditorModal () {
method handleGistEditorFormSubmit (line 127) | handleGistEditorFormSubmit (data) {
method updateGistsStoreWithUpdatedGist (line 167) | updateGistsStoreWithUpdatedGist (gistDetails) {
method renderGistEditorModalBody (line 285) | renderGistEditorModalBody (description, fileArray, isPrivate) {
method renderGistEditorModal (line 301) | renderGistEditorModal (description, fileArray, isPrivate) {
method closeRawModal (line 321) | closeRawModal () {
method handleCopyGistLinkClicked (line 330) | handleCopyGistLinkClicked (snippet, file) {
method handleCopyGistFileClicked (line 336) | handleCopyGistFileClicked (gist) {
method showRawModalModal (line 341) | showRawModalModal (gist) {
method renderRawModal (line 350) | renderRawModal () {
method selectText (line 374) | selectText () {
method handleCopyRawLinkClicked (line 378) | handleCopyRawLinkClicked (url) {
method renderPanelHeader (line 383) | renderPanelHeader (activeSnippet) {
method renderSnippetDescription (line 429) | renderSnippetDescription (gist) {
method handleCollapseClicked (line 470) | handleCollapseClicked (filename) {
method render (line 486) | render () {
function mapStateToProps (line 573) | function mapStateToProps (state) {
function mapDispatchToProps (line 589) | function mapDispatchToProps (dispatch) {
FILE: app/containers/snippetPanel/index.js
class SnippetPanel (line 8) | class SnippetPanel extends Component {
method renderEmptySnippetSection (line 9) | renderEmptySnippetSection () {
method renderNormalSnippetSection (line 16) | renderNormalSnippetSection () {
method render (line 26) | render () {
function mapStateToProps (line 40) | function mapStateToProps (state) {
FILE: app/containers/userPanel/index.js
class UserPanel (line 51) | class UserPanel extends Component {
method componentDidMount (line 52) | componentDidMount () {
method componentWillUnmount (line 64) | componentWillUnmount () {
method handleCreateSingleGist (line 68) | handleCreateSingleGist (data) {
method updateGistsStoreWithNewGist (line 92) | updateGistsStoreWithNewGist (gistDetails) {
method renderGistEditorModalBody (line 169) | renderGistEditorModalBody () {
method renderGistEditorModal (line 186) | renderGistEditorModal () {
method renderInSection (line 206) | renderInSection () {
method handleLogoutClicked (line 241) | handleLogoutClicked () {
method handleNewGistClicked (line 245) | handleNewGistClicked () {
method closeGistEditorModal (line 249) | closeGistEditorModal () {
method handleSyncClicked (line 253) | handleSyncClicked () {
method handleLogoutModalCancelClicked (line 257) | handleLogoutModalCancelClicked () {
method handleLogoutModalConfirmClicked (line 261) | handleLogoutModalConfirmClicked () {
method renderProfile (line 274) | renderProfile () {
method renderLogoutConfirmationModal (line 303) | renderLogoutConfirmationModal () {
method render (line 321) | render () {
function mapStateToProps (line 337) | function mapStateToProps (state) {
function mapDispatchToProps (line 349) | function mapDispatchToProps (dispatch) {
FILE: app/index.js
constant CONFIG_OPTIONS (line 68) | const CONFIG_OPTIONS = {
function launchAuthWindow (line 79) | function launchAuthWindow (token) {
function setSyncTime (line 158) | function setSyncTime (time) {
function initAccessToken (line 163) | function initAccessToken (token) {
function updateAuthWindowStatusOn (line 168) | function updateAuthWindowStatusOn () {
function updateAuthWindowStatusOff (line 173) | function updateAuthWindowStatusOff () {
function updateGistTagsAfterSync (line 179) | function updateGistTagsAfterSync (gistTags) {
function getEffectiveActiveGistTagAfterSync (line 186) | function getEffectiveActiveGistTagAfterSync (gistTags, newActiveTag) {
function updateActiveGistTagAfterSync (line 196) | function updateActiveGistTagAfterSync (gistTags, newActiveTagCandidate) {
function updateActiveGistBase (line 209) | function updateActiveGistBase (gists, activeGist) {
function updateActiveGistAfterSync (line 223) | function updateActiveGistAfterSync (gists, gistTags, newActiveTagCandida...
function updateActiveGistAfterClicked (line 234) | function updateActiveGistAfterClicked (gists, gistTags, newActiveTag) {
function updateGistStoreAfterSync (line 242) | function updateGistStoreAfterSync (gists) {
function takeSyncSnapshot (line 247) | function takeSyncSnapshot () {
function clearSyncSnapshot (line 255) | function clearSyncSnapshot () {
function reSyncUserGists (line 262) | function reSyncUserGists () {
function updateUserGists (line 267) | function updateUserGists (userLoginId, token) {
function initUserSession (line 375) | function initUserSession (token) {
function updateLocalStorage (line 422) | function updateLocalStorage (data) {
function getCachedUserInfo (line 438) | function getCachedUserInfo () {
function syncLocalPref (line 455) | function syncLocalPref (userName) {
function allDialogsClosed (line 469) | function allDialogsClosed (dialogsStatus) {
FILE: app/utilities/githubApi/index.js
constant TAG (line 8) | const TAG = '[REST] '
function exchangeAccessToken (line 28) | function exchangeAccessToken (clientId, clientSecret, authCode) {
function getUserProfile (line 44) | function getUserProfile (token) {
function getSingleGist (line 60) | function getSingleGist (token, gistId) {
function getAllGistsV2 (line 76) | function getAllGistsV2 (token, userId) {
function requestGists (line 108) | function requestGists (token, userId, page, gistList) {
function parseBody (line 120) | function parseBody (res, gistList) {
constant EMPTY_PAGE_ERROR_MESSAGE (line 124) | const EMPTY_PAGE_ERROR_MESSAGE = 'page empty (Not an error)'
function getAllGistsV1 (line 125) | function getAllGistsV1 (token, userId) {
function makeRangeArr (line 173) | function makeRangeArr (start, end) {
constant GISTS_PER_PAGE (line 179) | const GISTS_PER_PAGE = 100
function makeOptionForGetAllGists (line 180) | function makeOptionForGetAllGists (token, userId, page) {
function createSingleGist (line 199) | function createSingleGist (token, description, files, isPublic) {
function editSingleGist (line 219) | function editSingleGist (token, gistId, updatedDescription, updatedFiles) {
function deleteSingleGist (line 238) | function deleteSingleGist (token, gistId) {
constant EXCHANGE_ACCESS_TOKEN (line 253) | const EXCHANGE_ACCESS_TOKEN = 'EXCHANGE_ACCESS_TOKEN'
constant GET_ALL_GISTS (line 254) | const GET_ALL_GISTS = 'GET_ALL_GISTS'
constant GET_ALL_GISTS_V1 (line 255) | const GET_ALL_GISTS_V1 = 'GET_ALL_GISTS_V1'
constant GET_SINGLE_GIST (line 256) | const GET_SINGLE_GIST = 'GET_SINGLE_GIST'
constant GET_USER_PROFILE (line 257) | const GET_USER_PROFILE = 'GET_USER_PROFILE'
constant CREATE_SINGLE_GIST (line 258) | const CREATE_SINGLE_GIST = 'CREATE_SINGLE_GIST'
constant EDIT_SINGLE_GIST (line 259) | const EDIT_SINGLE_GIST = 'EDIT_SINGLE_GIST'
constant DELETE_SINGLE_GIST (line 260) | const DELETE_SINGLE_GIST = 'DELETE_SINGLE_GIST'
function getGitHubApi (line 262) | function getGitHubApi (selection) {
FILE: app/utilities/menu/mainMenu.js
method click (line 50) | click (item, focusedWindow) {
method click (line 91) | click () { require('electron').shell.openExternal('http://hackjutsu.com/...
FILE: app/utilities/notifier/index.js
function notifySuccess (line 5) | function notifySuccess (title, message = '') {
function notifyFailure (line 15) | function notifyFailure (title, message = '') {
FILE: app/utilities/parser/index.js
function descriptionParser (line 12) | function descriptionParser (payload) {
function addLangPrefix (line 33) | function addLangPrefix (payload) {
function parseLangName (line 41) | function parseLangName (payload) {
function addCustomTagsPrefix (line 48) | function addCustomTagsPrefix (tags) {
function parseCustomTags (line 55) | function parseCustomTags (payload) {
function parseCustomTagsLegacy (line 64) | function parseCustomTagsLegacy (payload) {
function parseCustomTagsTwitter (line 70) | function parseCustomTagsTwitter (payload) {
FILE: app/utilities/search/index.js
function resetFuseIndex (line 24) | function resetFuseIndex (list) {
function initFuseSearch (line 28) | function initFuseSearch () {
function addToFuseIndex (line 32) | function addToFuseIndex (item) {
function updateFuseIndex (line 36) | function updateFuseIndex (item) {
function fuseSearch (line 44) | function fuseSearch (pattern) {
FILE: app/utilities/store/index.js
class Store (line 5) | class Store {
method constructor (line 6) | constructor (opts) {
method get (line 17) | get (key) {
method set (line 22) | set (key, val) {
function parseDataFile (line 32) | function parseDataFile (filePath, defaults) {
FILE: app/utilities/themeManager/index.js
constant LIGHT_THEME (line 4) | const LIGHT_THEME = 'light'
constant DARK_THEME (line 5) | const DARK_THEME = 'dark'
class ThemeManager (line 7) | class ThemeManager {
method constructor (line 8) | constructor () {
method setTheme (line 18) | setTheme (theme) {
method toggleTheme (line 24) | toggleTheme () {
method generateScheme (line 29) | generateScheme (themeName) {
FILE: docs/js/jqBootstrapValidation.js
function regexFromString (line 875) | function regexFromString(inputstring) {
function executeFunctionByName (line 885) | function executeFunctionByName(functionName, context /*, args*/) {
FILE: docs/vendor/bootstrap/js/bootstrap.js
function transitionEnd (line 34) | function transitionEnd() {
function removeElement (line 126) | function removeElement() {
function Plugin (line 142) | function Plugin(option) {
function Plugin (line 251) | function Plugin(option) {
function Plugin (line 475) | function Plugin(option) {
function getTargetFromTrigger (line 695) | function getTargetFromTrigger($trigger) {
function Plugin (line 707) | function Plugin(option) {
function getParent (line 774) | function getParent($this) {
function clearMenus (line 787) | function clearMenus(e) {
function Plugin (line 880) | function Plugin(option) {
function Plugin (line 1208) | function Plugin(option, _relatedTarget) {
function complete (line 1574) | function complete() {
function Plugin (line 1750) | function Plugin(option) {
function Plugin (line 1859) | function Plugin(option) {
function ScrollSpy (line 1902) | function ScrollSpy(element, options) {
function Plugin (line 2022) | function Plugin(option) {
function next (line 2131) | function next() {
function Plugin (line 2177) | function Plugin(option) {
function Plugin (line 2334) | function Plugin(option) {
FILE: docs/vendor/jquery/jquery.js
function isArrayLike (line 563) | function isArrayLike( obj ) {
function Sizzle (line 772) | function Sizzle( selector, context, results, seed ) {
function createCache (line 912) | function createCache() {
function markFunction (line 930) | function markFunction( fn ) {
function assert (line 939) | function assert( fn ) {
function addHandle (line 961) | function addHandle( attrs, handler ) {
function siblingCheck (line 976) | function siblingCheck( a, b ) {
function createInputPseudo (line 1003) | function createInputPseudo( type ) {
function createButtonPseudo (line 1014) | function createButtonPseudo( type ) {
function createPositionalPseudo (line 1025) | function createPositionalPseudo( fn ) {
function testContext (line 1048) | function testContext( context ) {
function setFilters (line 2093) | function setFilters() {}
function toSelector (line 2164) | function toSelector( tokens ) {
function addCombinator (line 2174) | function addCombinator( matcher, combinator, base ) {
function elementMatcher (line 2232) | function elementMatcher( matchers ) {
function multipleContexts (line 2246) | function multipleContexts( selector, contexts, results ) {
function condense (line 2255) | function condense( unmatched, map, filter, context, xml ) {
function setMatcher (line 2276) | function setMatcher( preFilter, selector, matcher, postFilter, postFinde...
function matcherFromTokens (line 2369) | function matcherFromTokens( tokens ) {
function matcherFromGroupMatchers (line 2427) | function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
function winnow (line 2765) | function winnow( elements, qualifier, not ) {
function sibling (line 3078) | function sibling( cur, dir ) {
function createOptions (line 3159) | function createOptions( options ) {
function detach (line 3595) | function detach() {
function completed (line 3609) | function completed() {
function dataAttr (line 3779) | function dataAttr( elem, key, data ) {
function isEmptyDataObject (line 3813) | function isEmptyDataObject( obj ) {
function internalData (line 3829) | function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
function internalRemoveData (line 3921) | function internalRemoveData( elem, name, pvt ) {
function adjustCSS (line 4314) | function adjustCSS( elem, prop, valueParts, tween ) {
function createSafeFragment (line 4444) | function createSafeFragment( document ) {
function getAll (line 4548) | function getAll( context, tag ) {
function setGlobalEval (line 4577) | function setGlobalEval( elems, refElements ) {
function fixDefaultChecked (line 4593) | function fixDefaultChecked( elem ) {
function buildFragment (line 4599) | function buildFragment( elems, context, scripts, selection, ignored ) {
function returnTrue (line 4759) | function returnTrue() {
function returnFalse (line 4763) | function returnFalse() {
function safeActiveElement (line 4769) | function safeActiveElement() {
function on (line 4775) | function on( elem, types, selector, data, fn, one ) {
function manipulationTarget (line 5890) | function manipulationTarget( elem, content ) {
function disableScript (line 5900) | function disableScript( elem ) {
function restoreScript (line 5904) | function restoreScript( elem ) {
function cloneCopyEvent (line 5914) | function cloneCopyEvent( src, dest ) {
function fixCloneNodeIssues (line 5941) | function fixCloneNodeIssues( src, dest ) {
function domManip (line 6009) | function domManip( collection, args, callback, ignored ) {
function remove (line 6106) | function remove( elem, selector, keepData ) {
function actualDisplay (line 6442) | function actualDisplay( name, doc ) {
function defaultDisplay (line 6458) | function defaultDisplay( nodeName ) {
function computeStyleTests (line 6607) | function computeStyleTests() {
function addGetHookIf (line 6819) | function addGetHookIf( conditionFn, hookFn ) {
function vendorPropName (line 6862) | function vendorPropName( name ) {
function showHide (line 6881) | function showHide( elements, show ) {
function setPositiveNumber (line 6938) | function setPositiveNumber( elem, value, subtract ) {
function augmentWidthOrHeight (line 6947) | function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
function getWidthOrHeight (line 6991) | function getWidthOrHeight( elem, name, extra ) {
function Tween (line 7374) | function Tween( elem, options, prop, end, easing ) {
function createFxNow (line 7498) | function createFxNow() {
function genFx (line 7506) | function genFx( type, includeWidth ) {
function createTween (line 7526) | function createTween( value, prop, animation ) {
function defaultPrefilter (line 7540) | function defaultPrefilter( elem, props, opts ) {
function propFilter (line 7685) | function propFilter( props, specialEasing ) {
function Animation (line 7722) | function Animation( elem, properties, options ) {
function getClass (line 8803) | function getClass( elem ) {
function addToPrefiltersOrTransports (line 9115) | function addToPrefiltersOrTransports( structure ) {
function inspectPrefiltersOrTransports (line 9149) | function inspectPrefiltersOrTransports( structure, options, originalOpti...
function ajaxExtend (line 9178) | function ajaxExtend( target, src ) {
function ajaxHandleResponses (line 9198) | function ajaxHandleResponses( s, jqXHR, responses ) {
function ajaxConvert (line 9255) | function ajaxConvert( s, response, jqXHR, isSuccess ) {
function done (line 9753) | function done( status, nativeStatusText, responses, headers ) {
function getDisplay (line 9985) | function getDisplay( elem ) {
function filterHidden (line 9989) | function filterHidden( elem ) {
function buildParams (line 10027) | function buildParams( prefix, obj, traditional, add ) {
function createStandardXHR (line 10346) | function createStandardXHR() {
function createActiveXHR (line 10352) | function createActiveXHR() {
function getWindow (line 10682) | function getWindow( elem ) {
FILE: main.js
function getConfigPath (line 45) | function getConfigPath() {
function initGlobalLogger (line 367) | function initGlobalLogger () {
function setTray (line 388) | function setTray(app, mainWindow) {
Condensed preview — 201 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,545K chars).
[
{
"path": ".all-contributorsrc",
"chars": 8164,
"preview": "{\n \"files\": [\n \"README.md\"\n ],\n \"imageSize\": 100,\n \"commit\": false,\n \"contributors\": [\n {\n \"login\": \"hac"
},
{
"path": ".eslintignore",
"chars": 43,
"preview": "dist\n**/node_modules\n**/webpack*.config.js\n"
},
{
"path": ".eslintrc.js",
"chars": 645,
"preview": "module.exports = {\n \"extends\": \"standard\",\n \"env\": {\n \"browser\": true,\n \"mocha\": true,\n \"node\": tru"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 732,
"preview": "The more we know about your system and use case, the more easily and likely we can help.\n\n#### Environment info\n* OS:\n* "
},
{
"path": ".gitignore",
"chars": 108,
"preview": "node_modules\nbundle\ndist\n.DS_Store\nconfigs/account.js\nnpm-debug.log\ntest\nscreenshots\n*backup*\n.idea\n.vscode\n"
},
{
"path": ".travis.yml",
"chars": 36,
"preview": "language: node_js\nnode_js:\n - \"14\"\n"
},
{
"path": "CLAUDE.md",
"chars": 4271,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "LICENSE",
"chars": 1081,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2017-present CosmoX\n\nPermission is hereby granted, free of charge, to any person ob"
},
{
"path": "README.md",
"chars": 17695,
"preview": "<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->\n[:not(:hover)::-webkit-scrollbar {\n display: none;\n}\n \n::-webkit-scrollbar {\n width: 8px;\n height: 5px;\n"
},
{
"path": "app/containers/codeArea/index.js",
"chars": 4640,
"preview": "import HighlightJS from 'highlight.js'\nimport hljsDefineSolidity from 'highlightjs-solidity'\nimport hljsDefineGraphQL fr"
},
{
"path": "app/containers/codeArea/jupyterNotebook.scss",
"chars": 3057,
"preview": ".font-style-base {\n font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Droid Sans, sans-serif"
},
{
"path": "app/containers/codeArea/markdown.scss",
"chars": 6894,
"preview": "// GitHub style markdown css\n.markdown-section {\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe "
},
{
"path": "app/containers/dashboard/index.js",
"chars": 3506,
"preview": "import { bindActionCreators } from 'redux'\nimport { connect } from 'react-redux'\nimport { Modal, Image } from 'react-boo"
},
{
"path": "app/containers/dashboard/index.scss",
"chars": 669,
"preview": ".dashboard-modal {\n\n .font-style-base {\n font-family: Consolas, Menlo, Monaco, \"Courier New\", monospace;\n font-si"
},
{
"path": "app/containers/gistEditor/index.js",
"chars": 7381,
"preview": "import React, { Component } from 'react'\nimport CodeMirror from 'react-codemirror'\nimport 'codemirror/mode/meta'\n\n// Whe"
},
{
"path": "app/containers/gistEditor/index.scss",
"chars": 1566,
"preview": ".font-style-base {\n font-family: Consolas, Menlo, Monaco, \"Courier New\", monospace;\n font-size: 13px;\n}\n\n.CodeMirror {"
},
{
"path": "app/containers/gistEditorForm/index.js",
"chars": 6291,
"preview": "import { connect } from 'react-redux'\nimport { Field, FieldArray, reduxForm, formValueSelector } from 'redux-form'\nimpor"
},
{
"path": "app/containers/gistEditorForm/index.scss",
"chars": 2646,
"preview": ".gist-editor-form {\n\n .font-style-base {\n font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubunt"
},
{
"path": "app/containers/loginPage/index.js",
"chars": 7454,
"preview": "import { Alert, Button, Image, Modal, ProgressBar } from 'react-bootstrap'\nimport Avatar from 'boring-avatars'\nimport { "
},
{
"path": "app/containers/loginPage/index.scss",
"chars": 908,
"preview": ".login-modal {\n\n .modal-button-base {\n margin-top: 5px;\n margin-bottom: 5px;\n width: 100%;\n }\n\n .modal {\n "
},
{
"path": "app/containers/navigationPanel/index.js",
"chars": 10339,
"preview": "import { bindActionCreators } from 'redux'\nimport { connect } from 'react-redux'\nimport { Modal, Button } from 'react-bo"
},
{
"path": "app/containers/navigationPanel/index.scss",
"chars": 3260,
"preview": ".menu-panel {\n padding: 4px 7px;\n float: left;\n width: 140px;\n height: 100vh;\n display: flex;\n flex-direction: col"
},
{
"path": "app/containers/navigationPanelDetails/index.js",
"chars": 4747,
"preview": "import { bindActionCreators } from 'redux'\nimport { connect } from 'react-redux'\nimport { descriptionParser } from '../."
},
{
"path": "app/containers/navigationPanelDetails/index.scss",
"chars": 1784,
"preview": ".panel-thumbnails-background {\n height: 100vh;\n position: relative;\n}\n\n.panel-thumbnails-scroll {\n float: left;\n wid"
},
{
"path": "app/containers/searchPage/index.js",
"chars": 6495,
"preview": "import { bindActionCreators } from 'redux'\nimport { connect } from 'react-redux'\nimport { Modal } from 'react-bootstrap'"
},
{
"path": "app/containers/searchPage/index.scss",
"chars": 2389,
"preview": ".search-modal {\n\n .font-style-base {\n font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, D"
},
{
"path": "app/containers/snippet/index.js",
"chars": 19496,
"preview": "import { bindActionCreators } from 'redux'\nimport { connect } from 'react-redux'\nimport { default as GistEditorForm, UPD"
},
{
"path": "app/containers/snippet/index.scss",
"chars": 6481,
"preview": ".font-style-base {\n font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Droid Sans, sans-serif"
},
{
"path": "app/containers/snippetPanel/index.js",
"chars": 1318,
"preview": "import { connect } from 'react-redux'\nimport { Well } from 'react-bootstrap'\nimport React, { Component } from 'react'\nim"
},
{
"path": "app/containers/snippetPanel/index.scss",
"chars": 680,
"preview": ".Pane2 {\n overflow: hidden;\n}\n\n.snippet-panel {\n float: right;\n width: 100%;\n overflow: overlay;\n height: 100vh;\n}\n"
},
{
"path": "app/containers/userPanel/index.js",
"chars": 10054,
"preview": "import { bindActionCreators } from 'redux'\nimport { connect } from 'react-redux'\nimport { default as GistEditorForm, NEW"
},
{
"path": "app/containers/userPanel/index.scss",
"chars": 4755,
"preview": ".user-panel {\n\n .font-style-base {\n font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI, Ubuntu, Dro"
},
{
"path": "app/index.js",
"chars": 21591,
"preview": "import { ipcRenderer } from 'electron'\nimport React from 'react'\nimport ReactDom from 'react-dom'\nimport { Provider } fr"
},
{
"path": "app/reducers/index.js",
"chars": 1944,
"preview": "import { combineReducers } from 'redux'\nimport { reducer as form } from 'redux-form'\nimport aboutModalStatus from './red"
},
{
"path": "app/reducers/reducer_about_modal.js",
"chars": 230,
"preview": "import { UPDATE_ABOUT_MODAL_STATUS } from '../actions'\n\nexport default function (state = 'OFF', action) {\n switch (acti"
},
{
"path": "app/reducers/reducer_active_gist.js",
"chars": 270,
"preview": "import { SELECT_GIST, LOGOUT_USER_SESSION } from '../actions'\n\nexport default function (state = null, action) {\n switch"
},
{
"path": "app/reducers/reducer_active_gist_tag.js",
"chars": 364,
"preview": "import { SELECT_GIST_TAG, LOGOUT_USER_SESSION } from '../actions'\nimport { addLangPrefix as Prefixed } from '../utilitie"
},
{
"path": "app/reducers/reducer_auth_window_status.js",
"chars": 228,
"preview": "import { UPDATE_AUTHWINDOW_STATUS } from '../actions'\n\nexport default function (state = 'OFF', action) {\n switch (actio"
},
{
"path": "app/reducers/reducer_dashboard_modal.js",
"chars": 238,
"preview": "import { UPDATE_DASHBOARD_MODAL_STATUS } from '../actions'\n\nexport default function (state = 'OFF', action) {\n switch ("
},
{
"path": "app/reducers/reducer_file_expand_status.js",
"chars": 245,
"preview": "import { UPDATE_FILE_EXPAND_STATUS } from '../actions'\n\nexport default function (state = [], action) {\n switch (action."
},
{
"path": "app/reducers/reducer_gist_delete_modal.js",
"chars": 252,
"preview": "import { UPDATE_GIST_DELETE_MODAL_STATUS } from '../actions/index'\n\nexport default function (state = 'OFF', action) {\n "
},
{
"path": "app/reducers/reducer_gist_edit_modal.js",
"chars": 224,
"preview": "import { UPDATE_GIST_EDIT_MODAL } from '../actions'\n\nexport default function (state = 'OFF', action) {\n switch (action."
},
{
"path": "app/reducers/reducer_gist_new_modal.js",
"chars": 222,
"preview": "import { UPDATE_GIST_NEW_MODAL } from '../actions'\n\nexport default function (state = 'OFF', action) {\n switch (action.t"
},
{
"path": "app/reducers/reducer_gist_raw_modal.js",
"chars": 299,
"preview": "import { UPDATE_GIST_RAW_MODAL } from '../actions'\n\nexport default function (state = { status: 'OFF', file: null, conten"
},
{
"path": "app/reducers/reducer_gist_sync_status.js",
"chars": 227,
"preview": "import { UPDATE_GIST_SYNC_STATUS } from '../actions'\n\nexport default function (state = 'DONE', action) {\n switch (actio"
},
{
"path": "app/reducers/reducer_gist_tags.js",
"chars": 277,
"preview": "import { UPDATE_GIST_TAGS, LOGOUT_USER_SESSION } from '../actions'\n\nexport default function (state = {}, action) {\n swi"
},
{
"path": "app/reducers/reducer_gists.js",
"chars": 303,
"preview": "import { UPDATE_GISTS, UPDATE_SINGLE_GIST } from '../actions'\n\nexport default function (state = {}, action) {\n switch ("
},
{
"path": "app/reducers/reducer_immersive_mode.js",
"chars": 236,
"preview": "import { UPDATE_IMMERSIVE_MODE_STATUS } from '../actions'\n\nexport default function (state = 'OFF', action) {\n switch (a"
},
{
"path": "app/reducers/reducer_logout_modal.js",
"chars": 242,
"preview": "import { UPDATE_LOGOUT_MODAL_STATUS } from '../actions/index'\n\nexport default function (state = 'OFF', action) {\n switc"
},
{
"path": "app/reducers/reducer_new_version_info.js",
"chars": 244,
"preview": "import { UPDATE_NEW_VERSION_INFO } from '../actions'\n\nexport default function (state = { version: '', url: '' }, action)"
},
{
"path": "app/reducers/reducer_pinned_tags.js",
"chars": 217,
"preview": "import { UPDATE_PINNED_TAGS } from '../actions'\n\nexport default function (state = [], action) {\n switch (action.type) {"
},
{
"path": "app/reducers/reducer_pinned_tags_selections_modal.js",
"chars": 242,
"preview": "import { UPDATE_PINNED_TAGS_MODAL_STATUS } from '../actions'\n\nexport default function (state = 'OFF', action) {\n switch"
},
{
"path": "app/reducers/reducer_scroll_request_status.js",
"chars": 236,
"preview": "import { UPDATE_SCROLL_REQUEST_STATUS } from '../actions'\n\nexport default function (state = 'OFF', action) {\n switch (a"
},
{
"path": "app/reducers/reducer_search_window_status.js",
"chars": 232,
"preview": "import { UPDATE_SEARCHWINDOW_STATUS } from '../actions'\n\nexport default function (state = 'OFF', action) {\n switch (act"
},
{
"path": "app/reducers/reducer_sync_time.js",
"chars": 211,
"preview": "import { UPDATE_SYNC_TIME } from '../actions'\n\nexport default function (state = null, action) {\n switch (action.type) {"
},
{
"path": "app/reducers/reducer_token.js",
"chars": 285,
"preview": "import { UPDATE_ACCESS_TOKEN, REMOVE_ACCESS_TOKEN } from '../actions'\n\nexport default function (state = null, action) {\n"
},
{
"path": "app/reducers/reducer_update_available_bar_status.js",
"chars": 244,
"preview": "import { UPDATE_UPDATEAVAILABLEBAR_STATUS } from '../actions'\n\nexport default function (state = 'OFF', action) {\n switc"
},
{
"path": "app/reducers/reducer_user_session.js",
"chars": 352,
"preview": "import { UPDATE_USER_SESSION, LOGOUT_USER_SESSION } from '../actions'\n\nexport default function (state = { activeStatus: "
},
{
"path": "app/utilities/githubApi/index.js",
"chars": 8195,
"preview": "import { Promise } from 'bluebird'\nimport { remote } from 'electron'\nimport { notifyFailure } from '../notifier'\nimport "
},
{
"path": "app/utilities/jupyterNotebook/index.js",
"chars": 708,
"preview": "const nb = require('notebookjs')\nconst Prism = require('prismjs')\nconst highlighter = (code, lang) => {\n if (typeof lan"
},
{
"path": "app/utilities/markdown/index.js",
"chars": 562,
"preview": "import HighlightJS from 'highlight.js'\nimport MarkdownIt from 'markdown-it'\nimport MdTaskList from 'markdown-it-task-lis"
},
{
"path": "app/utilities/menu/mainMenu.js",
"chars": 2833,
"preview": "const electron = require('electron')\nconst app = electron.app\n\nconst template = [\n {\n label: 'Edit',\n submenu: [\n"
},
{
"path": "app/utilities/notifier/index.js",
"chars": 617,
"preview": "import { remote } from 'electron'\n\nconst conf = remote.getGlobal('conf')\n\nexport function notifySuccess (title, message "
},
{
"path": "app/utilities/parser/index.js",
"chars": 2560,
"preview": "const twitter = require('twitter-text')\n\n/* Old(Legacy) Style: [my_title] my_description #tags: tag1, tag2, tag3\n New"
},
{
"path": "app/utilities/search/index.js",
"chars": 1065,
"preview": "const Fuse = require('fuse.js')\n\nlet fuse = null\nlet fuseIndex = []\n\nconst fuseOptions = {\n shouldSort: true,\n tokeniz"
},
{
"path": "app/utilities/store/index.js",
"chars": 1736,
"preview": "const electron = require('electron')\nconst path = require('path')\nconst fs = require('fs')\n\nclass Store {\n constructor "
},
{
"path": "app/utilities/themeManager/index.js",
"chars": 908,
"preview": "import darkTheme from './themes/darkTheme.json'\nimport lightTheme from './themes/lightTheme.json'\n\nconst LIGHT_THEME = '"
},
{
"path": "app/utilities/themeManager/themes/darkTheme.json",
"chars": 508,
"preview": "{\n \"bg-primary\": \"rgb(50, 50, 50)\",\n \"bg-secondary\": \"rgb(46, 46, 46)\",\n \"border-color\": \"#3a3b41\",\n \"text-p"
},
{
"path": "app/utilities/themeManager/themes/lightTheme.json",
"chars": 489,
"preview": "{\n \"bg-primary\": \"#FFFFFF\",\n \"bg-secondary\": \"#f5f4f5\",\n \"border-color\": \"#E5E8EB\",\n \"text-primary\": \"#31313"
},
{
"path": "app/utilities/vendor/bootstrap/css/bootstrap-theme.css",
"chars": 26132,
"preview": "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://gi"
},
{
"path": "app/utilities/vendor/bootstrap/css/bootstrap.css",
"chars": 145693,
"preview": "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://gi"
},
{
"path": "app/utilities/vendor/highlightJS/styles/agate.css",
"chars": 1276,
"preview": "/*!\n * Agate by Taufik Nurrohman <https://github.com/tovic>\n * ----------------------------------------------------\n *\n "
},
{
"path": "app/utilities/vendor/highlightJS/styles/androidstudio.css",
"chars": 774,
"preview": "/*\nDate: 24 Fev 2015\nAuthor: Pedro Oliveira <kanytu@gmail . com>\n*/\n\n.hljs {\n color: #a9b7c6;\n background: #282b2e;\n "
},
{
"path": "app/utilities/vendor/highlightJS/styles/arduino-light.css",
"chars": 1053,
"preview": "/*\n\nArduino® Light Theme - Stefania Mellai <s.mellai@arduino.cc>\n\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n pa"
},
{
"path": "app/utilities/vendor/highlightJS/styles/arta.css",
"chars": 852,
"preview": "/*\nDate: 17.V.2011\nAuthor: pumbur <pumbur@pumbur.net>\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n padding: 0.5em"
},
{
"path": "app/utilities/vendor/highlightJS/styles/ascetic.css",
"chars": 591,
"preview": "/*\n\nOriginal style from softwaremaniacs.org (c) Ivan Sagalaev <Maniac@SoftwareManiacs.Org>\n\n*/\n\n.hljs {\n display: block"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-cave-dark.css",
"chars": 1274,
"preview": "/* Base16 Atelier Cave Dark - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-sche"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-cave-light.css",
"chars": 1299,
"preview": "/* Base16 Atelier Cave Light - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-sch"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-dune-dark.css",
"chars": 1094,
"preview": "/* Base16 Atelier Dune Dark - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-sche"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-dune-light.css",
"chars": 1095,
"preview": "/* Base16 Atelier Dune Light - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-sch"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-estuary-dark.css",
"chars": 1310,
"preview": "/* Base16 Atelier Estuary Dark - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-s"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-estuary-light.css",
"chars": 1311,
"preview": "/* Base16 Atelier Estuary Light - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-forest-dark.css",
"chars": 1110,
"preview": "/* Base16 Atelier Forest Dark - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-sc"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-forest-light.css",
"chars": 1111,
"preview": "/* Base16 Atelier Forest Light - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-s"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-heath-dark.css",
"chars": 1102,
"preview": "/* Base16 Atelier Heath Dark - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-sch"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-heath-light.css",
"chars": 1103,
"preview": "/* Base16 Atelier Heath Light - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-sc"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-lakeside-dark.css",
"chars": 1126,
"preview": "/* Base16 Atelier Lakeside Dark - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-lakeside-light.css",
"chars": 1127,
"preview": "/* Base16 Atelier Lakeside Light - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-plateau-dark.css",
"chars": 1310,
"preview": "/* Base16 Atelier Plateau Dark - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-s"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-plateau-light.css",
"chars": 1311,
"preview": "/* Base16 Atelier Plateau Light - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-savanna-dark.css",
"chars": 1310,
"preview": "/* Base16 Atelier Savanna Dark - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-s"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-savanna-light.css",
"chars": 1311,
"preview": "/* Base16 Atelier Savanna Light - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-seaside-dark.css",
"chars": 1118,
"preview": "/* Base16 Atelier Seaside Dark - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-s"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-seaside-light.css",
"chars": 1119,
"preview": "/* Base16 Atelier Seaside Light - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-sulphurpool-dark.css",
"chars": 1150,
"preview": "/* Base16 Atelier Sulphurpool Dark - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/ateli"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atelier-sulphurpool-light.css",
"chars": 1151,
"preview": "/* Base16 Atelier Sulphurpool Light - Theme */\n/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atel"
},
{
"path": "app/utilities/vendor/highlightJS/styles/atom-one-dark.css",
"chars": 1266,
"preview": "/*\n\nAtom One Dark by Daniel Gamage\nOriginal One Dark Syntax theme from https://github.com/atom/one-dark-syntax\n\nbase: "
},
{
"path": "app/utilities/vendor/highlightJS/styles/atom-one-light.css",
"chars": 1269,
"preview": "/*\n\nAtom One Light by Daniel Gamage\nOriginal One Light Syntax theme from https://github.com/atom/one-light-syntax\n\nbase:"
},
{
"path": "app/utilities/vendor/highlightJS/styles/brown-paper.css",
"chars": 842,
"preview": "/*\n\nBrown Paper style from goldblog.com.ua (c) Zaripov Yura <yur4ik7@ukr.net>\n\n*/\n\n.hljs {\n display: block;\n overflow-"
},
{
"path": "app/utilities/vendor/highlightJS/styles/codepen-embed.css",
"chars": 842,
"preview": "/*\n codepen.io Embed Theme\n Author: Justin Perry <http://github.com/ourmaninamsterdam>\n Original theme - https://gith"
},
{
"path": "app/utilities/vendor/highlightJS/styles/color-brewer.css",
"chars": 883,
"preview": "/*\n\nColorbrewer theme\nOriginal: https://github.com/mbostock/colorbrewer-theme (c) Mike Bostock <mike@ocks.org>\nPorted by"
},
{
"path": "app/utilities/vendor/highlightJS/styles/darcula.css",
"chars": 912,
"preview": "/*\n\nDarcula color scheme from the JetBrains family of IDEs\n\n*/\n\n\n.hljs {\n display: block;\n overflow-x: auto;\n padding"
},
{
"path": "app/utilities/vendor/highlightJS/styles/dark.css",
"chars": 794,
"preview": "/*\n\nDark style from softwaremaniacs.org (c) Ivan Sagalaev <Maniac@SoftwareManiacs.Org>\n\n*/\n\n.hljs {\n display: block;\n "
},
{
"path": "app/utilities/vendor/highlightJS/styles/darkula.css",
"chars": 154,
"preview": "/*\n Deprecated due to a typo in the name and left here for compatibility purpose only.\n Please use darcula.css instead"
},
{
"path": "app/utilities/vendor/highlightJS/styles/default.css",
"chars": 1159,
"preview": "/*\n\nOriginal highlight.js style (c) Ivan Sagalaev <maniac@softwaremaniacs.org>\n\n*/\n\n.hljs {\n display: block;\n overflow"
},
{
"path": "app/utilities/vendor/highlightJS/styles/docco.css",
"chars": 1141,
"preview": "/*\nDocco style used in http://jashkenas.github.com/docco/ converted by Simon Madine (@thingsinjars)\n*/\n\n.hljs {\n displa"
},
{
"path": "app/utilities/vendor/highlightJS/styles/dracula.css",
"chars": 1015,
"preview": "/*\n\nDracula Theme v1.2.0\n\nhttps://github.com/zenorocha/dracula-theme\n\nCopyright 2015, All rights reserved\n\nCode licensed"
},
{
"path": "app/utilities/vendor/highlightJS/styles/far.css",
"chars": 849,
"preview": "/*\n\nFAR Style (c) MajestiC <majestic2k@gmail.com>\n\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n padding: 0.5em;\n "
},
{
"path": "app/utilities/vendor/highlightJS/styles/foundation.css",
"chars": 1086,
"preview": "/*\nDescription: Foundation 4 docs style for highlight.js\nAuthor: Dan Allen <dan.j.allen@gmail.com>\nWebsite: http://found"
},
{
"path": "app/utilities/vendor/highlightJS/styles/github-gist.css",
"chars": 1040,
"preview": "/**\n * GitHub Gist Theme\n * Author : Anthony Attard - https://github.com/AnthonyAttard\n * Author : Louis Barranqueiro - "
},
{
"path": "app/utilities/vendor/highlightJS/styles/github.css",
"chars": 1148,
"preview": "/*\n\ngithub.com style (c) Vasily Polovnyov <vast@whiteants.net>\n\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n padd"
},
{
"path": "app/utilities/vendor/highlightJS/styles/googlecode.css",
"chars": 1053,
"preview": "/*\n\nGoogle Code style (c) Aahan Krish <geekpanth3r@gmail.com>\n\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n paddi"
},
{
"path": "app/utilities/vendor/highlightJS/styles/grayscale.css",
"chars": 1966,
"preview": "/*\n\ngrayscale style (c) MY Sun <simonmysun@gmail.com>\n\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n padding: 0.5e"
},
{
"path": "app/utilities/vendor/highlightJS/styles/gruvbox-dark.css",
"chars": 1441,
"preview": "/*\n\nGruvbox style (dark) (c) Pavel Pertsev (original style at https://github.com/morhetz/gruvbox)\n\n*/\n\n.hljs {\n display"
},
{
"path": "app/utilities/vendor/highlightJS/styles/gruvbox-light.css",
"chars": 1442,
"preview": "/*\n\nGruvbox style (light) (c) Pavel Pertsev (original style at https://github.com/morhetz/gruvbox)\n\n*/\n\n.hljs {\n displa"
},
{
"path": "app/utilities/vendor/highlightJS/styles/hopscotch.css",
"chars": 1059,
"preview": "/*\n * Hopscotch\n * by Jan T. Sott\n * https://github.com/idleberg/Hopscotch\n *\n * This work is licensed under the Creativ"
},
{
"path": "app/utilities/vendor/highlightJS/styles/hybrid.css",
"chars": 1342,
"preview": "/*\n\nvim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid)\n\n*/\n\n/*background color*/\n.hljs {\n display: block;\n "
},
{
"path": "app/utilities/vendor/highlightJS/styles/idea.css",
"chars": 1173,
"preview": "/*\n\nIntellij Idea-like styling (c) Vasily Polovnyov <vast@whiteants.net>\n\n*/\n\n.hljs {\n display: block;\n overflow-x: au"
},
{
"path": "app/utilities/vendor/highlightJS/styles/ir-black.css",
"chars": 871,
"preview": "/*\n IR_Black style (c) Vasily Mikhailitchenko <vaskas@programica.ru>\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n"
},
{
"path": "app/utilities/vendor/highlightJS/styles/kimbie.dark.css",
"chars": 1067,
"preview": "/*\n Name: Kimbie (dark)\n Author: Jan T. Sott\n License: Creative Commons Attribution-ShareAlike 4.0 Unpor"
},
{
"path": "app/utilities/vendor/highlightJS/styles/kimbie.light.css",
"chars": 1068,
"preview": "/*\n Name: Kimbie (light)\n Author: Jan T. Sott\n License: Creative Commons Attribution-ShareAlike 4.0 Unpo"
},
{
"path": "app/utilities/vendor/highlightJS/styles/magula.css",
"chars": 891,
"preview": "/*\nDescription: Magula style for highligh.js\nAuthor: Ruslan Keba <rukeba@gmail.com>\nWebsite: http://rukeba.com/\nVersion:"
},
{
"path": "app/utilities/vendor/highlightJS/styles/mono-blue.css",
"chars": 749,
"preview": "/*\n Five-color theme from a single blue hue.\n*/\n.hljs {\n display: block;\n overflow-x: auto;\n padding: 0.5em;\n backg"
},
{
"path": "app/utilities/vendor/highlightJS/styles/monokai-sublime.css",
"chars": 1026,
"preview": "/*\n\nMonokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/\n\n*/\n\n.hljs {\n display: bl"
},
{
"path": "app/utilities/vendor/highlightJS/styles/monokai.css",
"chars": 938,
"preview": "/*\nMonokai style - ported by Luigi Maselli - http://grigio.org\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n paddi"
},
{
"path": "app/utilities/vendor/highlightJS/styles/obsidian.css",
"chars": 1079,
"preview": "/**\n * Obsidian style\n * ported by Alexander Marenin (http://github.com/ioncreature)\n */\n\n.hljs {\n display: block;\n ov"
},
{
"path": "app/utilities/vendor/highlightJS/styles/ocean.css",
"chars": 1004,
"preview": "/* Ocean Dark Theme */\n/* https://github.com/gavsiu */\n/* Original theme - https://github.com/chriskempson/base16 */\n\n/*"
},
{
"path": "app/utilities/vendor/highlightJS/styles/paraiso-dark.css",
"chars": 1007,
"preview": "/*\n Paraíso (dark)\n Created by Jan T. Sott (http://github.com/idleberg)\n Inspired by the art of Rubens LP (http"
},
{
"path": "app/utilities/vendor/highlightJS/styles/paraiso-light.css",
"chars": 1008,
"preview": "/*\n Paraíso (light)\n Created by Jan T. Sott (http://github.com/idleberg)\n Inspired by the art of Rubens LP (htt"
},
{
"path": "app/utilities/vendor/highlightJS/styles/pojoaque.css",
"chars": 1124,
"preview": "/*\n\nPojoaque Style by Jason Tate\nhttp://web-cms-designs.com/ftopict-10-pojoaque-style-for-highlight-js-code-highlighter."
},
{
"path": "app/utilities/vendor/highlightJS/styles/purebasic.css",
"chars": 2338,
"preview": "/*\n\nPureBASIC native IDE style ( version 1.0 - April 2016 )\n\nby Tristano Ajmone <tajmone@gmail.com>\n\nPublic Domain\n\nNOTE"
},
{
"path": "app/utilities/vendor/highlightJS/styles/qtcreator_dark.css",
"chars": 977,
"preview": "/*\n\nQt Creator dark color scheme\n\n*/\n\n\n.hljs {\n display: block;\n overflow-x: auto;\n padding: 0.5em;\n background: #00"
},
{
"path": "app/utilities/vendor/highlightJS/styles/qtcreator_light.css",
"chars": 978,
"preview": "/*\n\nQt Creator light color scheme\n\n*/\n\n\n.hljs {\n display: block;\n overflow-x: auto;\n padding: 0.5em;\n background: #f"
},
{
"path": "app/utilities/vendor/highlightJS/styles/railscasts.css",
"chars": 1211,
"preview": "/*\n\nRailscasts-like style (c) Visoft, Inc. (Damien White)\n\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n padding: "
},
{
"path": "app/utilities/vendor/highlightJS/styles/rainbow.css",
"chars": 983,
"preview": "/*\n\nStyle with support for rainbow parens\n\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n padding: 0.5em;\n backgro"
},
{
"path": "app/utilities/vendor/highlightJS/styles/school-book.css",
"chars": 999,
"preview": "/*\n\nSchool Book style from goldblog.com.ua (c) Zaripov Yura <yur4ik7@ukr.net>\n\n*/\n\n.hljs {\n display: block;\n overflow-"
},
{
"path": "app/utilities/vendor/highlightJS/styles/solarized-dark.css",
"chars": 1145,
"preview": "/*\n\nOrginal Style from ethanschoonover.com/solarized (c) Jeremy Hull <sourdrums@gmail.com>\n\n*/\n\n.hljs {\n display: block"
},
{
"path": "app/utilities/vendor/highlightJS/styles/solarized-light.css",
"chars": 1145,
"preview": "/*\n\nOrginal Style from ethanschoonover.com/solarized (c) Jeremy Hull <sourdrums@gmail.com>\n\n*/\n\n.hljs {\n display: block"
},
{
"path": "app/utilities/vendor/highlightJS/styles/sunburst.css",
"chars": 1183,
"preview": "/*\n\nSunburst-like style (c) Vasily Polovnyov <vast@whiteants.net>\n\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n p"
},
{
"path": "app/utilities/vendor/highlightJS/styles/tomorrow-night-blue.css",
"chars": 1152,
"preview": "/* Tomorrow Night Blue Theme */\n/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */\n/* Original them"
},
{
"path": "app/utilities/vendor/highlightJS/styles/tomorrow-night-bright.css",
"chars": 1082,
"preview": "/* Tomorrow Night Bright Theme */\n/* Original theme - https://github.com/chriskempson/tomorrow-theme */\n/* http://jmblog"
},
{
"path": "app/utilities/vendor/highlightJS/styles/tomorrow-night-eighties.css",
"chars": 1086,
"preview": "/* Tomorrow Night Eighties Theme */\n/* Original theme - https://github.com/chriskempson/tomorrow-theme */\n/* http://jmbl"
},
{
"path": "app/utilities/vendor/highlightJS/styles/tomorrow-night.css",
"chars": 1149,
"preview": "/* Tomorrow Night Theme */\n/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */\n/* Original theme - h"
},
{
"path": "app/utilities/vendor/highlightJS/styles/tomorrow.css",
"chars": 978,
"preview": "/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */\n\n/* Tomorrow Comment */\n.hljs-comment,\n.hljs-quo"
},
{
"path": "app/utilities/vendor/highlightJS/styles/vs.css",
"chars": 837,
"preview": "/*\n\nVisual Studio-like style based on original C# coloring by Jason Diamond <jason@diamond.name>\n\n*/\n.hljs {\n display: "
},
{
"path": "app/utilities/vendor/highlightJS/styles/xcode.css",
"chars": 1071,
"preview": "/*\n\nXCode style (c) Angel Garcia <angelgarcia.mail@gmail.com>\n\n*/\n\n.hljs {\n display: block;\n overflow-x: auto;\n paddi"
},
{
"path": "app/utilities/vendor/highlightJS/styles/xt256.css",
"chars": 1046,
"preview": "\n/*\n xt256.css\n\n Contact: initbar [at] protonmail [dot] ch\n : github.com/initbar\n*/\n\n.hljs {\n display: block;"
},
{
"path": "app/utilities/vendor/highlightJS/styles/zenburn.css",
"chars": 947,
"preview": "/*\n\nZenburn style from voldmar.ru (c) Vladimir Epifanov <voldmar@voldmar.ru>\nbased on dark.css by Ivan Sagalaev\n\n*/\n\n.hl"
},
{
"path": "app/utilities/vendor/prism/prism.scss",
"chars": 2375,
"preview": "/* PrismJS 1.15.0\nhttps://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+python */\n/**\n * "
},
{
"path": "configs/accountDummy.js",
"chars": 133,
"preview": "module.exports = {\n client_id: '9e234234234237abf', // dummy id\n client_secret: 'c4c87e5a72342343242356416f456b' // du"
},
{
"path": "configs/defaultConfig.js",
"chars": 1276,
"preview": "module.exports = {\n \"theme\": \"light\",\n \"autoUpdate\": false,\n \"avatar\": {\n \"type\": \"github\",\n \"bor"
},
{
"path": "docs/css/freelancer.css",
"chars": 12442,
"preview": "/*!\n * Start Bootstrap - Freelancer v3.3.7+1 (http://startbootstrap.com/template-overviews/freelancer)\n * Copyright 2013"
},
{
"path": "docs/index.html",
"chars": 29393,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE="
},
{
"path": "docs/js/contact_me.js",
"chars": 3056,
"preview": "$(function() {\n\n $(\"#contactForm input,#contactForm textarea\").jqBootstrapValidation({\n preventSubmit: true,\n "
},
{
"path": "docs/js/freelancer.js",
"chars": 1436,
"preview": "// Freelancer Theme JavaScript\n\n(function($) {\n \"use strict\"; // Start of use strict\n\n // jQuery for page scrollin"
},
{
"path": "docs/js/jqBootstrapValidation.js",
"chars": 36180,
"preview": "/* jqBootstrapValidation\n * A plugin for automating validation on Twitter Bootstrap formatted forms.\n *\n * v1.3.6\n *\n * "
},
{
"path": "docs/vendor/bootstrap/css/bootstrap.css",
"chars": 146045,
"preview": "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under MIT (https://gi"
},
{
"path": "docs/vendor/bootstrap/js/bootstrap.js",
"chars": 69707,
"preview": "/*!\n * Bootstrap v3.3.7 (http://getbootstrap.com)\n * Copyright 2011-2016 Twitter, Inc.\n * Licensed under the MIT license"
},
{
"path": "docs/vendor/font-awesome/css/font-awesome.css",
"chars": 35134,
"preview": "/*!\n * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome\n * License - http://fontawesome.io/lice"
},
{
"path": "docs/vendor/font-awesome/less/animated.less",
"chars": 713,
"preview": "// Animated Icons\n// --------------------------\n\n.@{fa-css-prefix}-spin {\n -webkit-animation: fa-spin 2s infinite linea"
},
{
"path": "docs/vendor/font-awesome/less/bordered-pulled.less",
"chars": 585,
"preview": "// Bordered & Pulled\n// -------------------------\n\n.@{fa-css-prefix}-border {\n padding: .2em .25em .15em;\n border: sol"
},
{
"path": "docs/vendor/font-awesome/less/core.less",
"chars": 452,
"preview": "// Base Class Definition\n// -------------------------\n\n.@{fa-css-prefix} {\n display: inline-block;\n font: normal norma"
},
{
"path": "docs/vendor/font-awesome/less/fixed-width.less",
"chars": 119,
"preview": "// Fixed Width Icons\n// -------------------------\n.@{fa-css-prefix}-fw {\n width: (18em / 14);\n text-align: center;\n}\n"
},
{
"path": "docs/vendor/font-awesome/less/font-awesome.less",
"chars": 495,
"preview": "/*!\n * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome\n * License - http://fontawesome.io/lice"
},
{
"path": "docs/vendor/font-awesome/less/icons.less",
"chars": 46249,
"preview": "/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen\n readers do not read off random characters th"
},
{
"path": "docs/vendor/font-awesome/less/larger.less",
"chars": 370,
"preview": "// Icon Sizes\n// -------------------------\n\n/* makes the font 33% larger relative to the icon container */\n.@{fa-css-pre"
},
{
"path": "docs/vendor/font-awesome/less/list.less",
"chars": 377,
"preview": "// List Icons\n// -------------------------\n\n.@{fa-css-prefix}-ul {\n padding-left: 0;\n margin-left: @fa-li-width;\n lis"
},
{
"path": "docs/vendor/font-awesome/less/mixins.less",
"chars": 1603,
"preview": "// Mixins\n// --------------------------\n\n.fa-icon() {\n display: inline-block;\n font: normal normal normal @fa-font-siz"
},
{
"path": "docs/vendor/font-awesome/less/path.less",
"chars": 771,
"preview": "/* FONT PATH\n * -------------------------- */\n\n@font-face {\n font-family: 'FontAwesome';\n src: url('@{fa-font-path}/fo"
},
{
"path": "docs/vendor/font-awesome/less/rotated-flipped.less",
"chars": 622,
"preview": "// Rotated & Flipped Icons\n// -------------------------\n\n.@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); }\n.@"
},
{
"path": "docs/vendor/font-awesome/less/screen-reader.less",
"chars": 118,
"preview": "// Screen Readers\n// -------------------------\n\n.sr-only { .sr-only(); }\n.sr-only-focusable { .sr-only-focusable(); }\n"
},
{
"path": "docs/vendor/font-awesome/less/stacked.less",
"chars": 476,
"preview": "// Stacked Icons\n// -------------------------\n\n.@{fa-css-prefix}-stack {\n position: relative;\n display: inline-block;\n"
},
{
"path": "docs/vendor/font-awesome/less/variables.less",
"chars": 20890,
"preview": "// Variables\n// --------------------------\n\n@fa-font-path: \"../fonts\";\n@fa-font-size-base: 14px;\n@fa-line-heigh"
},
{
"path": "docs/vendor/font-awesome/scss/_animated.scss",
"chars": 715,
"preview": "// Spinning Icons\n// --------------------------\n\n.#{$fa-css-prefix}-spin {\n -webkit-animation: fa-spin 2s infinite line"
},
{
"path": "docs/vendor/font-awesome/scss/_bordered-pulled.scss",
"chars": 592,
"preview": "// Bordered & Pulled\n// -------------------------\n\n.#{$fa-css-prefix}-border {\n padding: .2em .25em .15em;\n border: so"
},
{
"path": "docs/vendor/font-awesome/scss/_core.scss",
"chars": 459,
"preview": "// Base Class Definition\n// -------------------------\n\n.#{$fa-css-prefix} {\n display: inline-block;\n font: normal norm"
},
{
"path": "docs/vendor/font-awesome/scss/_fixed-width.scss",
"chars": 120,
"preview": "// Fixed Width Icons\n// -------------------------\n.#{$fa-css-prefix}-fw {\n width: (18em / 14);\n text-align: center;\n}\n"
},
{
"path": "docs/vendor/font-awesome/scss/_icons.scss",
"chars": 46979,
"preview": "/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen\n readers do not read off random characters th"
},
{
"path": "docs/vendor/font-awesome/scss/_larger.scss",
"chars": 375,
"preview": "// Icon Sizes\n// -------------------------\n\n/* makes the font 33% larger relative to the icon container */\n.#{$fa-css-pr"
},
{
"path": "docs/vendor/font-awesome/scss/_list.scss",
"chars": 378,
"preview": "// List Icons\n// -------------------------\n\n.#{$fa-css-prefix}-ul {\n padding-left: 0;\n margin-left: $fa-li-width;\n li"
},
{
"path": "docs/vendor/font-awesome/scss/_mixins.scss",
"chars": 1637,
"preview": "// Mixins\n// --------------------------\n\n@mixin fa-icon() {\n display: inline-block;\n font: normal normal normal #{$fa-"
},
{
"path": "docs/vendor/font-awesome/scss/_path.scss",
"chars": 783,
"preview": "/* FONT PATH\n * -------------------------- */\n\n@font-face {\n font-family: 'FontAwesome';\n src: url('#{$fa-font-path}/f"
},
{
"path": "docs/vendor/font-awesome/scss/_rotated-flipped.scss",
"chars": 672,
"preview": "// Rotated & Flipped Icons\n// -------------------------\n\n.#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, "
},
{
"path": "docs/vendor/font-awesome/scss/_screen-reader.scss",
"chars": 134,
"preview": "// Screen Readers\n// -------------------------\n\n.sr-only { @include sr-only(); }\n.sr-only-focusable { @include sr-only-f"
},
{
"path": "docs/vendor/font-awesome/scss/_stacked.scss",
"chars": 482,
"preview": "// Stacked Icons\n// -------------------------\n\n.#{$fa-css-prefix}-stack {\n position: relative;\n display: inline-block;"
},
{
"path": "docs/vendor/font-awesome/scss/_variables.scss",
"chars": 20971,
"preview": "// Variables\n// --------------------------\n\n$fa-font-path: \"../fonts\" !default;\n$fa-font-size-base: 14px !defau"
},
{
"path": "docs/vendor/font-awesome/scss/font-awesome.scss",
"chars": 430,
"preview": "/*!\n * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome\n * License - http://fontawesome.io/lice"
},
{
"path": "docs/vendor/jquery/jquery.js",
"chars": 293430,
"preview": "/*!\n * jQuery JavaScript Library v1.12.4\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Co"
},
{
"path": "index.html",
"chars": 348,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Lepton</title>\n <!-- stylesheet for katex sup"
},
{
"path": "license.json",
"chars": 151676,
"preview": "{\n \"@babel/code-frame@7.10.1\": {\n \"licenses\": \"MIT\",\n \"repository\": \"https://github.com/babel/babel\",\n \"publis"
},
{
"path": "main.js",
"chars": 12193,
"preview": "require('@electron/remote/main').initialize()\nconst os = require('os')\nconst electron = require('electron')\nconst nconf "
},
{
"path": "package.json",
"chars": 4649,
"preview": "{\n \"name\": \"Lepton\",\n \"version\": \"1.10.1-alpha.1\",\n \"description\": \"Democratizing Code Snippets Management (macOS/Win"
},
{
"path": "webpack.config.js",
"chars": 1631,
"preview": "\n\nconst path = require('path')\nconst webpack = require('webpack')\nconst nodeExternals = require('webpack-node-externals'"
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the hackjutsu/Lepton GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 201 files (1.4 MB), approximately 420.2k tokens, and a symbol index with 389 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.