├── app
├── render
│ ├── partials
│ │ ├── 404.html
│ │ ├── welcome.html
│ │ ├── topbar.html
│ │ └── sidebar.html
│ ├── index.js
│ ├── index.html
│ ├── db.js
│ ├── css
│ │ ├── main.less
│ │ └── main.css
│ └── app.js
├── config
│ └── config.js
├── package.json
├── browser
│ └── window_state.js
└── main.js
├── doc
└── screenshot.png
├── resources
├── icon.png
├── osx
│ ├── icon.icns
│ ├── dmg-icon.icns
│ ├── dmg-background.png
│ ├── dmg-background@2x.png
│ ├── appdmg.json
│ ├── helper_apps
│ │ ├── Info.plist
│ │ ├── Info EH.plist
│ │ └── Info NP.plist
│ └── Info.plist
├── windows
│ ├── icon.ico
│ ├── setup-banner.bmp
│ ├── setup-icon.ico
│ └── installer.nsi
└── linux
│ ├── DEBIAN
│ └── control
│ └── app.desktop
├── gulpfile.js
├── .editorconfig
├── tasks
├── release.js
├── generate_specs_import.js
├── utils.js
├── start.js
├── app_npm_install.js
├── release_linux.js
├── build.js
├── release_windows.js
└── release_osx.js
├── README.md
├── package.json
└── .gitignore
/app/render/partials/404.html:
--------------------------------------------------------------------------------
1 |
Page not found
2 |
--------------------------------------------------------------------------------
/app/render/partials/welcome.html:
--------------------------------------------------------------------------------
1 | Welcome
2 |
3 |
--------------------------------------------------------------------------------
/doc/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxding/greader/HEAD/doc/screenshot.png
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxding/greader/HEAD/resources/icon.png
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./tasks/build');
4 | require('./tasks/release');
5 |
--------------------------------------------------------------------------------
/resources/osx/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxding/greader/HEAD/resources/osx/icon.icns
--------------------------------------------------------------------------------
/resources/windows/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxding/greader/HEAD/resources/windows/icon.ico
--------------------------------------------------------------------------------
/resources/osx/dmg-icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxding/greader/HEAD/resources/osx/dmg-icon.icns
--------------------------------------------------------------------------------
/resources/osx/dmg-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxding/greader/HEAD/resources/osx/dmg-background.png
--------------------------------------------------------------------------------
/resources/windows/setup-banner.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxding/greader/HEAD/resources/windows/setup-banner.bmp
--------------------------------------------------------------------------------
/resources/windows/setup-icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxding/greader/HEAD/resources/windows/setup-icon.ico
--------------------------------------------------------------------------------
/resources/osx/dmg-background@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxding/greader/HEAD/resources/osx/dmg-background@2x.png
--------------------------------------------------------------------------------
/resources/linux/DEBIAN/control:
--------------------------------------------------------------------------------
1 | Package: {{name}}
2 | Version: {{version}}
3 | Maintainer: {{author}}
4 | Priority: optional
5 | Architecture: amd64
6 | Installed-Size: {{size}}
7 | Description: {{description}}
8 |
--------------------------------------------------------------------------------
/app/render/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | (function (){
4 | window.$ = window.jQuery = require("jquery");
5 | require("bootstrap");
6 | require("angular");
7 | require("angular-ui-router");
8 |
9 | require("./app.js");
10 | require("../config/config");
11 | })();
12 |
--------------------------------------------------------------------------------
/resources/linux/app.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Version=1.0
3 | Type=Application
4 | Encoding=UTF-8
5 | Name={{productName}}
6 | Comment={{description}}
7 | Exec=/opt/{{name}}/{{name}}
8 | Path=/opt/{{name}}/
9 | Icon=/opt/{{name}}/icon.png
10 | Terminal=false
11 | Categories=Application;
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.json]
13 | indent_size = 2
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/resources/osx/appdmg.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "{{productName}}",
3 | "icon": "{{dmgIcon}}",
4 | "background": "{{dmgBackground}}",
5 | "icon-size": 128,
6 | "contents": [
7 | { "x": 410, "y": 220, "type": "link", "path": "/Applications" },
8 | { "x": 130, "y": 220, "type": "file", "path": "{{appPath}}" }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/tasks/release.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var utils = require('./utils');
5 |
6 | var releaseForOs = {
7 | osx: require('./release_osx'),
8 | linux: require('./release_linux'),
9 | windows: require('./release_windows'),
10 | };
11 |
12 | gulp.task('release', ['build'], function () {
13 | return releaseForOs[utils.os()]();
14 | });
15 |
--------------------------------------------------------------------------------
/app/config/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (function () {
4 | var jetpack = require('fs-jetpack');
5 | if (typeof window === 'object') {
6 | // Web browser context, __dirname points to folder where app.html file is.
7 | window.env = jetpack.read(require("path").join(__dirname, '../package.json'), 'json');
8 | } else {
9 | // Node context
10 | module.exports = jetpack.read(__dirname + '../package.json', 'json');
11 | }
12 | }());
13 |
--------------------------------------------------------------------------------
/resources/osx/helper_apps/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleIdentifier
6 | {{identifier}}.helper
7 | CFBundleName
8 | {{productName}} Helper
9 | CFBundlePackageType
10 | APPL
11 | DTSDKName
12 | macosx
13 | LSUIElement
14 |
15 | NSSupportsAutomaticGraphicsSwitching
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Github Readme Reader is a collector of readme documentations, you can use it to collect the readme documentations you want to read it.
2 | All the Readme file will be downloaded on your local, you can read it at any where even you are offline.
3 |
4 | ## Usage
5 |
6 | ```
7 | npm install // install dependencies
8 | npm start // start for dev
9 | npm run release // create app
10 | ```
11 | Add readme documentation of repositories you love on the top navigator.
12 |
13 | Learn more about build on [electron-boilerplate](https://github.com/szwacz/electron-boilerplate).
14 | ## ScreenShot
15 | 
16 |
17 |
18 | ## TODO
19 | - Delete readme file
20 | - Welcome page
21 | - Content search
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "asar": "^0.7.2",
4 | "electron-prebuilt": "^0.35.1",
5 | "fs-jetpack": "^0.7.0",
6 | "gulp": "^3.9.0",
7 | "gulp-less": "^3.0.3",
8 | "gulp-util": "^3.0.6",
9 | "q": "^1.4.1",
10 | "rollup": "^0.21.0",
11 | "tree-kill": "^0.1.1",
12 | "yargs": "^3.15.0"
13 | },
14 | "optionalDependencies": {
15 | "appdmg": "^0.3.2",
16 | "rcedit": "^0.3.0"
17 | },
18 | "scripts": {
19 | "postinstall": "node ./tasks/app_npm_install",
20 | "app-install": "node ./tasks/app_npm_install",
21 | "build": "gulp build",
22 | "release": "gulp release --env=production",
23 | "start": "node ./tasks/start",
24 | "test": "node ./tasks/start --env=test"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/resources/osx/helper_apps/Info EH.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDisplayName
6 | {{productName}} Helper EH
7 | CFBundleExecutable
8 | {{productName}} Helper EH
9 | CFBundleIdentifier
10 | {{identifier}}.helper.EH
11 | CFBundleName
12 | {{productName}} Helper EH
13 | CFBundlePackageType
14 | APPL
15 | DTSDKName
16 | macosx
17 | LSUIElement
18 |
19 | NSSupportsAutomaticGraphicsSwitching
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/resources/osx/helper_apps/Info NP.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDisplayName
6 | {{productName}} Helper NP
7 | CFBundleExecutable
8 | {{productName}} Helper NP
9 | CFBundleIdentifier
10 | {{identifier}}.helper.NP
11 | CFBundleName
12 | {{productName}} Helper NP
13 | CFBundlePackageType
14 | APPL
15 | DTSDKName
16 | macosx
17 | LSUIElement
18 |
19 | NSSupportsAutomaticGraphicsSwitching
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "GReader",
3 | "productName": "GReader",
4 | "version": "0.0.1",
5 | "main": "main.js",
6 | "homepage": "https://github.com/Nekle/greader",
7 | "description": "Github Readme Reader is a collector of readme documentations, you can use it to collect the readme documentations you want to read it. All the Readme file will be downloaded on your local, you can read it at any where even you are offline.",
8 | "dbFileName": "readme.db",
9 | "tableName": "readme",
10 | "scripts": {
11 | "start": "electron ."
12 | },
13 | "dependencies": {
14 | "angular": "^1.3.15",
15 | "angular-ui-router": "^0.2.14",
16 | "bootstrap": "^3.3.4",
17 | "fs-jetpack": "^0.6.4",
18 | "github": "^0.2.4",
19 | "github-markdown-css": "^2.0.8",
20 | "jquery": "^2.1.4",
21 | "sql.js": "^0.2.21"
22 | },
23 | "license": "MIT"
24 | }
25 |
--------------------------------------------------------------------------------
/tasks/generate_specs_import.js:
--------------------------------------------------------------------------------
1 | // Spec files are scattered through the whole project. Here we're searching
2 | // for them and generate one entry file which will run all the tests.
3 |
4 | 'use strict';
5 |
6 | var jetpack = require('fs-jetpack');
7 | var srcDir = jetpack.cwd('app');
8 |
9 | var fileName = 'spec.js';
10 | var fileBanner = "// This file is generated automatically.\n"
11 | + "// All your modifications to it will be lost (so don't do it).\n";
12 | var whatToInclude = [
13 | '*.spec.js',
14 | '!node_modules/**',
15 | ];
16 |
17 | module.exports = function () {
18 | return srcDir.findAsync('.', { matching: whatToInclude }, 'relativePath')
19 | .then(function (specPaths) {
20 | var fileContent = specPaths.map(function (path) {
21 | return 'import "' + path + '";';
22 | }).join('\n');
23 | return srcDir.writeAsync(fileName, fileBanner + fileContent);
24 | })
25 | .then(function () {
26 | return srcDir.path(fileName);
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/resources/osx/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDisplayName
6 | {{productName}}
7 | CFBundleExecutable
8 | {{productName}}
9 | CFBundleIconFile
10 | icon.icns
11 | CFBundleIdentifier
12 | {{identifier}}
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | {{productName}}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleVersion
20 | {{version}}
21 | CFBundleGetInfoString
22 | {{version}}
23 | LSMinimumSystemVersion
24 | 10.8.0
25 | NSMainNibFile
26 | MainMenu
27 | NSPrincipalClass
28 | AtomApplication
29 | NSSupportsAutomaticGraphicsSwitching
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/tasks/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var argv = require('yargs').argv;
4 | var os = require('os');
5 | var jetpack = require('fs-jetpack');
6 |
7 | module.exports.os = function () {
8 | switch (os.platform()) {
9 | case 'darwin':
10 | return 'osx';
11 | case 'linux':
12 | return 'linux';
13 | case 'win32':
14 | return 'windows';
15 | }
16 | return 'unsupported';
17 | };
18 |
19 | module.exports.replace = function (str, patterns) {
20 | Object.keys(patterns).forEach(function (pattern) {
21 | var matcher = new RegExp('{{' + pattern + '}}', 'g');
22 | str = str.replace(matcher, patterns[pattern]);
23 | });
24 | return str;
25 | };
26 |
27 | module.exports.getEnvName = function () {
28 | return argv.env || 'development';
29 | };
30 |
31 | module.exports.getSigningId = function () {
32 | return argv.sign;
33 | };
34 |
35 | module.exports.getElectronVersion = function () {
36 | var manifest = jetpack.read(__dirname + '/../package.json', 'json');
37 | return manifest.devDependencies['electron-prebuilt'].substring(1);
38 | };
39 |
--------------------------------------------------------------------------------
/app/render/partials/topbar.html:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/app/render/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Github Readme Reader
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/browser/window_state.js:
--------------------------------------------------------------------------------
1 | // Simple module to help you remember the size and position of windows.
2 | // Can be used for more than one window, just construct many
3 | // instances of it and give each different name.
4 |
5 | 'use strict';
6 |
7 | var app = require('app');
8 | var jetpack = require('fs-jetpack');
9 |
10 | module.exports = function (name, defaults) {
11 |
12 | var userDataDir = jetpack.cwd(app.getPath('userData'));
13 | var stateStoreFile = 'window-state-' + name +'.json'
14 |
15 | var state = userDataDir.read(stateStoreFile, 'json') || {
16 | width: defaults.width,
17 | height: defaults.height
18 | };
19 |
20 | var saveState = function (win) {
21 | if (!win.isMaximized() && !win.isMinimized()) {
22 | var position = win.getPosition();
23 | var size = win.getSize();
24 | state.x = position[0];
25 | state.y = position[1];
26 | state.width = size[0];
27 | state.height = size[1];
28 | }
29 | state.isMaximized = win.isMaximized();
30 | userDataDir.write(stateStoreFile, state, { atomic: true });
31 | };
32 |
33 | return {
34 | get x() { return state.x },
35 | get y() { return state.y },
36 | get width() { return state.width },
37 | get height() { return state.height },
38 | get isMaximized() { return state.isMaximized },
39 | saveState: saveState
40 | };
41 | };
42 |
--------------------------------------------------------------------------------
/tasks/start.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Q = require('q');
4 | var electron = require('electron-prebuilt');
5 | var pathUtil = require('path');
6 | var childProcess = require('child_process');
7 | var kill = require('tree-kill');
8 | var utils = require('./utils');
9 | var watch;
10 |
11 | var gulpPath = pathUtil.resolve('./node_modules/.bin/gulp');
12 | if (process.platform === 'win32') {
13 | gulpPath += '.cmd';
14 | }
15 |
16 | var runBuild = function () {
17 | var deferred = Q.defer();
18 |
19 | var build = childProcess.spawn(gulpPath, [
20 | 'build',
21 | '--env=' + utils.getEnvName(),
22 | '--color'
23 | ], {
24 | stdio: 'inherit'
25 | });
26 |
27 | build.on('close', function (code) {
28 | deferred.resolve();
29 | });
30 |
31 | return deferred.promise;
32 | };
33 |
34 | var runGulpWatch = function () {
35 | watch = childProcess.spawn(gulpPath, [
36 | 'watch',
37 | '--env=' + utils.getEnvName(),
38 | '--color'
39 | ], {
40 | stdio: 'inherit'
41 | });
42 |
43 | watch.on('close', function (code) {
44 | // Gulp watch exits when error occured during build.
45 | // Just respawn it then.
46 | runGulpWatch();
47 | });
48 | };
49 |
50 | var runApp = function () {
51 | var app = childProcess.spawn(electron, ['./build'], {
52 | stdio: 'inherit'
53 | });
54 |
55 | app.on('close', function (code) {
56 | // User closed the app. Kill the host process.
57 | kill(watch.pid, 'SIGKILL', function () {
58 | process.exit();
59 | });
60 | });
61 | };
62 |
63 | runBuild()
64 | .then(function () {
65 | runGulpWatch();
66 | runApp();
67 | });
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io
2 | ### OSX ###
3 | .DS_Store
4 | .AppleDouble
5 | .LSOverride
6 |
7 | ### WebStorm ###
8 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
9 |
10 | *.iml
11 |
12 | ## Directory-based project format:
13 | .idea/
14 | # if you remove the above rule, at least ignore the following:
15 |
16 | # User-specific stuff:
17 | # .idea/workspace.xml
18 | # .idea/tasks.xml
19 | # .idea/dictionaries
20 |
21 | # Sensitive or high-churn files:
22 | # .idea/dataSources.ids
23 | # .idea/dataSources.xml
24 | # .idea/sqlDataSources.xml
25 | # .idea/dynamic.xml
26 | # .idea/uiDesigner.xml
27 |
28 | # Gradle:
29 | # .idea/gradle.xml
30 | # .idea/libraries
31 |
32 | # Mongo Explorer plugin:
33 | # .idea/mongoSettings.xml
34 |
35 | ## File-based project format:
36 | *.ipr
37 | *.iws
38 |
39 | ## Plugin-specific files:
40 |
41 | # IntelliJ
42 | /out/
43 |
44 | # mpeltonen/sbt-idea plugin
45 | .idea_modules/
46 |
47 | # JIRA plugin
48 | atlassian-ide-plugin.xml
49 |
50 | # Crashlytics plugin (for Android Studio and IntelliJ)
51 | com_crashlytics_export_strings.xml
52 | crashlytics.properties
53 | crashlytics-build.properties
54 |
55 | ### Node ###
56 | # Logs
57 | logs
58 | *.log
59 |
60 | # Runtime data
61 | pids
62 | *.pid
63 | *.seed
64 |
65 | # Directory for instrumented libs generated by jscoverage/JSCover
66 | lib-cov
67 |
68 | # Coverage directory used by tools like istanbul
69 | coverage
70 |
71 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
72 | .grunt
73 |
74 | # node-waf configuration
75 | .lock-wscript
76 |
77 | # Compiled binary addons (http://nodejs.org/api/addons.html)
78 | build/Release
79 |
80 | # Dependency directory
81 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
82 |
83 | **/node_modules
84 |
85 | releases
86 | build
87 |
--------------------------------------------------------------------------------
/tasks/app_npm_install.js:
--------------------------------------------------------------------------------
1 | // This script allows you to install native modules (those which have
2 | // to be compiled) for your Electron app.
3 | // The problem is that 'npm install' compiles them against node.js you have
4 | // installed on your computer, NOT against node.js used inside Electron.
5 | // To compile them agains Electron we need to tell npm it explicitly (and
6 | // that's what this script does).
7 |
8 | 'use strict';
9 |
10 | var childProcess = require('child_process');
11 | var jetpack = require('fs-jetpack');
12 | var argv = require('yargs').argv;
13 |
14 | var utils = require('./utils');
15 |
16 | var electronVersion = utils.getElectronVersion();
17 |
18 | var nodeModulesDir = jetpack.cwd(__dirname + '/../app/node_modules')
19 | var dependenciesCompiledAgainst = nodeModulesDir.read('electron_version');
20 |
21 | // When you raise version of Electron used in your project, the safest
22 | // thing to do is remove all installed dependencies and install them
23 | // once again (so they compile against new version if you use any
24 | // native package).
25 | if (electronVersion !== dependenciesCompiledAgainst) {
26 | nodeModulesDir.dir('.', { empty: true });
27 | // Save the version string in file next to all installed modules so we know
28 | // in the future what version of Electron has been used to compile them.
29 | nodeModulesDir.write('electron_version', electronVersion);
30 | }
31 |
32 | // Tell the 'npm install' that we want to compile for Electron.
33 | process.env.npm_config_disturl = "https://atom.io/download/atom-shell";
34 | process.env.npm_config_target = electronVersion;
35 |
36 | var params = ['install'];
37 | // Maybe there was name of package user wants to install passed
38 | // as a parameter to this script.
39 | if (argv._.length > 0) {
40 | params.push(argv._[0]);
41 | params.push('--save');
42 | }
43 |
44 | var installCommand;
45 | if (process.platform === 'win32') {
46 | installCommand = 'npm.cmd'
47 | } else {
48 | installCommand = 'npm'
49 | }
50 |
51 | var install = childProcess.spawn(installCommand, params, {
52 | cwd: __dirname + '/../app',
53 | env: process.env,
54 | stdio: 'inherit'
55 | });
56 |
--------------------------------------------------------------------------------
/app/render/db.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var jetpack = require('fs-jetpack');
4 |
5 | function DB() {
6 | var GithubApi = require('github');
7 | this.github = new GithubApi({version: "3.0.0"});
8 | this.db = null;
9 | this.config = window.env;
10 | this.userDataDir = jetpack.cwd(require("remote").require("app").getPath("userData"));
11 | }
12 |
13 | DB.prototype.init = function () {
14 |
15 | if (this.db) {
16 | return this.db;
17 | }
18 |
19 | var fs = require("fs");
20 | var sqlite = require("sql.js");
21 | var self = this;
22 | function createOrReadDatabase(dbname) {
23 | var yes = fs.existsSync(self.userDataDir.path(dbname));
24 | if(yes) {
25 | var data = fs.readFileSync(self.userDataDir.path(dbname));
26 | if (!data) {
27 | return ;
28 | }
29 | return new sqlite.Database(data);
30 | } else {
31 | try {
32 | var db = new sqlite.Database();
33 | // Run a query without reading the results
34 | db.run("CREATE TABLE " + self.config.tableName + " (full_name, html_url, language, description, owner_html_url);");
35 | db.run("create unique index if not exists full_name_idx on " + self.config.tableName + "(full_name)");
36 | var buffer = new Buffer(db.export());
37 | self.userDataDir.write(dbname, buffer);
38 | return db;
39 | } catch(e) {
40 | console.log(e);
41 | }
42 | }
43 | }
44 | this.db = createOrReadDatabase(this.config.dbFileName);
45 | return this.db;
46 | };
47 |
48 |
49 | DB.prototype.add = function (repo) {
50 | var fs = require("fs");
51 | var p = Promise.resolve(repo);
52 | var self = this;
53 | return p.then(function (repo) {
54 | return new Promise(function (resolve, reject) {
55 | self.github.repos.getReadme({headers: {Accept: "application/vnd.github.v3.html"}, user: repo.user, repo: repo.name}, function (err, page) {
56 | if (err) {
57 | reject(err);
58 | } else if (!page) {
59 | reject("No readme file");
60 | } else {
61 | var name = repo.user + "_" + repo.name;
62 | name = name.replace(/\.|\//g, '_');
63 | self.userDataDir.writeAsync(name + ".html", page).then(resolve, reject);
64 | }
65 | });
66 | });
67 | }).then(function () {
68 | return new Promise(function (resolve, reject) {
69 | self.github.repos.get({user: repo.user, repo: repo.name}, function (err, data) {
70 | if (err) {
71 | return reject(err);
72 | }
73 | try {
74 | self.db.run("INSERT INTO " + self.config.tableName + " VALUES (?,?,?,?,?)", [data.full_name, data.html_url, data.language, data.description, data.owner.html_url]);
75 | var d = self.db.export();
76 | var buffer = new Buffer(d);
77 | self.userDataDir.writeAsync(self.config.dbFileName, buffer).then(resolve, reject);
78 | } catch(e) {
79 | reject(e);
80 | }
81 | });
82 | });
83 | });
84 | };
85 |
86 |
87 | module.exports = exports = DB;
88 |
--------------------------------------------------------------------------------
/app/render/partials/sidebar.html:
--------------------------------------------------------------------------------
1 |
27 |
28 |
95 |
--------------------------------------------------------------------------------
/tasks/release_linux.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Q = require('q');
4 | var gulpUtil = require('gulp-util');
5 | var childProcess = require('child_process');
6 | var jetpack = require('fs-jetpack');
7 | var asar = require('asar');
8 | var utils = require('./utils');
9 |
10 | var projectDir;
11 | var releasesDir;
12 | var packName;
13 | var packDir;
14 | var tmpDir;
15 | var readyAppDir;
16 | var manifest;
17 |
18 | var init = function () {
19 | projectDir = jetpack;
20 | tmpDir = projectDir.dir('./tmp', { empty: true });
21 | releasesDir = projectDir.dir('./releases');
22 | manifest = projectDir.read('app/package.json', 'json');
23 | packName = manifest.name + '_' + manifest.version;
24 | packDir = tmpDir.dir(packName);
25 | readyAppDir = packDir.cwd('opt', manifest.name);
26 |
27 | return Q();
28 | };
29 |
30 | var copyRuntime = function () {
31 | return projectDir.copyAsync('node_modules/electron-prebuilt/dist', readyAppDir.path(), { overwrite: true });
32 | };
33 |
34 | var packageBuiltApp = function () {
35 | var deferred = Q.defer();
36 |
37 | asar.createPackage(projectDir.path('build'), readyAppDir.path('resources/app.asar'), function() {
38 | deferred.resolve();
39 | });
40 |
41 | return deferred.promise;
42 | };
43 |
44 | var finalize = function () {
45 | // Create .desktop file from the template
46 | var desktop = projectDir.read('resources/linux/app.desktop');
47 | desktop = utils.replace(desktop, {
48 | name: manifest.name,
49 | productName: manifest.productName,
50 | description: manifest.description,
51 | version: manifest.version,
52 | author: manifest.author
53 | });
54 | packDir.write('usr/share/applications/' + manifest.name + '.desktop', desktop);
55 |
56 | // Copy icon
57 | projectDir.copy('resources/icon.png', readyAppDir.path('icon.png'));
58 |
59 | return Q();
60 | };
61 |
62 | var renameApp = function() {
63 | return readyAppDir.renameAsync("electron", manifest.name);
64 | };
65 |
66 | var packToDebFile = function () {
67 | var deferred = Q.defer();
68 |
69 | var debFileName = packName + '_amd64.deb';
70 | var debPath = releasesDir.path(debFileName);
71 |
72 | gulpUtil.log('Creating DEB package...');
73 |
74 | // Counting size of the app in KiB
75 | var appSize = Math.round(readyAppDir.inspectTree('.').size / 1024);
76 |
77 | // Preparing debian control file
78 | var control = projectDir.read('resources/linux/DEBIAN/control');
79 | control = utils.replace(control, {
80 | name: manifest.name,
81 | description: manifest.description,
82 | version: manifest.version,
83 | author: manifest.author,
84 | size: appSize
85 | });
86 | packDir.write('DEBIAN/control', control);
87 |
88 | // Build the package...
89 | childProcess.exec('fakeroot dpkg-deb -Zxz --build ' + packDir.path().replace(/\s/g, '\\ ') + ' ' + debPath.replace(/\s/g, '\\ '),
90 | function (error, stdout, stderr) {
91 | if (error || stderr) {
92 | console.log("ERROR while building DEB package:");
93 | console.log(error);
94 | console.log(stderr);
95 | } else {
96 | gulpUtil.log('DEB package ready!', debPath);
97 | }
98 | deferred.resolve();
99 | });
100 |
101 | return deferred.promise;
102 | };
103 |
104 | var cleanClutter = function () {
105 | return tmpDir.removeAsync('.');
106 | };
107 |
108 | module.exports = function () {
109 | return init()
110 | .then(copyRuntime)
111 | .then(packageBuiltApp)
112 | .then(finalize)
113 | .then(renameApp)
114 | .then(packToDebFile)
115 | .then(cleanClutter)
116 | .catch(console.error);
117 | };
118 |
--------------------------------------------------------------------------------
/tasks/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var pathUtil = require('path');
4 | var Q = require('q');
5 | var gulp = require('gulp');
6 | var rollup = require('rollup');
7 | var less = require('gulp-less');
8 | var jetpack = require('fs-jetpack');
9 |
10 | var utils = require('./utils');
11 |
12 | var projectDir = jetpack;
13 | var srcDir = projectDir.cwd('./app');
14 | var destDir = projectDir.cwd('./build');
15 |
16 | var paths = {
17 | copyFromAppDir: [
18 | './node_modules/**',
19 | './render/**',
20 | './config/**',
21 | './browser/**',
22 | './**/*.+(jpg|png|svg)'
23 | ],
24 | }
25 |
26 | // -------------------------------------
27 | // Tasks
28 | // -------------------------------------
29 |
30 | gulp.task('clean', function(callback) {
31 | return destDir.dirAsync('.', { empty: true });
32 | });
33 |
34 |
35 | var copyTask = function () {
36 | return projectDir.copyAsync('app', destDir.path(), {
37 | overwrite: true,
38 | matching: paths.copyFromAppDir
39 | });
40 | };
41 | gulp.task('copy', ['clean'], copyTask);
42 | gulp.task('copy-watch', copyTask);
43 |
44 |
45 | var bundle = function (src, dest) {
46 | var deferred = Q.defer();
47 |
48 | rollup.rollup({
49 | entry: src,
50 | }).then(function (bundle) {
51 | var jsFile = pathUtil.basename(dest);
52 | var result = bundle.generate({
53 | format: 'cjs',
54 | sourceMap: true,
55 | sourceMapFile: jsFile,
56 | });
57 | // Wrap code in self invoking function so the variables don't
58 | // pollute the global namespace.
59 | var isolatedCode = '(function () {' + result.code + '}());';
60 | return Q.all([
61 | destDir.writeAsync(dest, isolatedCode + '\n//# sourceMappingURL=' + jsFile + '.map'),
62 | destDir.writeAsync(dest + '.map', result.map.toString()),
63 | ]);
64 | }).then(function () {
65 | deferred.resolve();
66 | }).catch(function (err) {
67 | console.error('Build: Error during rollup', err.stack);
68 | });
69 |
70 | return deferred.promise;
71 | };
72 |
73 | var bundleApplication = function () {
74 | return Q.all([
75 | bundle(srcDir.path('main.js'), destDir.path('main.js'))
76 | ]);
77 | };
78 |
79 | var bundleTask = function () {
80 | return bundleApplication();
81 | };
82 | gulp.task('bundle', ['clean'], bundleTask);
83 | gulp.task('bundle-watch', bundleTask);
84 |
85 |
86 | var lessTask = function () {
87 | return gulp.src('app/render/css/main.less')
88 | .pipe(less())
89 | .pipe(gulp.dest(destDir.path('/render/css')));
90 | };
91 | gulp.task('less', ['clean'], lessTask);
92 | gulp.task('less-watch', lessTask);
93 |
94 |
95 | gulp.task('finalize', ['clean'], function () {
96 | var manifest = srcDir.read('package.json', 'json');
97 |
98 | // Add "dev" or "test" suffix to name, so Electron will write all data
99 | // like cookies and localStorage in separate places for each environment.
100 | switch (utils.getEnvName()) {
101 | case 'development':
102 | manifest.name += '-dev';
103 | manifest.productName += ' Dev';
104 | break;
105 | case 'test':
106 | manifest.name += '-test';
107 | manifest.productName += ' Test';
108 | break;
109 | }
110 |
111 | // Copy environment variables to package.json file for easy use
112 | // in the running application. This is not official way of doing
113 | // things, but also isn't prohibited ;)
114 | manifest.env = projectDir.read('config/env_' + utils.getEnvName() + '.json', 'json');
115 |
116 | destDir.write('package.json', manifest);
117 | });
118 |
119 |
120 | gulp.task('watch', function () {
121 | gulp.watch('app/**/*.js', ['bundle-watch']);
122 | gulp.watch(paths.copyFromAppDir, { cwd: 'app' }, ['copy-watch']);
123 | gulp.watch('app/**/*.less', ['less-watch']);
124 | });
125 |
126 |
127 | gulp.task('build', ['bundle', 'less', 'copy', 'finalize']);
128 |
--------------------------------------------------------------------------------
/tasks/release_windows.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Q = require('q');
4 | var gulpUtil = require('gulp-util');
5 | var childProcess = require('child_process');
6 | var jetpack = require('fs-jetpack');
7 | var asar = require('asar');
8 | var utils = require('./utils');
9 |
10 | var projectDir;
11 | var tmpDir;
12 | var releasesDir;
13 | var readyAppDir;
14 | var manifest;
15 |
16 | var init = function () {
17 | projectDir = jetpack;
18 | tmpDir = projectDir.dir('./tmp', { empty: true });
19 | releasesDir = projectDir.dir('./releases');
20 | manifest = projectDir.read('app/package.json', 'json');
21 | readyAppDir = tmpDir.cwd(manifest.name);
22 |
23 | return Q();
24 | };
25 |
26 | var copyRuntime = function () {
27 | return projectDir.copyAsync('node_modules/electron-prebuilt/dist', readyAppDir.path(), { overwrite: true });
28 | };
29 |
30 | var cleanupRuntime = function () {
31 | return readyAppDir.removeAsync('resources/default_app');
32 | };
33 |
34 | var packageBuiltApp = function () {
35 | var deferred = Q.defer();
36 |
37 | asar.createPackage(projectDir.path('build'), readyAppDir.path('resources/app.asar'), function() {
38 | deferred.resolve();
39 | });
40 |
41 | return deferred.promise;
42 | };
43 |
44 | var finalize = function () {
45 | var deferred = Q.defer();
46 |
47 | projectDir.copy('resources/windows/icon.ico', readyAppDir.path('icon.ico'));
48 |
49 | // Replace Electron icon for your own.
50 | var rcedit = require('rcedit');
51 | rcedit(readyAppDir.path('electron.exe'), {
52 | 'icon': projectDir.path('resources/windows/icon.ico'),
53 | 'version-string': {
54 | 'ProductName': manifest.productName,
55 | 'FileDescription': manifest.description,
56 | }
57 | }, function (err) {
58 | if (!err) {
59 | deferred.resolve();
60 | }
61 | });
62 |
63 | return deferred.promise;
64 | };
65 |
66 | var renameApp = function () {
67 | return readyAppDir.renameAsync('electron.exe', manifest.productName + '.exe');
68 | };
69 |
70 | var createInstaller = function () {
71 | var deferred = Q.defer();
72 |
73 | var finalPackageName = manifest.name + '_' + manifest.version + '.exe';
74 | var installScript = projectDir.read('resources/windows/installer.nsi');
75 | installScript = utils.replace(installScript, {
76 | name: manifest.name,
77 | productName: manifest.productName,
78 | version: manifest.version,
79 | src: readyAppDir.path(),
80 | dest: releasesDir.path(finalPackageName),
81 | icon: readyAppDir.path('icon.ico'),
82 | setupIcon: projectDir.path('resources/windows/setup-icon.ico'),
83 | banner: projectDir.path('resources/windows/setup-banner.bmp'),
84 | });
85 | tmpDir.write('installer.nsi', installScript);
86 |
87 | gulpUtil.log('Building installer with NSIS...');
88 |
89 | // Remove destination file if already exists.
90 | releasesDir.remove(finalPackageName);
91 |
92 | // Note: NSIS have to be added to PATH (environment variables).
93 | var nsis = childProcess.spawn('makensis', [
94 | tmpDir.path('installer.nsi')
95 | ], {
96 | stdio: 'inherit'
97 | });
98 | nsis.on('error', function (err) {
99 | if (err.message === 'spawn makensis ENOENT') {
100 | throw "Can't find NSIS. Are you sure you've installed it and"
101 | + " added to PATH environment variable?";
102 | } else {
103 | throw err;
104 | }
105 | });
106 | nsis.on('close', function () {
107 | gulpUtil.log('Installer ready!', releasesDir.path(finalPackageName));
108 | deferred.resolve();
109 | });
110 |
111 | return deferred.promise;
112 | };
113 |
114 | var cleanClutter = function () {
115 | return tmpDir.removeAsync('.');
116 | };
117 |
118 | module.exports = function () {
119 | return init()
120 | .then(copyRuntime)
121 | .then(cleanupRuntime)
122 | .then(packageBuiltApp)
123 | .then(finalize)
124 | .then(renameApp)
125 | .then(createInstaller)
126 | .then(cleanClutter)
127 | .catch(console.error);
128 | };
129 |
--------------------------------------------------------------------------------
/resources/windows/installer.nsi:
--------------------------------------------------------------------------------
1 | ; NSIS packaging/install script
2 | ; Docs: http://nsis.sourceforge.net/Docs/Contents.html
3 |
4 | !include LogicLib.nsh
5 | !include nsDialogs.nsh
6 |
7 | ; --------------------------------
8 | ; Variables
9 | ; --------------------------------
10 |
11 | !define dest "{{dest}}"
12 | !define src "{{src}}"
13 | !define name "{{name}}"
14 | !define productName "{{productName}}"
15 | !define version "{{version}}"
16 | !define icon "{{icon}}"
17 | !define setupIcon "{{setupIcon}}"
18 | !define banner "{{banner}}"
19 |
20 | !define exec "{{productName}}.exe"
21 |
22 | !define regkey "Software\${productName}"
23 | !define uninstkey "Software\Microsoft\Windows\CurrentVersion\Uninstall\${productName}"
24 |
25 | !define uninstaller "uninstall.exe"
26 |
27 | ; --------------------------------
28 | ; Installation
29 | ; --------------------------------
30 |
31 | Unicode true
32 | SetCompressor /SOLID lzma
33 |
34 | Name "${productName}"
35 | Icon "${setupIcon}"
36 | OutFile "${dest}"
37 | InstallDir "$PROGRAMFILES\${productName}"
38 | InstallDirRegKey HKLM "${regkey}" ""
39 |
40 | RequestExecutionLevel admin
41 | CRCCheck on
42 | SilentInstall normal
43 |
44 | XPStyle on
45 | ShowInstDetails nevershow
46 | AutoCloseWindow false
47 | WindowIcon off
48 |
49 | Caption "${productName} Setup"
50 | ; Don't add sub-captions to title bar
51 | SubCaption 3 " "
52 | SubCaption 4 " "
53 |
54 | Page custom welcome
55 | Page instfiles
56 |
57 | Var Image
58 | Var ImageHandle
59 |
60 | Function .onInit
61 |
62 | ; Extract banner image for welcome page
63 | InitPluginsDir
64 | ReserveFile "${banner}"
65 | File /oname=$PLUGINSDIR\banner.bmp "${banner}"
66 |
67 | FunctionEnd
68 |
69 | ; Custom welcome page
70 | Function welcome
71 |
72 | nsDialogs::Create 1018
73 |
74 | ${NSD_CreateLabel} 185 1u 210 100% "Welcome to ${productName} version ${version} installer.$\r$\n$\r$\nClick install to begin."
75 |
76 | ${NSD_CreateBitmap} 0 0 170 210 ""
77 | Pop $Image
78 | ${NSD_SetImage} $Image $PLUGINSDIR\banner.bmp $ImageHandle
79 |
80 | nsDialogs::Show
81 |
82 | ${NSD_FreeImage} $ImageHandle
83 |
84 | FunctionEnd
85 |
86 | ; Installation declarations
87 | Section "Install"
88 |
89 | WriteRegStr HKLM "${regkey}" "Install_Dir" "$INSTDIR"
90 | WriteRegStr HKLM "${uninstkey}" "DisplayName" "${productName}"
91 | WriteRegStr HKLM "${uninstkey}" "DisplayIcon" '"$INSTDIR\icon.ico"'
92 | WriteRegStr HKLM "${uninstkey}" "UninstallString" '"$INSTDIR\${uninstaller}"'
93 |
94 | ; Remove all application files copied by previous installation
95 | RMDir /r "$INSTDIR"
96 |
97 | SetOutPath $INSTDIR
98 |
99 | ; Include all files from /build directory
100 | File /r "${src}\*"
101 |
102 | ; Create start menu shortcut
103 | CreateShortCut "$SMPROGRAMS\${productName}.lnk" "$INSTDIR\${exec}" "" "$INSTDIR\icon.ico"
104 |
105 | WriteUninstaller "${uninstaller}"
106 |
107 | SectionEnd
108 |
109 | ; --------------------------------
110 | ; Uninstaller
111 | ; --------------------------------
112 |
113 | ShowUninstDetails nevershow
114 |
115 | UninstallCaption "Uninstall ${productName}"
116 | UninstallText "Don't like ${productName} anymore? Hit uninstall button."
117 | UninstallIcon "${icon}"
118 |
119 | UninstPage custom un.confirm un.confirmOnLeave
120 | UninstPage instfiles
121 |
122 | Var RemoveAppDataCheckbox
123 | Var RemoveAppDataCheckbox_State
124 |
125 | ; Custom uninstall confirm page
126 | Function un.confirm
127 |
128 | nsDialogs::Create 1018
129 |
130 | ${NSD_CreateLabel} 1u 1u 100% 24u "If you really want to remove ${productName} from your computer press uninstall button."
131 |
132 | ${NSD_CreateCheckbox} 1u 35u 100% 10u "Remove also my ${productName} personal data"
133 | Pop $RemoveAppDataCheckbox
134 |
135 | nsDialogs::Show
136 |
137 | FunctionEnd
138 |
139 | Function un.confirmOnLeave
140 |
141 | ; Save checkbox state on page leave
142 | ${NSD_GetState} $RemoveAppDataCheckbox $RemoveAppDataCheckbox_State
143 |
144 | FunctionEnd
145 |
146 | ; Uninstall declarations
147 | Section "Uninstall"
148 |
149 | DeleteRegKey HKLM "${uninstkey}"
150 | DeleteRegKey HKLM "${regkey}"
151 |
152 | Delete "$SMPROGRAMS\${productName}.lnk"
153 |
154 | ; Remove whole directory from Program Files
155 | RMDir /r "$INSTDIR"
156 |
157 | ; Remove also appData directory generated by your app if user checked this option
158 | ${If} $RemoveAppDataCheckbox_State == ${BST_CHECKED}
159 | RMDir /r "$APPDATA\${productName}"
160 | ${EndIf}
161 |
162 | SectionEnd
163 |
--------------------------------------------------------------------------------
/app/render/css/main.less:
--------------------------------------------------------------------------------
1 | @sidebar-bg-color: #f5f5f5;
2 | @sidebar-width: 250px;
3 | @page-top-padding: 50px;
4 | @border-color: #bbbbbb;
5 | @highlight-color: yellow;
6 | @sidebar-font-color: black;
7 |
8 | .transition(@transition) {
9 | -webkit-transition: @transition;
10 | -o-transition: @transition;
11 | transition: @transition;
12 | }
13 | .animation(@animation) {
14 | -webkit-animation: @animation;
15 | -o-animation: @animation;
16 | animation: @animation;
17 | }
18 | .vertical-gradient(@start-color: #F7F7F7; @end-color: #E3E3E3) {
19 | background-image: -webkit-linear-gradient(top, @start-color, @end-color);
20 | background-image: -o-linear-gradient(top, @start-color, @end-color);
21 | background-image: linear-gradient(to bottom, @start-color, @end-color);
22 | background-repeat: repeat-x;
23 | }
24 | body { padding-top: @page-top-padding; }
25 | nav.top-bar {
26 | .vertical-gradient(#FAFAFA, #F0F0F0);
27 | border-bottom: 1px solid @border-color;
28 | .add-form {
29 | .input-group {
30 | input{
31 | width: 300px;
32 | }
33 | }
34 | }
35 | }
36 | div#sidebar{
37 | position: fixed;
38 | top: 0;
39 | bottom: 0;
40 | color: @sidebar-font-color;
41 | width: @sidebar-width;
42 | font-family: Lucida Grande;
43 | white-space: nowrap;
44 | background: @sidebar-bg-color;
45 | border-right: 1px solid @border-color;
46 |
47 | // search input
48 | .readme-list-search-wrapper {
49 | padding: 10px 5px 10px 5px;
50 | .vertical-gradient(#F7F7F7, #E3E3E3);
51 | border-bottom: 1px solid @border-color;
52 | border-top: 1px solid @border-color;
53 | }
54 |
55 | // readme list
56 | .readme-nav {
57 | font-size: 13.5px;
58 | padding-top: @page-top-padding;
59 | .readme-nav-list {
60 | padding: 5px 0 5px 0;
61 | overflow-y: auto;
62 | overflow-x: hidden;
63 | list-style-type: none;
64 | .readme-nav-list-item {
65 | &:hover {
66 | background-color: #cecece;
67 | }
68 | &.active {
69 | background-color: #afafaf;
70 | }
71 | a {
72 | display: block;
73 | color: black;
74 | padding: 1px 0 1px 10px;
75 | text-decoration: none;
76 | }
77 | .readme-item-name {
78 | padding-left: 4px;
79 | }
80 | }
81 | }
82 | }
83 | // hand bar
84 | .handbar {
85 | padding: 0 8px;
86 | .vertical-gradient(#F7F7F7, #CCCCCC);
87 | border-top: 1px solid @border-color;
88 | border-bottom: 1px solid @border-color;
89 | }
90 | // content navigator
91 | .content-nav {
92 | position: fixed;
93 | bottom: 0;
94 | width: @sidebar-width;
95 | font-size: 13px;
96 | background-color: @sidebar-bg-color;
97 | border-right: 1px solid @border-color;
98 | .content-nav-list {
99 | height: 300px;
100 | margin: 0 0 16px 0;
101 | padding: 0;
102 | overflow-y: auto;
103 | overflow-x: hidden;
104 | list-style: none;
105 | .content-nav-list-item {
106 | padding: 3px 0 3px 10px;
107 | &:nth-child(2n){
108 | background-color: #FFFFFF;
109 | }
110 | &:hover{
111 | background-color: #cecece;
112 | }
113 | &.active{
114 | background-color: #AC9DF2;
115 | }
116 | a {
117 | display: block;
118 | text-decoration: none;
119 | color: black;
120 | }
121 | }
122 | }
123 | }
124 | }
125 | /* Content */
126 | div#content {
127 | min-width: 500px;
128 | margin: @page-top-padding 20px 20px 270px;
129 | }
130 |
131 | .highlighted { background: yellow }
132 |
133 | .download-progress {
134 | width: 100%;
135 | .transition(width .6s ease);
136 | &.active{
137 | .animation(download-progress-stripes 2s linear infinite);
138 | background-image: -webkit-linear-gradient(45deg, rgba(231, 231, 231, 0.45) 25%,transparent 25%,transparent 50%, rgba(231, 231, 231, 0.45) 50%, rgba(231, 231, 231, 0.45) 75%,transparent 75%,transparent);
139 | background-image: linear-gradient(45deg, rgba(231, 231, 231, 0.45) 25%,transparent 25%,transparent 50%, rgba(231, 231, 231, 0.45) 50%, rgba(231, 231, 231, 0.45) 75%,transparent 75%,transparent);
140 | -webkit-background-size: 40px 40px;
141 | background-size: 40px 40px;
142 | }
143 | }
144 | @-webkit-keyframes download-progress-stripes {
145 | from { background-position: 40px 0; }
146 | to { background-position: 0 0; }
147 | }
148 | @keyframes download-progress-stripes {
149 | from { background-position: 40px 0; }
150 | to { background-position: 0 0; }
151 | }
152 |
--------------------------------------------------------------------------------
/app/render/css/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 50px;
3 | }
4 | nav.top-bar {
5 | background-image: -webkit-linear-gradient(top, #fafafa, #f0f0f0);
6 | background-image: -o-linear-gradient(top, #fafafa, #f0f0f0);
7 | background-image: linear-gradient(to bottom, #fafafa, #f0f0f0);
8 | background-repeat: repeat-x;
9 | border-bottom: 1px solid #bbbbbb;
10 | }
11 | nav.top-bar .add-form .input-group input {
12 | width: 300px;
13 | }
14 | div#sidebar {
15 | position: fixed;
16 | top: 0;
17 | bottom: 0;
18 | color: #000000;
19 | width: 250px;
20 | font-family: Lucida Grande;
21 | white-space: nowrap;
22 | background: #f5f5f5;
23 | border-right: 1px solid #bbbbbb;
24 | }
25 | div#sidebar .readme-list-search-wrapper {
26 | padding: 10px 5px 10px 5px;
27 | background-image: -webkit-linear-gradient(top, #f7f7f7, #e3e3e3);
28 | background-image: -o-linear-gradient(top, #f7f7f7, #e3e3e3);
29 | background-image: linear-gradient(to bottom, #f7f7f7, #e3e3e3);
30 | background-repeat: repeat-x;
31 | border-bottom: 1px solid #bbbbbb;
32 | border-top: 1px solid #bbbbbb;
33 | }
34 | div#sidebar .readme-nav {
35 | font-size: 13.5px;
36 | padding-top: 50px;
37 | }
38 | div#sidebar .readme-nav .readme-nav-list {
39 | padding: 5px 0 5px 0;
40 | overflow-y: auto;
41 | overflow-x: hidden;
42 | list-style-type: none;
43 | }
44 | div#sidebar .readme-nav .readme-nav-list .readme-nav-list-item:hover {
45 | background-color: #cecece;
46 | }
47 | div#sidebar .readme-nav .readme-nav-list .readme-nav-list-item.active {
48 | background-color: #afafaf;
49 | }
50 | div#sidebar .readme-nav .readme-nav-list .readme-nav-list-item a {
51 | display: block;
52 | color: black;
53 | padding: 1px 0 1px 10px;
54 | text-decoration: none;
55 | }
56 | div#sidebar .readme-nav .readme-nav-list .readme-nav-list-item .readme-item-name {
57 | padding-left: 4px;
58 | }
59 | div#sidebar .handbar {
60 | padding: 0 8px;
61 | background-image: -webkit-linear-gradient(top, #f7f7f7, #cccccc);
62 | background-image: -o-linear-gradient(top, #f7f7f7, #cccccc);
63 | background-image: linear-gradient(to bottom, #f7f7f7, #cccccc);
64 | background-repeat: repeat-x;
65 | border-top: 1px solid #bbbbbb;
66 | border-bottom: 1px solid #bbbbbb;
67 | }
68 | div#sidebar .content-nav {
69 | position: fixed;
70 | bottom: 0;
71 | width: 250px;
72 | font-size: 13px;
73 | background-color: #f5f5f5;
74 | border-right: 1px solid #bbbbbb;
75 | }
76 | div#sidebar .content-nav .content-nav-list {
77 | height: 300px;
78 | margin: 0 0 16px 0;
79 | padding: 0;
80 | overflow-y: auto;
81 | overflow-x: hidden;
82 | list-style: none;
83 | }
84 | div#sidebar .content-nav .content-nav-list .content-nav-list-item {
85 | padding: 3px 0 3px 10px;
86 | }
87 | div#sidebar .content-nav .content-nav-list .content-nav-list-item:nth-child(2n) {
88 | background-color: #FFFFFF;
89 | }
90 | div#sidebar .content-nav .content-nav-list .content-nav-list-item:hover {
91 | background-color: #cecece;
92 | }
93 | div#sidebar .content-nav .content-nav-list .content-nav-list-item.active {
94 | background-color: #AC9DF2;
95 | }
96 | div#sidebar .content-nav .content-nav-list .content-nav-list-item a {
97 | display: block;
98 | text-decoration: none;
99 | color: black;
100 | }
101 | /* Content */
102 | div#content {
103 | min-width: 500px;
104 | margin: 50px 20px 20px 270px;
105 | }
106 | .highlighted {
107 | background: #ffff00;
108 | }
109 | .download-progress {
110 | width: 100%;
111 | -webkit-transition: width 0.6s ease;
112 | -o-transition: width 0.6s ease;
113 | transition: width 0.6s ease;
114 | }
115 | .download-progress.active {
116 | -webkit-animation: download-progress-stripes 2s linear infinite;
117 | -o-animation: download-progress-stripes 2s linear infinite;
118 | animation: download-progress-stripes 2s linear infinite;
119 | background-image: -webkit-linear-gradient(45deg, rgba(231, 231, 231, 0.45) 25%, transparent 25%, transparent 50%, rgba(231, 231, 231, 0.45) 50%, rgba(231, 231, 231, 0.45) 75%, transparent 75%, transparent);
120 | background-image: linear-gradient(45deg, rgba(231, 231, 231, 0.45) 25%, transparent 25%, transparent 50%, rgba(231, 231, 231, 0.45) 50%, rgba(231, 231, 231, 0.45) 75%, transparent 75%, transparent);
121 | -webkit-background-size: 40px 40px;
122 | background-size: 40px 40px;
123 | }
124 | @-webkit-keyframes download-progress-stripes {
125 | from {
126 | background-position: 40px 0;
127 | }
128 | to {
129 | background-position: 0 0;
130 | }
131 | }
132 | @keyframes download-progress-stripes {
133 | from {
134 | background-position: 40px 0;
135 | }
136 | to {
137 | background-position: 0 0;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/app/main.js:
--------------------------------------------------------------------------------
1 | var electron = require("electron");
2 | var app = electron.app;
3 | var BrowserWindow = electron.BrowserWindow;
4 | var windowStateKeeper = require('./browser/window_state');
5 |
6 | electron.crashReporter.start();
7 |
8 | var mainWindow = null;
9 |
10 | app.on("window-all-closed", function () {
11 | if (process.platform != "darwin") {
12 | app.quit();
13 | }
14 | });
15 |
16 | function createWindow() {
17 | // Preserver of the window size and position between app launches.
18 | var mainWindowState = windowStateKeeper('main', {
19 | width: 1000,
20 | height: 600
21 | });
22 |
23 | mainWindow = new BrowserWindow({
24 | title: "GReader",
25 | x: mainWindowState.x,
26 | y: mainWindowState.y,
27 | width: mainWindowState.width,
28 | height: mainWindowState.height,
29 | "min-width": 800,
30 | "min-height": 300
31 | });
32 |
33 | if (mainWindowState.isMaximized) {
34 | mainWindow.maximize();
35 | }
36 |
37 | mainWindow.loadUrl("file://" + __dirname + "/render/index.html");
38 |
39 | // save state before close
40 | mainWindow.on('close', function () {
41 | mainWindowState.saveState(mainWindow);
42 | });
43 |
44 | // defer mainWindow
45 | mainWindow.on("closed", function () {
46 | mainWindow = null;
47 | });
48 |
49 | // open url in default browser
50 | mainWindow.webContents.on("will-navigate", function (event, url) {
51 | if(!url.startsWith("file://")) {
52 | event.preventDefault();
53 | require("shell").openExternal(url);
54 | }
55 | });
56 |
57 | var template = [
58 | {
59 | label: 'ReadmeReader',
60 | submenu: [
61 | {
62 | label: 'Services',
63 | submenu: []
64 | },
65 | {
66 | type: 'separator'
67 | },
68 | {
69 | label: 'Hide',
70 | accelerator: 'Command+H',
71 | selector: 'hide:'
72 | },
73 | {
74 | label: 'Hide Others',
75 | accelerator: 'Command+Shift+H',
76 | selector: 'hideOtherApplications:'
77 | },
78 | {
79 | label: 'Show All',
80 | selector: 'unhideAllApplications:'
81 | },
82 | {
83 | type: 'separator'
84 | },
85 | {
86 | label: 'Quit',
87 | accelerator: 'Command+Q',
88 | click: function() { app.quit(); }
89 | }
90 | ]
91 | },
92 | {
93 | label: 'Edit',
94 | submenu: [
95 | {
96 | label: 'Undo',
97 | accelerator: 'Command+Z',
98 | selector: 'undo:'
99 | },
100 | {
101 | label: 'Redo',
102 | accelerator: 'Shift+Command+Z',
103 | selector: 'redo:'
104 | },
105 | {
106 | type: 'separator'
107 | },
108 | {
109 | label: 'Cut',
110 | accelerator: 'Command+X',
111 | selector: 'cut:'
112 | },
113 | {
114 | label: 'Copy',
115 | accelerator: 'Command+C',
116 | selector: 'copy:'
117 | },
118 | {
119 | label: 'Paste',
120 | accelerator: 'Command+V',
121 | selector: 'paste:'
122 | },
123 | {
124 | label: 'Select All',
125 | accelerator: 'Command+A',
126 | selector: 'selectAll:'
127 | }
128 | ]
129 | },
130 | {
131 | label: 'View',
132 | submenu: [
133 | {
134 | label: 'Reload',
135 | accelerator: 'Command+R',
136 | click: function() { BrowserWindow.getFocusedWindow().reloadIgnoringCache(); }
137 | },
138 | {
139 | label: 'Toggle DevTools',
140 | accelerator: 'Alt+Command+I',
141 | click: function() { BrowserWindow.getFocusedWindow().toggleDevTools(); }
142 | }
143 | ]
144 | },
145 | {
146 | label: 'Window',
147 | submenu: [
148 | {
149 | label: 'Minimize',
150 | accelerator: 'Command+M',
151 | selector: 'performMiniaturize:'
152 | },
153 | {
154 | label: 'Close',
155 | accelerator: 'Command+W',
156 | selector: 'performClose:'
157 | },
158 | {
159 | type: 'separator'
160 | },
161 | {
162 | label: 'Bring All to Front',
163 | selector: 'arrangeInFront:'
164 | }
165 | ]
166 | },
167 | {
168 | label: 'Help',
169 | submenu: []
170 | }
171 | ];
172 | var Menu = require("menu");
173 | var menu = Menu.buildFromTemplate(template);
174 | Menu.setApplicationMenu(menu);
175 | }
176 |
177 |
178 | app.on("ready", function () {
179 | createWindow();
180 | });
181 |
182 | app.on("activate-with-no-open-windows", function () {
183 | createWindow();
184 | });
185 |
--------------------------------------------------------------------------------
/tasks/release_osx.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Q = require('q');
4 | var gulpUtil = require('gulp-util');
5 | var jetpack = require('fs-jetpack');
6 | var asar = require('asar');
7 | var utils = require('./utils');
8 | var child_process = require('child_process');
9 |
10 | var projectDir;
11 | var releasesDir;
12 | var tmpDir;
13 | var finalAppDir;
14 | var manifest;
15 |
16 | var init = function () {
17 | projectDir = jetpack;
18 | tmpDir = projectDir.dir('./tmp', { empty: true });
19 | releasesDir = projectDir.dir('./releases');
20 | manifest = projectDir.read('app/package.json', 'json');
21 | finalAppDir = tmpDir.cwd(manifest.productName + '.app');
22 |
23 | return Q();
24 | };
25 |
26 | var copyRuntime = function () {
27 | return projectDir.copyAsync('node_modules/electron-prebuilt/dist/Electron.app', finalAppDir.path());
28 | };
29 |
30 | var cleanupRuntime = function() {
31 | finalAppDir.remove('Contents/Resources/default_app');
32 | finalAppDir.remove('Contents/Resources/atom.icns');
33 | return Q();
34 | }
35 |
36 | var packageBuiltApp = function () {
37 | var deferred = Q.defer();
38 |
39 | asar.createPackage(projectDir.path('build'), finalAppDir.path('Contents/Resources/app.asar'), function() {
40 | deferred.resolve();
41 | });
42 |
43 | return deferred.promise;
44 | };
45 |
46 | var finalize = function () {
47 | // Prepare main Info.plist
48 | var info = projectDir.read('resources/osx/Info.plist');
49 | info = utils.replace(info, {
50 | productName: manifest.productName,
51 | identifier: manifest.identifier,
52 | version: manifest.version
53 | });
54 | finalAppDir.write('Contents/Info.plist', info);
55 |
56 | // Prepare Info.plist of Helper apps
57 | [' EH', ' NP', ''].forEach(function (helper_suffix) {
58 | info = projectDir.read('resources/osx/helper_apps/Info' + helper_suffix + '.plist');
59 | info = utils.replace(info, {
60 | productName: manifest.productName,
61 | identifier: manifest.identifier
62 | });
63 | finalAppDir.write('Contents/Frameworks/Electron Helper' + helper_suffix + '.app/Contents/Info.plist', info);
64 | });
65 |
66 | // Copy icon
67 | projectDir.copy('resources/osx/icon.icns', finalAppDir.path('Contents/Resources/icon.icns'));
68 |
69 | return Q();
70 | };
71 |
72 | var renameApp = function() {
73 | // Rename helpers
74 | [' Helper EH', ' Helper NP', ' Helper'].forEach(function (helper_suffix) {
75 | finalAppDir.rename('Contents/Frameworks/Electron' + helper_suffix + '.app/Contents/MacOS/Electron' + helper_suffix, manifest.productName + helper_suffix );
76 | finalAppDir.rename('Contents/Frameworks/Electron' + helper_suffix + '.app', manifest.productName + helper_suffix + '.app');
77 | });
78 | // Rename application
79 | finalAppDir.rename('Contents/MacOS/Electron', manifest.productName);
80 | return Q();
81 | }
82 |
83 | var signApp = function () {
84 | var identity = utils.getSigningId();
85 | if (identity) {
86 | var cmd = 'codesign --deep --force --sign "' + identity + '" "' + finalAppDir.path() + '"';
87 | gulpUtil.log('Signing with:', cmd);
88 | return Q.nfcall(child_process.exec, cmd);
89 | } else {
90 | return Q();
91 | }
92 | }
93 |
94 | var packToDmgFile = function () {
95 | var deferred = Q.defer();
96 |
97 | var appdmg = require('appdmg');
98 | var dmgName = manifest.name + '_' + manifest.version + '.dmg';
99 |
100 | // Prepare appdmg config
101 | var dmgManifest = projectDir.read('resources/osx/appdmg.json');
102 | dmgManifest = utils.replace(dmgManifest, {
103 | productName: manifest.productName,
104 | appPath: finalAppDir.path(),
105 | dmgIcon: projectDir.path("resources/osx/dmg-icon.icns"),
106 | dmgBackground: projectDir.path("resources/osx/dmg-background.png")
107 | });
108 | tmpDir.write('appdmg.json', dmgManifest);
109 |
110 | // Delete DMG file with this name if already exists
111 | releasesDir.remove(dmgName);
112 |
113 | gulpUtil.log('Packaging to DMG file...');
114 |
115 | var readyDmgPath = releasesDir.path(dmgName);
116 | appdmg({
117 | source: tmpDir.path('appdmg.json'),
118 | target: readyDmgPath
119 | })
120 | .on('error', function (err) {
121 | console.error(err);
122 | })
123 | .on('finish', function () {
124 | gulpUtil.log('DMG file ready!', readyDmgPath);
125 | deferred.resolve();
126 | });
127 |
128 | return deferred.promise;
129 | };
130 |
131 | var cleanClutter = function () {
132 | return tmpDir.removeAsync('.');
133 | };
134 |
135 | module.exports = function () {
136 | return init()
137 | .then(copyRuntime)
138 | .then(cleanupRuntime)
139 | .then(packageBuiltApp)
140 | .then(finalize)
141 | .then(renameApp)
142 | .then(signApp)
143 | .then(packToDmgFile)
144 | .then(cleanClutter)
145 | .catch(console.error);
146 | };
147 |
--------------------------------------------------------------------------------
/app/render/app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var app = angular.module("GithubReadmeReader", ["ui.router"]);
4 |
5 | app.controller("TableContentsNavCtrl", ["$scope", "$rootScope", "readmeList", function ($scope, $rootScope, readmeList) {
6 | $scope.readmeNavList = [];
7 | $scope.contentNavList = [];
8 |
9 | function updateReadmeList() {
10 | $scope.readmeList = readmeList.list();
11 | var list = [];
12 | $scope.readmeList.forEach(function (readme) {
13 | var name = readme["full_name"].replace(/\.|\//g, '_');
14 | list.push({urlName: name, displayName: readme["full_name"], url: readme["html_url"]});
15 | });
16 | $scope.readmeNavList = list;
17 | }
18 | $scope.$on("readmeListUpdateEvent", function () {
19 | updateReadmeList();
20 | $scope.$digest(); // update UI . why?
21 | });
22 |
23 | $rootScope.$on("$viewContentLoaded", function () {
24 | var anchors = $("a[id^=user-content-].anchor");
25 | var list = [];
26 | anchors.each(function () {
27 | var $this = $(this);
28 | list.push({ name: $this.parent().text().trim(), anchor: $this.attr("id")});
29 | });
30 | $scope.contentNavList = list;
31 | });
32 |
33 | $scope.openReadme = function (e) {
34 | $(e.target).parents("li").siblings().removeClass("active");
35 | $(e.target).parents("li").addClass("active");
36 | };
37 |
38 | $scope.gotoContent = function (anchor, e) {
39 | var target = $("a[id="+ anchor +"]")[0];
40 | $('html, body').animate({ scrollTop: $(target).offset().top - 50},'fast');
41 | $(e.target).parents("li").siblings().removeClass("active");
42 | $(e.target).parents("li").addClass("active");
43 | };
44 |
45 | updateReadmeList();
46 | }]);
47 |
48 | app.controller("NavCtrl", ["$scope", "$rootScope", "$sce", "readmeList", function ($scope, $rootScope, $sce, readmeList) {
49 | $scope.currentRepo = "Welcome";
50 | $scope.repoUrl = "";
51 | $scope.downloading = false;
52 | $scope.add = function (repoUrl) {
53 | var paths = repoUrl.split("/");
54 | var user = paths[paths.length - 2];
55 | var name = paths[paths.length - 1];
56 | if (!user || !name) {
57 | return console.log("Invalid url");
58 | }
59 | $scope.downloading = true;
60 | $(".download-progress").addClass("active");
61 | readmeList.add({user: user, name: name}).then(function () {
62 | $scope.downloading = false;
63 | $scope.repoUrl = "";
64 | $(".download-progress").removeClass("active");
65 | $scope.$digest();
66 | }).catch(function (err) {
67 | $scope.downloading = false;
68 | $(".download-progress").removeClass("active");
69 | console.log(err);
70 | $scope.$digest();
71 | });
72 | };
73 |
74 | $rootScope.$on('$stateChangeSuccess',function(event, toState, toParams, fromState, fromParams){
75 | $scope.currentRepo = toParams;
76 | });
77 | }]);
78 |
79 | app.config(['$stateProvider', "$urlRouterProvider", function ($stateProvider, $urlRouterProvider) {
80 | $stateProvider.state("home", {
81 | url:"",
82 | templateUrl: "partials/welcome.html"
83 | }).state("page", {
84 | url: "/readme/:id/:display?url",
85 | templateUrl: function ($stateParams) {
86 | var appDataPath = require("remote").require("app").getPath("userData");
87 | return appDataPath + "/" + $stateParams.id + ".html";
88 | }
89 | }).state("notfound", {
90 | url: "/404",
91 | templateUrl: "partials/404.html"
92 | });
93 | $urlRouterProvider.otherwise("/notfound");
94 |
95 | }]);
96 |
97 | app.provider("readmeList", [function () {
98 | var readmeList = [];
99 | function convertToObject(columnNames, values) {
100 | var list = [];
101 | columnNames = columnNames || [];
102 | values = values || [];
103 | values.forEach(function (repo) {
104 | var obj = {};
105 | repo.forEach(function (value, i) {
106 | obj[columnNames[i]] = value;
107 | });
108 | list.push(obj);
109 | });
110 |
111 | return list;
112 | }
113 | var DB = require("./db.js");
114 | var db = new DB(window.env);
115 | var connect = null;
116 |
117 | var initialized = false;
118 | this.init = function () {
119 | connect = db.init();
120 | try {
121 | var contents = connect.exec("SELECT * FROM " + window.env.tableName);
122 | if (contents.length > 0) {
123 | readmeList = convertToObject(contents[0].columns, contents[0].values);
124 | }
125 | initialized = true;
126 | } catch(e) {
127 | console.log(e);
128 | }
129 | };
130 |
131 | this.refresh = function () {
132 | var contents = connect.exec("SELECT * FROM " + window.env.tableName);
133 | if (contents.length > 0) {
134 | readmeList = convertToObject(contents[0].columns, contents[0].values);
135 | }
136 | };
137 |
138 | var self = this;
139 | this.readmeList = function () {
140 | if (!initialized) {
141 | self.init();
142 | }
143 | return readmeList;
144 | };
145 |
146 | function addRepo(repo) {
147 | return db.add(repo);
148 | }
149 |
150 | this.$get = ["$rootScope", function ($rootScope) {
151 | return {
152 | list: function () {
153 | return self.readmeList();
154 | },
155 | add: function (repo) {
156 | return addRepo(repo).then(function () {
157 | self.refresh();
158 | $rootScope.$broadcast("readmeListUpdateEvent");
159 | });
160 | }
161 | };
162 | }];
163 | }]);
164 |
165 | app.filter('highlight', ["$sce", function($sce) {
166 | return function(text, phrase) {
167 | if (phrase) text = text.replace(new RegExp('('+phrase+')', 'gi'),
168 | '$1');
169 | return $sce.trustAsHtml(text)
170 | }
171 | }]);
172 |
173 | app.filter("decodeURL", ["$window", function ($window) {
174 | return $window.decodeURIComponent;
175 | }]);
176 |
177 |
178 | app.filter("stringTrunc", ["$filter", function ($filter) {
179 | return function (str, n, r, w) {
180 | r = r || "...";
181 | n = n || str.length;
182 | var count = 0, returnStr = "";
183 | if (!w) {
184 | returnStr = $filter('limitTo')(str, n);
185 | if (returnStr.length < str) return returnStr + r;
186 | }
187 | var words = str.split(" ");
188 | angular.forEach(words, function (word) {
189 | if (count + word.length > n) return returnStr + r;
190 | returnStr += word + " ";
191 | count = returnStr.length;
192 | });
193 | if (returnStr.length < str.length ) returnStr += r;
194 |
195 | return returnStr;
196 | };
197 | }]);
198 |
199 | app.run(["$rootScope", function ($rootScope) {
200 | $rootScope.$on("$viewContentLoaded", function () {
201 | // fix anchors in readme file
202 | $('a[href^=#]').each(function () {
203 | var $this = $(this);
204 | var name = $this.attr("href").replace("#", "");
205 | if (name.indexOf('/') > -1) return; // not anchors
206 | var target = $("a[id=user-content-" + name +"]")[0] || $("a[name=user-content-" + name +"]")[0];
207 | if (target) { // only add event when it's a anchor and has target
208 | $this.on("click", function () {
209 | $('html, body').animate({ scrollTop: $(target).offset().top - 50},'fast');
210 | });
211 | }
212 | });
213 | });
214 | }]);
215 |
--------------------------------------------------------------------------------