1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 02:06:29 +00:00

Compare commits

...

123 Commits

Author SHA1 Message Date
Dick Choi
a7a499a2b1 fix authenticode 2015-12-25 04:27:36 +09:00
Rokt33r
b646313b58 bump up version to v0.5.0 2015-12-25 04:27:36 +09:00
Rokt33r
f3ce4ca803 no zip for windows & must compile before release app 2015-12-25 04:27:36 +09:00
Rokt33r
93d99c0c47 fix wrong calling of updater 2015-12-25 04:27:36 +09:00
Dick Choi
ae1fc7572a add quit option to menu & fix sudden app quit caused by finder-window 2015-12-25 04:27:36 +09:00
Dick Choi
1a527cca10 set appdmg package optional 2015-12-25 04:27:36 +09:00
Rokt33r
c625513924 Add tooltip to editmode buttons 2015-12-25 04:27:35 +09:00
Rokt33r
3f58302a14 move intialize code from 'did-finish-load' 2015-12-25 04:27:35 +09:00
Rokt33r
63b199c9c2 scroll if trying to navigate up to out of screen 2015-12-25 04:27:35 +09:00
Rokt33r
fc64c565db cleanup codes 2015-12-25 04:27:35 +09:00
Rokt33r
91e60fa82b fix IPC bug: spawn 2 finder processes 2015-12-25 04:27:35 +09:00
Rokt33r
0cc52c2206 CommandをWindowsの場合Controlで表示する 2015-12-25 04:27:35 +09:00
Rokt33r
2ffe4ba70b clean up code 2015-12-25 04:27:35 +09:00
Rokt33r
2afd7e3687 Grunt deploy ready!!! 2015-12-25 04:24:19 +09:00
Rokt33r
a0f8d13c4f modify menu label(Boost-> Boostnote) 2015-12-21 22:41:16 +09:00
Rokt33r
2571ea021a focus previous app after hiding Finder window 2015-12-21 22:18:37 +09:00
Rokt33r
6950e05b6a set packaging for osx 2015-12-21 22:17:47 +09:00
Rokt33r
7eb767a268 IPC setup for windows & remove old updater code 2015-12-21 20:15:43 +09:00
Rokt33r
8e64abc4bc cleanup code 2015-12-20 15:47:29 +09:00
Dick Choi
52df793a74 remove remoteReleases URL 2015-12-19 14:52:29 +09:00
Dick Choi
8e44a421a2 switch npm scripts with Grunt task 2015-12-19 14:51:12 +09:00
Dick Choi
7f4ccdcac8 edit HTML title and loading picture URL 2015-12-19 13:59:24 +09:00
Dick Choi
03e8de2f62 Debug auto_update code of Windows app 2015-12-19 13:58:53 +09:00
Dick Choi
8b04eecc90 Add resources and use original logo 2015-12-19 00:55:12 +09:00
Dick Choi
16bcd86792 Refactor main process and add exception handler for Socket server 2015-12-19 00:54:11 +09:00
Dick Choi
be3c519a57 add silent option to notification 2015-12-17 11:34:50 +09:00
Dick Choi
8776cb1cea Finder behaviour for windows 2015-12-17 11:05:38 +09:00
Dick Choi
4c94503f9a variable name changed(WEB_URL -> SERVER_URL) 2015-12-16 09:18:47 +09:00
Dick Choi
48f57376d3 fix menu-template 2015-12-16 09:15:52 +09:00
Dick Choi
958469f526 set Notification icon for Windows 2015-12-15 14:00:56 +09:00
Dick Choi
2a774a7bb6 handle ctrl key as meta key when using Windows 2015-12-15 13:44:49 +09:00
Dick Choi
a872ad9d8b change IPC module(Raw buffer -> Socket) & set window behaviour for Windows 2015-12-15 13:43:28 +09:00
Dick Choi
2499a05473 Merge remote-tracking branch 'origin/master' into windows
Conflicts:
	browser/main/HomePage.js
	browser/main/HomePage/ArticleNavigator.js
	webpack.config.js
2015-12-15 13:06:01 +09:00
Rokt33r
6b66893ea4 Merge branch 'dev'
* dev:
  fix typo shareWith -> shareVia
  bump up version to 0.4.6
  Finderを開き直したら内容初期化
  ARTICLE_SHARE イベント追跡
  debug - 新規投稿が不可能
  enable copy (finder)
  modify dock.menu
  Folderの位置修正の保存
  add hot key:Navigate up(Ctrl + P) for CodeEditor
  switch API URL
  submit user name
  Url share done

Conflicts:
	package.json
2015-12-14 10:39:53 +09:00
Rokt33r
529c27aed5 fix typo shareWith -> shareVia 2015-12-13 22:46:59 +09:00
Rokt33r
70fc0afbc4 bump up version to 0.4.6 2015-12-13 19:45:03 +09:00
Rokt33r
09f81fd0d6 Finderを開き直したら内容初期化 2015-12-13 19:44:40 +09:00
Rokt33r
af7f2d4d5e ARTICLE_SHARE イベント追跡 2015-12-13 19:28:53 +09:00
Rokt33r
3bd5d6b9f6 debug - 新規投稿が不可能 2015-12-13 19:10:05 +09:00
Rokt33r
57912b5a5a enable copy (finder) 2015-12-13 18:36:31 +09:00
Rokt33r
a05f5b9737 modify dock.menu 2015-12-13 18:25:52 +09:00
Rokt33r
1963b586ac Folderの位置修正の保存 2015-12-13 17:01:36 +09:00
Rokt33r
3b9ad59849 add hot key:Navigate up(Ctrl + P) for CodeEditor 2015-12-13 16:29:45 +09:00
Rokt33r
79e0e5668d switch API URL 2015-12-13 16:29:01 +09:00
Rokt33r
0e8edf0c72 submit user name 2015-12-13 14:25:50 +09:00
Rokt33r
24e2544544 Url share done 2015-12-13 14:22:45 +09:00
Rokt33r
f3732c76ea Merge branch 'dev'
* dev:
  temporary setup
  0.4.5
  emit empty event(to create new record of today if not exists)
  MAIN_DETAIL_COPY, *_BY_SYNTAX, CLIENT_VERSION 追加
  debug ModeSelect component
  fix markdown style a little more
  Markdown styleを少し改善
  bump up version to 0.4.5 and change codesign path
  自動的にスクロールを合わせてくれる
  fix release path
  add electron-builder & modify deploy scripts
  bump up Ace editor
  Github release

Conflicts:
	package.json
2015-12-08 18:53:26 +09:00
Rokt33r
a4c72a9a86 temporary setup 2015-12-08 18:52:40 +09:00
Rokt33r
455610e586 0.4.5 2015-12-08 18:44:59 +09:00
Rokt33r
634d58b3ca emit empty event(to create new record of today if not exists) 2015-12-08 00:28:08 +09:00
Rokt33r
27bbd77e8c MAIN_DETAIL_COPY, *_BY_SYNTAX, CLIENT_VERSION 追加 2015-12-07 17:52:07 +09:00
Rokt33r
d8ae77ded7 debug ModeSelect component 2015-12-07 16:32:47 +09:00
Rokt33r
0648c04728 fix markdown style a little more 2015-12-05 19:16:04 +09:00
Rokt33r
57c26e3b4a Markdown styleを少し改善 2015-12-05 19:12:56 +09:00
Rokt33r
b03afff994 bump up version to 0.4.5 and change codesign path 2015-12-05 06:04:31 +09:00
Rokt33r
77f9e60177 自動的にスクロールを合わせてくれる 2015-12-05 05:56:53 +09:00
Rokt33r
35bb792496 fix release path 2015-12-04 08:59:17 +09:00
Rokt33r
8a87304800 add electron-builder & modify deploy scripts 2015-12-04 08:58:46 +09:00
Rokt33r
64bbe053f8 bump up Ace editor 2015-12-04 08:03:29 +09:00
Rokt33r
d3f420bf6d Github release 2015-12-04 08:03:17 +09:00
Rokt33r
7fcaaa297a Merge branch 'dev'
* dev:
  alert fix
  debug missing argument
  bump version
  rollback: setVisibleOnAllWorkspaces(true)
  FinderのActivity logをちゃんと取ってくる
  FinderのInputにLato fontが使われていない問題修正
  Search inputにRegExp operatorが入ると使えなかった問題改善
  User name change and modify style

Conflicts:
	package.json
2015-12-04 04:56:55 +09:00
Rokt33r
7c2d2044a9 alert fix 2015-12-04 04:56:04 +09:00
Rokt33r
aa32f59dc6 debug missing argument 2015-12-03 12:15:07 +09:00
Rokt33r
182af99e7c bump version 2015-12-03 12:02:29 +09:00
Rokt33r
5b520a7a81 rollback: setVisibleOnAllWorkspaces(true) 2015-12-03 12:02:21 +09:00
Rokt33r
364917c910 FinderのActivity logをちゃんと取ってくる 2015-12-03 07:59:47 +09:00
Rokt33r
ca7b9c786a FinderのInputにLato fontが使われていない問題修正 2015-12-03 07:25:35 +09:00
Rokt33r
15c2363098 Search inputにRegExp operatorが入ると使えなかった問題改善 2015-12-03 06:44:52 +09:00
Rokt33r
1a11095121 User name change and modify style 2015-12-03 05:32:10 +09:00
Rokt33r
2b384b1d15 fix updater bug 2015-12-01 02:00:18 +09:00
Rokt33r
a1d61edb9c Merge branch 'dev'
* dev:
  bump version
  Folder create modalを出したら、まっすぐName inputをFocusする
  編集警告が出ている時にCode editorがキー入力を認識する問題解決
  External link動きDebug
  add copy button
  External link用のDropdown menu追加
  コードを綺麗に
  Titleがなかったら灰色でUntitleと出す
  新規投稿 Cmd + n / Preview Cmd + P 追加
  articleのタイトルの基本タイトル追加 / 何も書かれていない時にUntitled labelをだす
  Finderのvisibile on all workspaces解除
  Searchbar tooltip changed(add exact match)
  change tray menu label(Open Finder => Open FInder window)
  Main windowの visible on all worpspace解除

Conflicts:
	package.json
2015-12-01 00:10:15 +09:00
Rokt33r
96a8687896 bump version 2015-11-30 23:11:31 +09:00
Rokt33r
0448773682 Folder create modalを出したら、まっすぐName inputをFocusする 2015-11-30 16:28:14 +09:00
Rokt33r
57998ba727 編集警告が出ている時にCode editorがキー入力を認識する問題解決 2015-11-30 16:22:10 +09:00
Rokt33r
de83447cb3 External link動きDebug 2015-11-30 12:53:46 +09:00
Rokt33r
eba19468d5 add copy button 2015-11-30 12:53:21 +09:00
Rokt33r
65c78df671 External link用のDropdown menu追加 2015-11-30 11:14:16 +09:00
Rokt33r
a7096aa89f コードを綺麗に 2015-11-30 04:28:23 +09:00
Rokt33r
15a50ef452 Titleがなかったら灰色でUntitleと出す 2015-11-30 04:17:52 +09:00
Rokt33r
04036e5c87 新規投稿 Cmd + n / Preview Cmd + P 追加 2015-11-30 03:44:58 +09:00
Rokt33r
2bbb5ef74e articleのタイトルの基本タイトル追加 / 何も書かれていない時にUntitled labelをだす 2015-11-29 18:57:49 +09:00
Rokt33r
91eb7feb3c Finderのvisibile on all workspaces解除 2015-11-29 11:08:13 +09:00
Rokt33r
978d77142c Searchbar tooltip changed(add exact match) 2015-11-29 11:05:18 +09:00
Rokt33r
e36478b9ac modify method name (api changed as electron updated) 2015-11-25 10:49:06 +09:00
Rokt33r
e1fe4dd693 change to use HTTPS for checking update 2015-11-25 09:51:33 +09:00
Rokt33r
b1ee949b1c This is a release version 2015-11-25 09:40:07 +09:00
Rokt33r
a0e5f8e97e Merge commit '80a0c59f878d899fc21b72f08eb8afeb1970f9ba'
* commit '80a0c59f878d899fc21b72f08eb8afeb1970f9ba':
  make it as prerelease
  bump up version
  MarkdownのCodeblockの行間をひろげる 
  編集中キャンセルを押しても消える情報があれば警告をだす
  データ移転バグ修正
  最初以降からはUpdaterがエラーをださない。
  Stream EPIPEエラー解決、データはこれからJSON保存
  notification デバッグ
  intercept entry point
  using ipc but not working in production
  bump up electron version 0.34 -> 0.35.1
  MarkdownでEmojiが使える
  Markdown内のコードにSyntax highlightenをいれる

Conflicts:
	main.js
2015-11-25 09:08:13 +09:00
Rokt33r
e9cfb2c4ee change tray menu label(Open Finder => Open FInder window) 2015-11-25 08:59:43 +09:00
Rokt33r
190b6edfb1 Main windowの visible on all worpspace解除 2015-11-25 08:50:56 +09:00
Rokt33r
ee4ac7371c Merge branch 'dev'
* dev:
  No node-notifier
  fix: 新しい記事を書く時に発生するバグ一体
  cleanup notification code
  Default文書修正
  開発中のものはデータを送らない
  初期記事内容修正cmd -> ctrl
  show devtool only devmode

Conflicts:
	main.js
2015-11-22 16:31:48 +09:00
Rokt33r
e731b7882d Merge branch 'dev'
* dev:
  no source map
  bump version
  hidden code
2015-11-21 06:05:17 +09:00
Rokt33r
ee91daad7e Merge branch 'dev'
* dev:
  hotfix: Edited alertが変な時に出る
2015-11-18 18:51:39 +09:00
Dick Choi
1318abd37e Windowsに合わせてUI修正(Font/Username/Key input:Command key -> Control key) 2015-11-17 09:01:25 +09:00
Dick Choi
76a031a8c9 Build環境設定 2015-11-17 09:00:08 +09:00
Dick Choi
09482ebcf3 fix wrong dependency 2015-11-17 05:36:08 +09:00
Rokt33r
67424f2d3a Merge branch 'dev'
* dev:
  bump up version 0.4.1
  記事が編集された状態で他の記事を見ようとすると警告をだす
  updateが準備できたら、再起動されるまで改めてUpdateの確認をしない
  Tag suggest
  EnterでSubmitができる - Hotkey, folder edit, folder create(preference/create new folder modal両方)
  Folderの位置を変えることができる
  Preferenceからもフォルダーの色の選択ができる。
  // filterを使うと確実にFolder名が一致するもののみを表示する
  fix style
  new folder modalにcolor select追加
  auto update確認
  FinderからCopyした時、通知を出す
  FinderにCopy to clipboard button追加
  Folder リストに articleの数をだす
  フォルダーで検索するときに in:じゃなくて /にする +バグ修正
  IntroのFinder説明変更
2015-11-16 07:03:46 +09:00
Rokt33r
61cc44cc83 Merge branch 'dev'
* dev:
  0.4.0-beta.2
  tutorial 追加
  ModeSelect
  行動データ, contact form, default articleに英語文追加, Intro fix
  Hotkey settingの時Alertで結果を出す Folder nameが長すぎ雨時のlayout崩れ解決 ArticleNavigatorの余計なスペースをなくす Default articleの誤字直し
  Cmd+Sで保存 debug craetedAt
  fix Title overflow behaviour
  0.4.0-beta.1

Conflicts:
	package.json
2015-11-11 03:09:28 +09:00
Rokt33r
59d3c6c94f 0.4.0-beta.1 2015-11-05 10:33:14 +09:00
Rokt33r
388027b731 Merge branch 'dev'
* dev:
  beta - add error alert(folder editing) - debug clear button of search input
2015-11-05 09:50:39 +09:00
Rokt33r
9758f5baa8 release version 0.4.0-alpha.7 2015-11-02 16:13:31 +09:00
Rokt33r
248262a597 Merge branch 'dev'
* dev: (43 commits)
  webpack bugfix, tooltip modified, preview button string changed(toggle Preview -> Preview / Edit)
  tooltip, tutorial追加
  ArticleListに自動scroll機能追加
  Tooltip追加、キー反応改善、Pinch2Zoom使用禁止、Webpack config debug
  編集状態でのMarkdown preview追加
  Key入力の動き改善 - Searchに内容がある時にEscを押すと内容をSearchの内容を削除する - Cmd + Fを押すとSearch inputがfocusされる
  Key入力追加
  prepare alpha.5 (remain work: MD preview, keybind)
  Going LIte
  add Team destroy
  add text:ellipsis to some labels & add member deletion confirm
  bump alpha.3
  add folder create
  add Member setting, Team setting, FolderSetting(80%) bump react-select
  bumpup alpha.2
  addTag search
  add Invalid token handler
  bumpup react (0.13.3->0.14.0), reacthmr work perfectly
  fix updater bug
  Folder indexing
  ...
2015-11-02 16:12:08 +09:00
Rokt33r
3f54eb52b2 Merge branch 'dev'
* dev:
  リアルタイム(SocketIO)実装 / Markdown style改善

Conflicts:
	package.json
2015-09-08 15:30:02 +09:00
Rokt33r
1e7415b692 fix version 0.2.11 2015-09-06 19:48:44 +09:00
Rokt33r
ff950ef28a Merge branch 'dev'
* dev:
  improve markdown style
2015-09-06 12:19:20 +09:00
Rokt33r
5fa37dbffb Merge branch 'dev'
* dev:
  version 0.2.10 - Hotkeyの設定機能 - Stylus refactor
2015-09-02 01:02:32 +09:00
Rokt33r
06a54d451c Merge branch 'dev'
* dev:
  0.2.9 API server changed, bump electron version 0.31.0
2015-08-31 01:34:15 +09:00
Rokt33r
45541a255b Merge branch 'dev'
* dev:
  - StylusでコンパイルされたCSSをCachingする(ロディングが短くなる) - Planet name changeのときにエラーハンドリング追加 + Bug fix - TeamのMemberを編集する場合、自分を編集することはできない - FinderにMarkdownのリンクがちゃんと外部に飛ぶように - Tray iconがちゃんと表示 - ArticleDetailのCodeアイコンがちゃんと表示されない
2015-08-30 05:31:13 +09:00
Rokt33r
345d7b427a Merge branch 'dev'
* dev:
  Loading font 微調整
2015-08-26 18:27:42 +09:00
Rokt33r
b2845e2284 Merge branch 'dev'
* dev:
  version 0.2.7  - Planet, Team作成の時Error message表示  - MarkdownのCode blockの背景を薄い灰色にする  - 権限なしのPlanetには  - SignUpに規約/Privacyの外部リンク追加  - Loading画面追加  - Font 添付(Lato regular)  - UserContainerでのTeam Label変更
2015-08-26 18:23:04 +09:00
Rokt33r
4bda84d69c cleanup old files 2015-08-24 17:42:43 +09:00
Rokt33r
b510aa11f5 Merge branch 'dev'
* dev:
  fix bugs - ArticleList text overflow behaviour in Finder, PlanetContainer - No DevTools
2015-08-24 17:42:00 +09:00
Rokt33r
85f833c865 Merge branch 'dev'
* dev:
  v0.2.5 - bugfix - Alert message added(Private planet/Team member)

Conflicts:
	package.json
2015-08-24 13:46:32 +09:00
Rokt33r
b93990d10b bump version 2015-08-24 06:23:41 +09:00
Rokt33r
a0bcb8edbe Merge branch 'dev'
* dev:
  実装 - MemberがOwnerではないときにTeam設定ボターンを全部隠す
  v0.2.4 - Minor fix
  fix minor bug
2015-08-24 06:22:32 +09:00
Rokt33r
19930a2472 fix minor bug 2015-08-23 03:49:19 +09:00
Rokt33r
e75d95b1fc Merge branch 'dev'
* dev:
  実装 - PlanetArticleListへのAuto scroll追加
  実装 - tooltip (Refresh, Planet setting, Planet refresh, Edit article, Delete article, Contact)
  #14 改善適用 - アプリが立ち上がったら最初はmy planetが表示されるようにしたい - Noteがスクロールできない - Note行間がなくて読みづらい
  実装 - Hotkey
2015-08-23 03:42:40 +09:00
Rokt33r
3b7215b36a Merge branch 'dev'
* dev:
  改善 - Finder descriptionのTextoverflow対策, 最初起動の時のFinderの異常振る舞い
2015-08-22 03:46:51 +09:00
Rokt33r
8ab96cf2fb fix version 2015-08-22 03:36:04 +09:00
Rokt33r
63af2fd8b1 fix submodule setting 2015-08-22 03:33:32 +09:00
Rokt33r
fcf26f7844 Merge branch 'dev'
* dev:
  改善 - PlanetArticleList, PlanetArticleDetail
  #14 改善点4つ適用  - Team member 管理画面でのスペルミス  - Member profile imageを丸にする  - Profile popup 改善  - 文字サイズ大きく(UserContainerのみ)
2015-08-22 03:28:56 +09:00
Rokt33r
a05bba15e7 Merge branch 'dev'
* dev: (79 commits)
  version 0.2.1
  修正 - Member list, Team listのデザイン修正
  実装 - Team pageとPlanet pageにMember List表示
  実装 - PlanetHeaderにPrivate鍵表示
  Noteから外部Linkを開くときにBrowserを使う
  LogoutModal実装
  Contact Modal追加
  on Refactor... #4
  on Refactor... #3
  on Refactor... #2
  on Refactoring...
  test 0.2.0
  Fix: Personal Settingボターンを右にする。写真からまっすぐProfile pageに入れるようにする
  Add: Refresh button
  Fix: Design changed
  Fix: Disable pinch to zoom
  Fix: Preview button修正
  Fix: PlanetNavigatorのHome削除 & SnippetsとBlueprintsはToggleができるように
  Fix: minor features 設定ボタンアイコンの変更 削除Modalでcmd+enterの使用 検索バーデザイン
  Add: Log in / Sign upの時にエラーが出たらAlertを表示する Debug: Tray Icon, PopUpWindow, Menuがいつの間にか消える
  ...

Conflicts:
	src/browser/main/controllers/AppController.js
	src/browser/main/controllers/directives/SideNavController.js
	src/browser/main/controllers/states/AuthRegisterController.js
	src/browser/main/index.html
	src/browser/main/services/Modal.js
	src/browser/main/styles/app.css
	src/browser/main/styles/app.styl
	src/browser/main/styles/directives/side-nav.styl
	src/browser/main/tpls/directives/side-nav.tpl.html
	src/browser/main/tpls/states/home.tpl.html
2015-08-21 04:58:01 +09:00
Rokt33r
3b907627f7 fix links 2015-07-03 18:35:10 +09:00
Rokt33r
2284fd41b9 add agreement 2015-07-03 16:41:42 +09:00
58 changed files with 1747 additions and 797 deletions

4
.gitignore vendored
View File

@@ -1,6 +1,6 @@
.env
node_modules/*
!node_modules/boost
Boost-darwin-x64/
backup/
dist/
compiled
/secret

2
LICENSE Normal file
View File

@@ -0,0 +1,2 @@
本製品をインストール、または使用することによって、お客様は利用規約(
https://b00st.io/regulations.htmlより拘束されることに承諾されたものとします。利用規約に同意されない場合、Boostnoteは、お客様に本製品のインストール、使用のいずれも許諾できません。

View File

@@ -1,30 +1,161 @@
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const Menu = electron.Menu
const MenuItem = electron.MenuItem
const app = electron.app
const ipcMain = electron.ipcMain
const Tray = electron.Tray
const path = require('path')
const nodeIpc = require('@rokt33r/node-ipc')
var finderWindow = new BrowserWindow({
var appQuit = false
var isFinderLoaded = false
nodeIpc.config.id = 'finder'
nodeIpc.config.retry = 1500
nodeIpc.config.silent = true
nodeIpc.connectTo(
'main',
path.join(app.getPath('userData'), 'boost.service'),
function () {
nodeIpc.of.main.on(
'error',
function (err) {
nodeIpc.log('<< ## err ##'.rainbow, nodeIpc.config.delay)
nodeIpc.log(err)
}
)
nodeIpc.of.main.on(
'connect',
function () {
nodeIpc.log('<< ## connected to world ##'.rainbow, nodeIpc.config.delay)
}
)
nodeIpc.of.main.on(
'disconnect',
function () {
nodeIpc.log('<< disconnected from main'.notice)
if (process.platform === 'darwin') {
appQuit = true
app.quit()
}
}
)
nodeIpc.of.main.on(
'message',
function (payload) {
switch (payload.type) {
case 'open-finder':
if (isFinderLoaded) openFinder()
break
}
}
)
}
)
function emit (type, data) {
var payload = {
type: type,
data: data
}
nodeIpc.of.main.emit('message', payload)
}
var config = {
width: 640,
height: 400,
show: false,
frame: false,
resizable: false,
'zoom-factor': 1.0,
'always-on-top': true,
'web-preferences': {
'overlay-scrollbars': true,
'skip-taskbar': true
},
'standard-window': false
})
}
if (process.platform === 'darwin') {
config['always-on-top'] = true
}
var finderWindow = new BrowserWindow(config)
var url = path.resolve(__dirname, '../browser/finder/index.html')
finderWindow.loadURL('file://' + url)
finderWindow.setSkipTaskbar(true)
if (process.platform === 'darwin') {
finderWindow.setVisibleOnAllWorkspaces(true)
}
finderWindow.on('blur', function () {
finderWindow.hide()
hideFinder()
})
finderWindow.setVisibleOnAllWorkspaces(true)
finderWindow.on('close', function (e) {
if (process.platform === 'darwin') {
if (appQuit) return true
e.preventDefault()
finderWindow.hide()
}
})
var appIcon = new Tray(path.join(__dirname, '../resources/tray-icon.png'))
appIcon.setToolTip('Boost')
var trayMenu = new Menu()
trayMenu.append(new MenuItem({
label: 'Open Main window',
click: function () {
emit('show-main-window')
}
}))
trayMenu.append(new MenuItem({
label: 'Open Finder window',
click: function () {
openFinder()
}
}))
trayMenu.append(new MenuItem({
label: 'Quit',
click: function () {
emit('quit-app')
}
}))
appIcon.setContextMenu(trayMenu)
appIcon.on('click', function (e) {
e.preventDefault()
appIcon.popUpContextMenu(trayMenu)
})
ipcMain.on('copy-finder', function () {
emit('copy-finder')
})
ipcMain.on('hide-finder', function () {
hideFinder()
})
finderWindow.webContents.on('did-finish-load', function () {
isFinderLoaded = true
})
function openFinder () {
if (isFinderLoaded) finderWindow.show()
}
function hideFinder () {
if (process.platform === 'win32') {
finderWindow.minimize()
return
}
if (process.platform === 'darwin') {
Menu.sendActionToFirstResponder('hide:')
}
finderWindow.hide()
}
module.exports = finderWindow

View File

@@ -1,4 +1,5 @@
const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const path = require('path')
@@ -16,10 +17,13 @@ const url = path.resolve(__dirname, '../browser/main/index.html')
mainWindow.loadURL('file://' + url)
mainWindow.setVisibleOnAllWorkspaces(true)
mainWindow.webContents.on('new-window', function (e) {
e.preventDefault()
})
app.on('activate', function () {
if (mainWindow == null) return null
mainWindow.show()
})
module.exports = mainWindow

View File

@@ -1,122 +1,160 @@
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const shell = electron.shell
module.exports = [
{
label: 'Electron',
submenu: [
{
label: 'About Boost',
selector: 'orderFrontStandardAboutPanel:'
},
{
type: 'separator'
},
{
label: 'Services',
submenu: []
},
{
type: 'separator'
},
{
label: 'Hide Boost',
accelerator: 'Command+H',
selector: 'hide:'
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
selector: 'hideOtherApplications:'
},
{
label: 'Show All',
selector: 'unhideAllApplications:'
},
{
type: 'separator'
},
{
label: 'Quit',
accelerator: 'Command+Q',
selector: 'terminate:'
var boost = {
label: 'Boostnote',
submenu: [
{
label: 'About Boostnote',
selector: 'orderFrontStandardAboutPanel:'
},
{
type: 'separator'
},
{
label: 'Services',
submenu: []
},
{
type: 'separator'
},
{
label: 'Hide Boostnote',
accelerator: 'Command+H',
selector: 'hide:'
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
selector: 'hideOtherApplications:'
},
{
label: 'Show All',
selector: 'unhideAllApplications:'
},
{
type: 'separator'
},
{
label: 'Quit',
accelerator: 'Command+Q',
selector: 'terminate:'
}
]
}
var edit = {
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'Command+Z',
selector: 'undo:'
},
{
label: 'Redo',
accelerator: 'Shift+Command+Z',
selector: 'redo:'
},
{
type: 'separator'
},
{
label: 'Cut',
accelerator: 'Command+X',
selector: 'cut:'
},
{
label: 'Copy',
accelerator: 'Command+C',
selector: 'copy:'
},
{
label: 'Paste',
accelerator: 'Command+V',
selector: 'paste:'
},
{
label: 'Select All',
accelerator: 'Command+A',
selector: 'selectAll:'
}
]
}
var view = {
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: (function () {
if (process.platform === 'darwin') return 'Command+R'
else return 'Ctrl+R'
})(),
click: function () {
BrowserWindow.getFocusedWindow().reload()
}
]
},
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'Command+Z',
selector: 'undo:'
},
{
label: 'Redo',
accelerator: 'Shift+Command+Z',
selector: 'redo:'
},
{
type: 'separator'
},
{
label: 'Cut',
accelerator: 'Command+X',
selector: 'cut:'
},
{
label: 'Copy',
accelerator: 'Command+C',
selector: 'copy:'
},
{
label: 'Paste',
accelerator: 'Command+V',
selector: 'paste:'
},
{
label: 'Select All',
accelerator: 'Command+A',
selector: 'selectAll:'
}
]
},
{
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: 'Command+R',
click: function () {
BrowserWindow.getFocusedWindow().reload()
}
}
]
},
{
label: 'Window',
submenu: [
{
label: 'Minimize',
accelerator: 'Command+M',
selector: 'performMiniaturize:'
},
{
label: 'Close',
accelerator: 'Command+W',
selector: 'performClose:'
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
selector: 'arrangeInFront:'
}
]
},
{
label: 'Help',
submenu: []
}
]
},
// {
// label: 'Toggle Developer Tools',
// accelerator: (function () {
// if (process.platform === 'darwin') return 'Alt+Command+I'
// else return 'Ctrl+Shift+I'
// })(),
// click: function (item, focusedWindow) {
// if (focusedWindow) BrowserWindow.getFocusedWindow().toggleDevTools()
// }
// }
]
}
var window = {
label: 'Window',
submenu: [
{
label: 'Minimize',
accelerator: 'Command+M',
selector: 'performMiniaturize:'
},
{
label: 'Close',
accelerator: 'Command+W',
selector: 'performClose:'
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
selector: 'arrangeInFront:'
}
]
}
var help = {
label: 'Help',
role: 'help',
submenu: [
{
label: 'Boostnote official site',
click: function () { shell.openExternal('https://b00st.io/') }
},
{
label: 'Tutorial page',
click: function () { shell.openExternal('https://b00st.io/tutorial.html') }
},
{
label: 'Discussions',
click: function () { shell.openExternal('https://github.com/BoostIO/boost-app-discussions/issues') }
},
{
label: 'Changelog',
click: function () { shell.openExternal('https://github.com/BoostIO/boost-releases/blob/master/changelog.md') }
}
]
}
module.exports = process.platform === 'darwin'
? [boost, edit, view, window, help]
: [view, help]

View File

@@ -2,7 +2,7 @@
<html>
<head>
<title>Boost Finder</title>
<title>Boostnote Finder</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
@@ -30,7 +30,8 @@
<script>
const electron = require('electron')
electron.webFrame.setZoomLevelLimits(1, 1)
var scriptUrl = process.env.BOOST_ENV === 'development'
const _ = require('lodash')
var scriptUrl = _.find(electron.remote.process.argv, a => a === '--hot')
? 'http://localhost:8080/assets/finder.js'
: '../../compiled/finder.js'
var scriptEl=document.createElement('script')

View File

@@ -11,12 +11,19 @@ import _ from 'lodash'
import dataStore from 'boost/dataStore'
const electron = require('electron')
const { remote, clipboard } = electron
const { remote, clipboard, ipcRenderer } = electron
const path = require('path')
var hideFinder = remote.getGlobal('hideFinder')
function hideFinder () {
ipcRenderer.send('hide-finder')
}
function notify (...args) {
return new window.Notification(...args)
function notify (title, options) {
if (process.platform === 'win32') {
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
options.silent = false
}
return new window.Notification(title, options)
}
require('../styles/finder/index.styl')
@@ -32,11 +39,20 @@ class FinderMain extends React.Component {
}
componentDidMount () {
this.keyDownHandler = e => this.handleKeyDown(e)
document.addEventListener('keydown', this.keyDownHandler)
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
this.focusHandler = e => {
let { dispatch } = this.props
dispatch(searchArticle(''))
}
window.addEventListener('focus', this.focusHandler)
}
handleClick (e) {
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
componentWillUnmount () {
document.removeEventListener('keydown', this.keyDownHandler)
window.removeEventListener('focus', this.focusHandler)
}
handleKeyDown (e) {
@@ -58,12 +74,18 @@ class FinderMain extends React.Component {
hideFinder()
e.preventDefault()
}
if (e.keyCode === 91 || e.metaKey) {
return
}
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
}
saveToClipboard () {
let { activeArticle } = this.props
clipboard.writeText(activeArticle.content)
ipcRenderer.send('copy-finder')
notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!'
})
@@ -98,7 +120,7 @@ class FinderMain extends React.Component {
let { articles, activeArticle, status, dispatch } = this.props
let saveToClipboard = () => this.saveToClipboard()
return (
<div onClick={e => this.handleClick(e)} onKeyDown={e => this.handleKeyDown(e)} className='Finder'>
<div onClick={e => this.handleClick(e)} className='Finder'>
<FinderInput
handleSearchChange={e => this.handleSearchChange(e)}
ref='finderInput'
@@ -152,6 +174,14 @@ function buildFilter (key) {
return {type: TEXT_FILTER, value: key}
}
function isContaining (target, needle) {
return target.match(new RegExp(_.escapeRegExp(needle)))
}
function startsWith (target, needle) {
return target.match(new RegExp('^' + _.escapeRegExp(needle)))
}
function remap (state) {
let { articles, folders, status } = state
@@ -168,10 +198,10 @@ function remap (state) {
let targetFolders
if (folders != null) {
let exactTargetFolders = folders.filter(folder => {
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
return _.find(folderExactFilters, filter => isContaining(folder.name, filter.value))
})
let fuzzyTargetFolders = folders.filter(folder => {
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
})
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
@@ -184,7 +214,7 @@ function remap (state) {
if (textFilters.length > 0) {
articles = textFilters.reduce((articles, textFilter) => {
return articles.filter(article => {
return article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
})
}, articles)
}
@@ -192,7 +222,7 @@ function remap (state) {
if (tagFilters.length > 0) {
articles = tagFilters.reduce((articles, tagFilter) => {
return articles.filter(article => {
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
})
}, articles)
}
@@ -201,7 +231,6 @@ function remap (state) {
let activeArticle = _.findWhere(articles, {key: status.articleKey})
if (activeArticle == null) activeArticle = articles[0]
console.log(status.search)
return {
articles,
activeArticle,

View File

@@ -1,15 +1,15 @@
import React, { PropTypes} from 'react'
import { connect } from 'react-redux'
import { CREATE_MODE, EDIT_MODE, IDLE_MODE, NEW, toggleTutorial } from 'boost/actions'
// import UserNavigator from './HomePage/UserNavigator'
import { EDIT_MODE, IDLE_MODE, toggleTutorial } from 'boost/actions'
import ArticleNavigator from './HomePage/ArticleNavigator'
import ArticleTopBar from './HomePage/ArticleTopBar'
import ArticleList from './HomePage/ArticleList'
import ArticleDetail from './HomePage/ArticleDetail'
import _ from 'lodash'
import { isModalOpen, closeModal } from 'boost/modal'
const electron = require('electron')
const BrowserWindow = electron.remote.BrowserWindow
const remote = electron.remote
const TEXT_FILTER = 'TEXT_FILTER'
const FOLDER_FILTER = 'FOLDER_FILTER'
@@ -29,12 +29,7 @@ class HomePage extends React.Component {
}
handleKeyDown (e) {
if (process.env.BOOST_ENV === 'development' && e.keyCode === 73 && e.metaKey && e.altKey) {
e.preventDefault()
e.stopPropagation()
BrowserWindow.getFocusedWindow().toggleDevTools()
return
}
let cmdOrCtrl = process.platform === 'darwin' ? e.metaKey : e.ctrlKey
if (isModalOpen()) {
if (e.keyCode === 27) closeModal()
@@ -51,20 +46,27 @@ class HomePage extends React.Component {
}
// Search inputがfocusされていたら大体のキー入力は無視される。
if (top.isInputFocused() && !e.metaKey) {
if (top.isInputFocused() && !(e.metaKey || e.ctrlKey)) {
if (e.keyCode === 13 || e.keyCode === 27) top.escape()
return
}
switch (status.mode) {
case CREATE_MODE:
case EDIT_MODE:
if (e.keyCode === 27) {
detail.handleCancelButtonClick()
}
if ((e.keyCode === 13 && e.metaKey) || (e.keyCode === 83 && e.metaKey)) {
if ((e.keyCode === 13 && (cmdOrCtrl)) || (e.keyCode === 83 && (cmdOrCtrl))) {
detail.handleSaveButtonClick()
}
if (e.keyCode === 80 && cmdOrCtrl) {
detail.handleTogglePreviewButtonClick()
}
if (e.keyCode === 78 && cmdOrCtrl) {
nav.handleNewPostButtonClick()
e.preventDefault()
}
break
case IDLE_MODE:
if (e.keyCode === 69) {
@@ -80,14 +82,14 @@ class HomePage extends React.Component {
if (e.keyCode === 27) {
detail.handleDeleteCancelButtonClick()
}
if (e.keyCode === 13 && e.metaKey) {
if (e.keyCode === 13 && cmdOrCtrl) {
detail.handleDeleteConfirmButtonClick()
}
break
}
// `detail`の`openDeleteConfirmMenu`が`true`なら呼ばれない。
if (e.keyCode === 27 || (e.keyCode === 70 && e.metaKey)) {
if (e.keyCode === 27 || (e.keyCode === 70 && cmdOrCtrl)) {
top.focusInput()
}
@@ -99,7 +101,7 @@ class HomePage extends React.Component {
list.selectNextArticle()
}
if (e.keyCode === 65 || e.keyCode === 13 && e.metaKey) {
if ((e.keyCode === 65 && !e.metaKey && !e.ctrlKey) || (e.keyCode === 13 && cmdOrCtrl) || (e.keyCode === 78 && cmdOrCtrl)) {
nav.handleNewPostButtonClick()
e.preventDefault()
}
@@ -107,13 +109,14 @@ class HomePage extends React.Component {
}
render () {
let { dispatch, status, articles, allArticles, activeArticle, folders, tags, filters } = this.props
let { dispatch, status, user, articles, allArticles, activeArticle, folders, tags, filters } = this.props
return (
<div className='HomePage'>
<ArticleNavigator
ref='nav'
dispatch={dispatch}
user={user}
folders={folders}
status={status}
allArticles={allArticles}
@@ -134,6 +137,7 @@ class HomePage extends React.Component {
<ArticleDetail
ref='detail'
dispatch={dispatch}
user={user}
activeArticle={activeArticle}
folders={folders}
status={status}
@@ -164,8 +168,16 @@ function buildFilter (key) {
return {type: TEXT_FILTER, value: key}
}
function isContaining (target, needle) {
return target.match(new RegExp(_.escapeRegExp(needle)))
}
function startsWith (target, needle) {
return target.match(new RegExp('^' + _.escapeRegExp(needle)))
}
function remap (state) {
let { folders, articles, status } = state
let { user, folders, articles, status } = state
if (articles == null) articles = []
articles.sort((a, b) => {
@@ -192,10 +204,10 @@ function remap (state) {
let targetFolders
if (folders != null) {
let exactTargetFolders = folders.filter(folder => {
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
return _.findWhere(folderExactFilters, {value: folder.name})
})
let fuzzyTargetFolders = folders.filter(folder => {
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
})
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
@@ -208,7 +220,7 @@ function remap (state) {
if (textFilters.length > 0) {
articles = textFilters.reduce((articles, textFilter) => {
return articles.filter(article => {
return article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
})
}, articles)
}
@@ -216,7 +228,7 @@ function remap (state) {
if (tagFilters.length > 0) {
articles = tagFilters.reduce((articles, tagFilter) => {
return articles.filter(article => {
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
})
}, articles)
}
@@ -227,6 +239,7 @@ function remap (state) {
if (activeArticle == null) activeArticle = articles[0]
return {
user,
folders,
status,
allArticles,
@@ -242,11 +255,9 @@ function remap (state) {
}
HomePage.propTypes = {
params: PropTypes.shape({
userId: PropTypes.string
}),
status: PropTypes.shape({
userId: PropTypes.string
status: PropTypes.shape(),
user: PropTypes.shape({
name: PropTypes.string
}),
articles: PropTypes.array,
allArticles: PropTypes.array,
@@ -257,7 +268,8 @@ HomePage.propTypes = {
folder: PropTypes.array,
tag: PropTypes.array,
text: PropTypes.array
})
}),
tags: PropTypes.array
}
export default connect(remap)(HomePage)

View File

@@ -0,0 +1,151 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import api from 'boost/api'
import clientKey from 'boost/clientKey'
import activityRecord from 'boost/activityRecord'
const clipboard = require('electron').clipboard
function getDefault () {
return {
openDropdown: false,
isSharing: false,
// Fetched url
url: null,
// for tooltip Copy -> Copied!
copied: false,
failed: false
}
}
export default class ShareButton extends React.Component {
constructor (props) {
super(props)
this.state = getDefault()
}
componentWillReceiveProps (nextProps) {
this.setState(getDefault())
}
componentDidMount () {
this.dropdownInterceptor = e => {
this.dropdownClicked = true
}
ReactDOM.findDOMNode(this.refs.dropdown).addEventListener('click', this.dropdownInterceptor)
this.shareViaPublicURLHandler = e => {
this.handleShareViaPublicURLClick(e)
}
}
componentWillUnmount () {
document.removeEventListener('click', this.dropdownHandler)
ReactDOM.findDOMNode(this.refs.dropdown).removeEventListener('click', this.dropdownInterceptor)
}
handleOpenButtonClick (e) {
this.openDropdown()
if (this.dropdownHandler == null) {
this.dropdownHandler = e => {
if (!this.dropdownClicked) {
this.closeDropdown()
} else {
this.dropdownClicked = false
}
}
}
document.removeEventListener('click', this.dropdownHandler)
document.addEventListener('click', this.dropdownHandler)
}
openDropdown () {
this.setState({openDropdown: true})
}
closeDropdown () {
document.removeEventListener('click', this.dropdownHandler)
this.setState({openDropdown: false})
}
handleShareViaPublicURLClick (e) {
let { user } = this.props
let input = Object.assign({}, this.props.article, {
clientKey: clientKey.get(),
writerName: user.name
})
this.setState({
isSharing: true,
failed: false
}, () => {
api.shareViaPublicURL(input)
.then(res => {
let url = res.body.url
this.setState({url: url, isSharing: false})
activityRecord.emit('ARTICLE_SHARE')
})
.catch(err => {
console.log(err)
this.setState({isSharing: false, failed: true})
})
})
}
handleCopyURLClick () {
clipboard.writeText(this.state.url)
this.setState({copied: true})
}
// Restore copy url tooltip
handleCopyURLMouseLeave () {
this.setState({copied: false})
}
render () {
let hasPublicURL = this.state.url != null
return (
<div className='ShareButton'>
<button ref='openButton' onClick={e => this.handleOpenButtonClick(e)} className='ShareButton-open-button'>
<i className='fa fa-fw fa-share-alt'/>
{
this.state.openDropdown ? null : (
<span className='tooltip'>Share</span>
)
}
</button>
<div ref='dropdown' className={'share-dropdown' + (this.state.openDropdown ? '' : ' hide')}>
{
!hasPublicURL ? (
<button
onClick={e => this.shareViaPublicURLHandler(e)}
ref='sharePublicURL'
disabled={this.state.isSharing}>
<i className='fa fa-fw fa-external-link'/> {this.state.failed ? 'Failed : Click to Try again' : !this.state.isSharing ? 'Share via public URL' : 'Sharing...'}
</button>
) : (
<div className='ShareButton-url'>
<input className='ShareButton-url-input' value={this.state.url} readOnly/>
<button
onClick={e => this.handleCopyURLClick(e)}
className='ShareButton-url-button'
onMouseLeave={e => this.handleCopyURLMouseLeave(e)}
>
<i className='fa fa-fw fa-clipboard'/>
<div className='ShareButton-url-button-tooltip'>{this.state.copied ? 'Copied!' : 'Copy URL'}</div>
</button>
<div className='ShareButton-url-alert'>This url is valid for 7 days.</div>
</div>
)
}
</div>
</div>
)
}
}
ShareButton.propTypes = {
article: PropTypes.shape({
publicURL: PropTypes.string
}),
user: PropTypes.shape({
name: PropTypes.string
})
}

View File

@@ -24,6 +24,10 @@ import TagLink from 'boost/components/TagLink'
import TagSelect from 'boost/components/TagSelect'
import ModeSelect from 'boost/components/ModeSelect'
import activityRecord from 'boost/activityRecord'
import ShareButton from './ShareButton'
const electron = require('electron')
const clipboard = electron.clipboard
const BRAND_COLOR = '#18AF90'
@@ -84,6 +88,10 @@ const modeSelectTutorialElement = (
</svg>
)
function notify (...args) {
return new window.Notification(...args)
}
function makeInstantArticle (article) {
return Object.assign({}, article)
}
@@ -99,12 +107,16 @@ export default class ArticleDetail extends React.Component {
isTagChanged: false,
isTitleChanged: false,
isContentChanged: false,
isModeChanged: false
isModeChanged: false,
openShareDropdown: false
}
}
componentDidMount () {
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
this.shareDropdownInterceptor = e => {
e.stopPropagation()
}
}
componentWillUnmount () {
@@ -154,6 +166,14 @@ export default class ArticleDetail extends React.Component {
)
}
handleClipboardButtonClick (e) {
activityRecord.emit('MAIN_DETAIL_COPY')
clipboard.writeText(this.props.activeArticle.content)
notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!'
})
}
handleEditButtonClick (e) {
let { dispatch } = this.props
dispatch(switchMode(EDIT_MODE))
@@ -176,7 +196,7 @@ export default class ArticleDetail extends React.Component {
}
renderIdle () {
let { status, activeArticle, folders } = this.props
let { status, activeArticle, folders, user } = this.props
let tags = activeArticle.tags != null ? activeArticle.tags.length > 0
? activeArticle.tags.map(tag => {
@@ -185,8 +205,13 @@ export default class ArticleDetail extends React.Component {
: (
<span className='noTags'>Not tagged yet</span>
) : null
let folder = _.findWhere(folders, {key: activeArticle.FolderKey})
let title = activeArticle.title.trim().length === 0
? <small>(Untitled)</small>
: activeArticle.title
return (
<div className='ArticleDetail idle'>
{this.state.openDeleteConfirmMenu
@@ -214,6 +239,15 @@ export default class ArticleDetail extends React.Component {
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
</div>
<div className='right'>
<ShareButton
article={activeArticle}
user={user}
/>
<button onClick={e => this.handleClipboardButtonClick(e)} className='editBtn'>
<i className='fa fa-fw fa-clipboard'/><span className='tooltip'>Copy to clipboard</span>
</button>
<button onClick={e => this.handleEditButtonClick(e)} className='editBtn'>
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Edit (e)</span>
</button>
@@ -232,7 +266,7 @@ export default class ArticleDetail extends React.Component {
<div className='detailPanel'>
<div className='header'>
<ModeIcon className='mode' mode={activeArticle.mode}/>
<div className='title'>{activeArticle.title}</div>
<div className='title'>{title}</div>
</div>
{activeArticle.mode === 'markdown'
? <MarkdownPreview content={activeArticle.content}/>
@@ -263,13 +297,17 @@ export default class ArticleDetail extends React.Component {
dispatch(unlockStatus())
delete newArticle.status
newArticle.status = null
newArticle.updatedAt = new Date()
newArticle.title = newArticle.title.trim()
if (newArticle.createdAt == null) {
newArticle.createdAt = new Date()
activityRecord.emit('ARTICLE_CREATE')
if (newArticle.title.length === 0) {
newArticle.title = `Created at ${moment(newArticle.createdAt).format('YYYY/MM/DD HH:mm')}`
}
activityRecord.emit('ARTICLE_CREATE', {mode: newArticle.mode})
} else {
activityRecord.emit('ARTICLE_UPDATE')
activityRecord.emit('ARTICLE_UPDATE', {mode: newArticle.mode})
}
dispatch(updateArticle(newArticle))
@@ -408,7 +446,36 @@ export default class ArticleDetail extends React.Component {
}
handleTogglePreviewButtonClick (e) {
this.setState({previewMode: !this.state.previewMode})
if (this.state.article.mode === 'markdown') {
if (!this.state.previewMode) {
let cursorPosition = this.refs.code.getCursorPosition()
let firstVisibleRow = this.refs.code.getFirstVisibleRow()
this.setState({
previewMode: true,
cursorPosition,
firstVisibleRow
}, function () {
let previewEl = ReactDOM.findDOMNode(this.refs.preview)
let anchors = previewEl.querySelectorAll('.lineAnchor')
for (let i = 0; i < anchors.length; i++) {
if (parseInt(anchors[i].dataset.key, 10) > cursorPosition.row || i === anchors.length - 1) {
var targetAnchor = anchors[i > 0 ? i - 1 : 0]
previewEl.scrollTop = targetAnchor.offsetTop - 100
break
}
}
})
} else {
this.setState({
previewMode: false
}, function () {
console.log(this.state.cursorPosition)
this.refs.code.moveCursorTo(this.state.cursorPosition.row, this.state.cursorPosition.column)
this.refs.code.scrollToLine(this.state.firstVisibleRow)
this.refs.code.editor.focus()
})
}
}
}
handleTitleKeyDown (e) {
@@ -453,18 +520,39 @@ export default class ArticleDetail extends React.Component {
<div className='right'>
{
this.state.article.mode === 'markdown'
? (<button className='preview' onClick={e => this.handleTogglePreviewButtonClick(e)}>{!this.state.previewMode ? 'Preview' : 'Edit'}</button>)
? (<button className='preview' onClick={e => this.handleTogglePreviewButtonClick(e)}>
{
!this.state.previewMode
? 'Preview'
: 'Edit'
}
<span className='tooltip'>{process.platform === 'darwin' ? '⌘' : '^'} + p</span>
</button>)
: null
}
<button onClick={e => this.handleCancelButtonClick(e)}>Cancel</button>
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>Save</button>
<button onClick={e => this.handleCancelButtonClick(e)} className='cancelBtn'>
Cancel
<span className='tooltip'>Esc</span>
</button>
<button onClick={e => this.handleSaveButtonClick(e)} className='saveBtn'>
Save
<span className='tooltip'>{process.platform === 'darwin' ? '⌘' : '^'} + s</span>
</button>
</div>
</div>
<div className='detailBody'>
<div className='detailPanel'>
<div className='header'>
<div className='title'>
<input onKeyDown={e => this.handleTitleKeyDown(e)} placeholder='Title' ref='title' value={this.state.article.title} onChange={e => this.handleTitleChange(e)}/>
<input
onKeyDown={e => this.handleTitleKeyDown(e)}
placeholder={this.state.article.createdAt == null
? `Created at ${moment().format('YYYY/MM/DD HH:mm')}`
: 'Title'}
ref='title'
value={this.state.article.title}
onChange={e => this.handleTitleChange(e)}
/>
</div>
<ModeSelect
ref='mode'
@@ -478,7 +566,7 @@ export default class ArticleDetail extends React.Component {
</div>
{this.state.previewMode
? <MarkdownPreview content={this.state.article.content}/>
? <MarkdownPreview ref='preview' content={this.state.article.content}/>
: (<CodeEditor
ref='code'
onChange={(e, value) => this.handleContentChange(e, value)}
@@ -512,7 +600,9 @@ export default class ArticleDetail extends React.Component {
ArticleDetail.propTypes = {
status: PropTypes.shape(),
activeArticle: PropTypes.shape(),
activeUser: PropTypes.shape(),
user: PropTypes.shape(),
folders: PropTypes.array,
tags: PropTypes.array,
dispatch: PropTypes.func
}
ArticleDetail.prototype.linkState = linkState

View File

@@ -80,6 +80,12 @@ export default class ArticleList extends React.Component {
: (<span>Not tagged yet</span>)
let folder = _.findWhere(folders, {key: article.FolderKey})
let title = article.status !== NEW
? article.title.trim().length === 0
? <small>(Untitled)</small>
: article.title
: '(New article)'
return (
<div key={'article-' + article.key}>
<div onClick={e => this.handleArticleClick(article)(e)} className={'articleItem' + (activeArticle.key === article.key ? ' active' : '')}>
@@ -91,7 +97,7 @@ export default class ArticleList extends React.Component {
<span className='updatedAt'>{article.status != null ? article.status : moment(article.updatedAt).fromNow()}</span>
</div>
<div className='middle'>
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{article.status !== NEW ? article.title : '(New article)'}</div>
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{title}</div>
</div>
<div className='bottom'>
<div className='tags'><i className='fa fa-fw fa-tags'/>{tagElements}</div>

View File

@@ -1,16 +1,12 @@
import React, { PropTypes } from 'react'
import { findWhere } from 'lodash'
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, EDIT_MODE } from 'boost/actions'
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, clearNewArticle, EDIT_MODE } from 'boost/actions'
import { openModal } from 'boost/modal'
import FolderMark from 'boost/components/FolderMark'
import Preferences from 'boost/components/modal/Preferences'
import CreateNewFolder from 'boost/components/modal/CreateNewFolder'
import keygen from 'boost/keygen'
const electron = require('electron')
const remote = electron.remote
let userName = remote.getGlobal('process').env.USER
const BRAND_COLOR = '#18AF90'
const preferenceTutorialElement = (
@@ -31,7 +27,7 @@ c-4,0-7.9,0-11.9-0.1C164,294,164,297,165.9,297L165.9,297z'/>
const newPostTutorialElement = (
<svg width='900' height='900' className='tutorial'>
<text x='290' y='155' fill={BRAND_COLOR} fontSize='24'>Create a new post!!</text>
<text x='300' y='180' fill={BRAND_COLOR} fontSize='16'>press ` + Enter` or `a`</text>
<text x='300' y='180' fill={BRAND_COLOR} fontSize='16' children={`press \`${process.platform === 'darwin' ? '⌘' : '^'} + Enter\` or \`a\``}/>
<svg x='130' y='-20' width='400' height='400'>
<path fill='white' d='M56.2,132.5c11.7-2.9,23.9-6,36.1-4.1c8.7,1.4,16.6,5.5,23.7,10.5c13.3,9.4,24.5,21.5,40.2,27
c1.8,0.6,2.6-2.3,0.8-2.9c-17.1-6-28.9-20.3-44-29.7c-7-4.4-14.8-7.4-23-8.2c-11.7-1.1-23.3,1.7-34.5,4.5
@@ -85,6 +81,7 @@ export default class ArticleNavigator extends React.Component {
status: 'NEW'
}
dispatch(clearNewArticle())
dispatch(updateArticle(newArticle))
dispatch(switchArticle(newArticle.key, true))
dispatch(switchMode(EDIT_MODE))
@@ -108,7 +105,7 @@ export default class ArticleNavigator extends React.Component {
}
render () {
let { status, folders, allArticles } = this.props
let { status, user, folders, allArticles } = this.props
let { targetFolders } = status
if (targetFolders == null) targetFolders = []
@@ -126,7 +123,7 @@ export default class ArticleNavigator extends React.Component {
return (
<div className='ArticleNavigator'>
<div className='userInfo'>
<div className='userProfileName'>{userName}</div>
<div className='userProfileName'>{user.name}</div>
<div className='userName'>localStorage</div>
<button onClick={e => this.handlePreferencesButtonClick(e)} className='settingBtn'>
<i className='fa fa-fw fa-chevron-down'/>
@@ -140,7 +137,7 @@ export default class ArticleNavigator extends React.Component {
<div className='controlSection'>
<button onClick={e => this.handleNewPostButtonClick(e)} className='newPostBtn'>
New Post
<span className='tooltip'>Create a new Post ( + Enter or a)</span>
<span className='tooltip'>Create a new Post ({process.platform === 'darwin' ? '⌘' : '^'} + Enter or a)</span>
</button>
{status.isTutorialOpen ? newPostTutorialElement : null}

View File

@@ -35,18 +35,33 @@ export default class ArticleTopBar extends React.Component {
super(props)
this.state = {
isTooltipHidden: true
isTooltipHidden: true,
isLinksDropdownOpen: false
}
}
componentDidMount () {
this.searchInput = ReactDOM.findDOMNode(this.refs.searchInput)
this.linksButton = ReactDOM.findDOMNode(this.refs.links)
this.showLinksDropdown = e => {
e.preventDefault()
e.stopPropagation()
if (!this.state.isLinksDropdownOpen) {
this.setState({isLinksDropdownOpen: true})
}
}
this.linksButton.addEventListener('click', this.showLinksDropdown)
this.hideLinksDropdown = e => {
if (this.state.isLinksDropdownOpen) {
this.setState({isLinksDropdownOpen: false})
}
}
document.addEventListener('click', this.hideLinksDropdown)
}
componentWillUnmount () {
this.searchInput.removeEventListener('keydown', this.showTooltip)
this.searchInput.removeEventListener('focus', this.showTooltip)
this.searchInput.removeEventListener('blur', this.showTooltip)
document.removeEventListener('click', this.hideLinksDropdown)
this.linksButton.removeEventListener('click', this.showLinksDropdown())
}
handleTooltipRequest (e) {
@@ -118,8 +133,11 @@ export default class ArticleTopBar extends React.Component {
: null
}
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
- Search by tag : #{'{string}'}<br/>
- Search by folder : /{'{folder_name}'}
<ul>
<li>- Search by tag : #{'{string}'}</li>
<li>- Search by folder : /{'{folder_name}'}</li>
<li><small>exact match : //{'{folder_name}'}</small></li>
</ul>
</div>
</div>
@@ -129,10 +147,23 @@ export default class ArticleTopBar extends React.Component {
<div className='right'>
<button onClick={e => this.handleTutorialButtonClick(e)}>?<span className='tooltip'>How to use</span>
</button>
<ExternalLink className='logo' href='http://b00st.io'>
<img src='../../resources/favicon-230x230.png' width='44' height='44'/>
<span className='tooltip'>Boost official page</span>
</ExternalLink>
<a ref='links' className='linksBtn' href>
<img src='../../resources/app.png' width='44' height='44'/>
</a>
{
this.state.isLinksDropdownOpen
? (
<div className='links-dropdown'>
<ExternalLink className='links-item' href='https://b00st.io'>
<i className='fa fa-fw fa-home'/>Boost official page
</ExternalLink>
<ExternalLink className='links-item' href='https://github.com/BoostIO/boost-app-discussions/issues'>
<i className='fa fa-fw fa-bullhorn'/> Discuss
</ExternalLink>
</div>
)
: null
}
</div>
{status.isTutorialOpen ? (

View File

@@ -2,8 +2,6 @@ const electron = require('electron')
const ipc = electron.ipcRenderer
import React, { PropTypes } from 'react'
var ContactModal = require('boost/components/modal/ContactModal')
export default class MainContainer extends React.Component {
constructor (props) {
super(props)
@@ -20,20 +18,12 @@ export default class MainContainer extends React.Component {
ipc.send('update-app', 'Deal with it.')
}
openContactModal () {
this.openModal(ContactModal)
}
render () {
return (
<div className='Main'>
{this.state.updateAvailable ? (
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
) : null}
{/* <button onClick={this.openContactModal} className='contactButton'>
<i className='fa fa-paper-plane-o'/>
<div className='tooltip'>Contact us</div>
</button> */}
{this.props.children}
</div>
)

View File

@@ -8,6 +8,7 @@
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
<link rel="stylesheet" href="../../node_modules/highlight.js/styles/xcode.css">
<link rel="shortcut icon" href="favicon.ico">
<title>Boostnote</title>
<style>
@font-face {
@@ -46,7 +47,7 @@
</head>
<body>
<div id="loadingCover">
<img src="../../resources/favicon-230x230.png">
<img src="../../resources/app.png">
<div class='message'>Loading...</div>
</div>
@@ -57,11 +58,11 @@
const electron = require('electron')
electron.webFrame.setZoomLevelLimits(1, 1)
var version = electron.remote.app.getVersion()
document.title = 'Boost' + ((version == null || version.length === 0) ? ' DEV' : '')
var scriptUrl = process.env.BOOST_ENV === 'development'
const _ = require('lodash')
var scriptUrl = _.find(electron.remote.process.argv, a => a === '--hot')
? 'http://localhost:8080/assets/main.js'
: '../../compiled/main.js'
var scriptEl=document.createElement('script')
var scriptEl = document.createElement('script')
scriptEl.setAttribute("type","text/javascript")
scriptEl.setAttribute("src", scriptUrl)
document.getElementsByTagName("head")[0].appendChild(scriptEl)

View File

@@ -1,4 +1,3 @@
import React from 'react'
import { Provider } from 'react-redux'
// import { updateUser } from 'boost/actions'
import { Router, Route, IndexRoute } from 'react-router'
@@ -6,6 +5,7 @@ import MainPage from './MainPage'
import HomePage from './HomePage'
// import auth from 'boost/auth'
import store from 'boost/store'
import React from 'react'
import ReactDOM from 'react-dom'
require('../styles/main/index.styl')
import { openModal } from 'boost/modal'
@@ -13,14 +13,20 @@ import Tutorial from 'boost/components/modal/Tutorial'
import activityRecord from 'boost/activityRecord'
const electron = require('electron')
const ipc = electron.ipcRenderer
const path = require('path')
activityRecord.init()
window.addEventListener('online', function () {
ipc.send('check-update', 'check-update')
})
function notify (...args) {
return new window.Notification(...args)
function notify (title, options) {
if (process.platform === 'win32') {
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
options.silent = false
}
console.log(options)
return new window.Notification(title, options)
}
ipc.on('notify', function (e, payload) {
@@ -29,6 +35,13 @@ ipc.on('notify', function (e, payload) {
})
})
ipc.on('copy-finder', function () {
activityRecord.emit('FINDER_COPY')
})
ipc.on('open-finder', function () {
activityRecord.emit('FINDER_OPEN')
})
let routes = (
<Route path='/' component={MainPage}>
<IndexRoute name='home' component={HomePage}/>

View File

@@ -7,13 +7,17 @@ global-reset()
iptBgColor = #E6E6E6
iptFocusBorderColor = #369DCD
DEFAULT_FONTS = 'Lato', 'MS Gothic', 'Malgun Gothic', 'Sans-serif'
body
font-family "Lato"
font-family DEFAULT_FONTS
color textColor
font-size fontSize
width 100%
height 100%
overflow hidden
button, input
font-family "Lato"
.Finder
absolute top bottom left right

View File

@@ -159,6 +159,7 @@ iptFocusBorderColor = #369DCD
&:hover
background-color darken(white, 10%)
.right
z-index 30
button
cursor pointer
height 33px
@@ -169,14 +170,26 @@ iptFocusBorderColor = #369DCD
background-color darken(white, 5%)
border solid 1px borderColor
border-radius 5px
&>.tooltip
tooltip()
top 105px
&.preview
width inherit
.tooltip
margin-left -55px
&:hover
background-color white
&.primary
.tooltip
opacity 1
&.cancelBtn
.tooltip
margin-left -25px
&.saveBtn
border none
background-color brandColor
color white
.tooltip
margin-left -45px
&:hover
color white
background-color lighten(brandColor, 10%)
@@ -284,7 +297,87 @@ iptFocusBorderColor = #369DCD
color noTagsColor
.right
z-index 30
button
div.share-dropdown
position absolute
right 5px
top 30px
background-color transparentify(invBackgroundColor, 80%)
padding 5px 0
width 200px
&.hide
display none
&>button
width 200px
text-align left
display block
height 33px
background-color transparent
color white
font-size 14px
padding 0 10px
border none
&:hover
background-color transparentify(lighten(invBackgroundColor, 30%), 80%)
&>.ShareButton-url
clearfix()
input.ShareButton-url-input
width 155px
margin 0 0 0 5px
height 25px
outline none
border none
border-top-left-radius 5px
border-bottom-left-radius 5px
float left
padding 5px
button.ShareButton-url-button
width 35px
height 25px
border none
margin 0 5px 0 0
outline none
border-top-right-radius 5px
border-bottom-right-radius 5px
background-color darken(white, 5%)
color inactiveTextColor
float right
div.ShareButton-url-button-tooltip
tooltip()
right 10px
&:hover
color textColor
div.ShareButton-url-button-tooltip
opacity 1
div.ShareButton-url-alert
float left
height 25px
line-height 25px
padding 0 15px
color white
.ShareButton
display inline-block
button.ShareButton-open-button
border-radius 16.5px
cursor pointer
height 33px
width 33px
border none
margin-right 5px
font-size 18px
color inactiveTextColor
background-color darken(white, 5%)
padding 0
.tooltip
tooltip()
margin-top 25px
margin-left -40px
&:hover
color textColor
.tooltip
opacity 1
&>button
border-radius 16.5px
cursor pointer
height 33px
@@ -323,7 +416,8 @@ iptFocusBorderColor = #369DCD
right 15px
font-size 24px
line-height 60px
white-space nowrap
overflow-x auto
overflow-y hidden
small
color #AAA

View File

@@ -48,6 +48,8 @@ articleItemColor = #777
left 19px
right 0
overflow ellipsis
small
color #AAA
.bottom
padding 5px 0
overflow-x auto

View File

@@ -14,7 +14,7 @@ articleCount = #999
.userProfileName
color brandColor
font-size 28px
padding 6px 0 0 10px
padding 6px 37px 0 10px
white-space nowrap
text-overflow ellipsis
overflow hidden

View File

@@ -62,6 +62,13 @@ infoBtnActiveBgColor = #3A3A3A
opacity 1
&.hide
opacity 0
ul
li:last-child
line-height 10px
margin-bottom 3px
small
font-size 10px
margin-left 15px
input
absolute top left
width 350px
@@ -140,17 +147,33 @@ infoBtnActiveBgColor = #3A3A3A
.tooltip
opacity 1
&>.logo
&>.linksBtn
display block
position absolute
top 8px
right 15px
opacity 0.7
.tooltip
tooltip()
margin-top 44px
margin-left -120px
&:hover
opacity 1
.tooltip
opacity 1
&>.links-dropdown
position fixed
z-index 50
right 10px
top 40px
background-color transparentify(invBackgroundColor, 80%)
padding 5px 0
.links-item
padding 0 10px
height 33px
width 100%
display block
line-height 33px
text-decoration none
color white
&:hover
background-color transparentify(lighten(invBackgroundColor, 30%), 80%)

View File

@@ -7,6 +7,8 @@ global-reset()
@import './containers/*'
@import './HomeContainer'
DEFAULT_FONTS = 'Lato', 'MS Gothic', 'Malgun Gothic', 'Sans-serif'
*
-webkit-app-region no-drag
-webkit-user-select none
@@ -17,13 +19,13 @@ html, body
overflow hidden
body
font-family "Lato"
font-family DEFAULT_FONTS
color textColor
font-size fontSize
font-weight 400
button, input, select
font-family "Lato"
font-family DEFAULT_FONTS
div, span, a, button, input, textarea
box-sizing border-box

View File

@@ -1,36 +1,38 @@
marked()
h1, h2, h3, h4, h5, h6, p
&:first-child
margin-top 0
hr
border-top none
border-bottom solid 1px borderColor
margin 15px 0
h1, h2, h3, h4, h5, h6
margin 0 0 15px
font-weight 600
* + h1, * + h2, * + h3, * + h4, * + h5, * + h6
margin-top 25px
h1
font-size 2em
border-bottom solid 2px borderColor
margin 0.33em auto 0.67em
line-height 2.333em
h2
font-size 1.5em
margin 0.42em auto 0.83em
font-size 1.66em
line-height 2.07em
h3
font-size 1.17em
margin 0.5em auto 1em
font-size 1.33em
line-height 1.6625em
h4
font-size 1em
margin 0.67em auto 1.33em
font-size 1.15em
line-height 1.4375em
h5
font-size 0.83em
margin 0.84em auto 1.67em
font-size 1em
line-height 1.25em
h6
font-size 0.67em
margin 1.16em auto 2.33em
h1, h2, h3, h4, h5, h6
font-weight 700
line-height 1.8em
font-size 0.8em
line-height 1em
* + p, * + blockquote, * + ul, * + ol, * + pre
margin-top 15px
p
line-height 1.8em
margin 15px 0 25px
line-height 1.9em
margin 0 0 15px
img
max-width 100%
strong
@@ -41,15 +43,17 @@ marked()
text-decoration line-through
blockquote
border-left solid 4px brandBorderColor
margin 15px 0 25px
margin 0 0 15px
padding 0 25px
ul
list-style-type disc
padding-left 35px
margin-bottom 35px
margin-bottom 15px
li
display list-item
line-height 1.8em
&>li>ul, &>li>ol
margin 0
&>li>ul
list-style-type circle
&>li>ul
@@ -57,10 +61,12 @@ marked()
ol
list-style-type decimal
padding-left 35px
margin-bottom 35px
margin-bottom 15px
li
display list-item
line-height 1.8em
&>li>ul, &>li>ol
margin 0
code
font-family Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace;
padding 2px 4px
@@ -70,15 +76,19 @@ marked()
color black
text-decoration none
background-color #F6F6F6
margin-right 2px
* + code
margin-left 2px
pre
padding 5px
border solid 1px borderColor
border-radius 5px
overflow-x auto
margin 15px 0 25px
margin 0 0 15px
background-color #F6F6F6
line-height 1.35em
&>code
margin 0
padding 0
border none
border-radius 0

View File

@@ -1,72 +1,17 @@
const electron = require('electron')
const app = electron.app
const Tray = electron.Tray
const Menu = electron.Menu
const MenuItem = electron.MenuItem
process.stdin.setEncoding('utf8')
var finderWindow = null
console.log = function () {
process.stdout.write(JSON.stringify({
type: 'log',
data: JSON.stringify(Array.prototype.slice.call(arguments).join(' '))
}), 'utf-8')
}
function emit (type, data) {
process.stdout.write(JSON.stringify({
type: type,
data: JSON.stringify(data)
}), 'utf-8')
}
var finderWindow
app.on('ready', function () {
app.dock.hide()
var appIcon = new Tray(__dirname + '/resources/tray-icon.png')
appIcon.setToolTip('Boost')
if (process.platform === 'darwin') {
app.dock.hide()
}
var template = require('./atom-lib/menu-template')
var menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
finderWindow = require('./atom-lib/finder-window')
finderWindow.webContents.on('did-finish-load', function () {
var trayMenu = new Menu()
trayMenu.append(new MenuItem({
label: 'Open Main window',
click: function () {
emit('show-main-window')
}
}))
trayMenu.append(new MenuItem({
label: 'Open Finder',
click: function () {
finderWindow.show()
}
}))
trayMenu.append(new MenuItem({
label: 'Quit',
click: function () {
emit('quit-app')
}
}))
appIcon.setContextMenu(trayMenu)
process.stdin.on('data', function (payload) {
try {
payload = JSON.parse(payload)
} catch (e) {
console.log('Not parsable payload : ', payload)
return
}
console.log('from main >> ', payload.type)
switch (payload.type) {
case 'open-finder':
finderWindow.show()
break
}
})
})
global.hideFinder = function () {
Menu.sendActionToFirstResponder('hide:')
}
})

205
gruntfile.js Normal file
View File

@@ -0,0 +1,205 @@
const path = require('path')
const ChildProcess = require('child_process')
const packager = require('electron-packager')
const fs = require('fs')
if (process.platform === 'darwin') {
const appdmg = require('appdmg')
}
module.exports = function (grunt) {
if (process.platform === 'win32') auth_code = grunt.file.readJSON('secret/auth_code.json')
var initConfig = {
pkg: grunt.file.readJSON('package.json'),
'create-windows-installer': {
x64: {
appDirectory: path.join(__dirname, 'dist', 'Boostnote-win32-x64'),
outputDirectory: path.join(__dirname, 'dist'),
authors: 'MAISIN&CO., Inc.',
exe: 'Boostnote.exe',
loadingGif: path.join(__dirname, 'resources/boostnote-install.gif'),
iconUrl: path.join(__dirname, 'resources/app.ico'),
setupIcon: path.join(__dirname, 'resources/dmg.ico'),
certificateFile: path.join(__dirname, 'secret', 'authenticode_cer.p12'),
certificatePassword: auth_code.win_cert_pw,
noMsi: true
}
}
}
grunt.initConfig(initConfig)
grunt.loadNpmTasks('grunt-electron-installer')
grunt.registerTask('compile', function () {
var done = this.async()
var execPath = path.join('node_modules', '.bin', 'webpack') + ' --config webpack.config.production.js'
grunt.log.writeln(execPath)
ChildProcess.exec(execPath,
{
env: Object.assign({}, process.env, {
BABEL_ENV: 'production'
})
},
function (err, stdout, stderr) {
grunt.log.writeln(stdout)
if (err) {
grunt.log.writeln(err)
grunt.log.writeln(stderr)
done(false)
return
}
done()
}
)
})
grunt.registerTask('zip', function (platform) {
var done = this.async()
switch (platform) {
case 'osx':
var execPath = 'cd dist/Boostnote-darwin-x64 && zip -r -y -q ../Boostnote-mac.zip Boostnote.app'
grunt.log.writeln(execPath)
ChildProcess.exec(execPath,
function (err, stdout, stderr) {
grunt.log.writeln(stdout)
if (err) {
grunt.log.writeln(err)
grunt.log.writeln(stderr)
done(false)
return
}
done()
}
)
break
default:
done()
return
}
})
grunt.registerTask('pack', function (platform) {
grunt.log.writeln(path.join(__dirname, 'dist'))
var done = this.async()
var opts = {
name: 'Boostnote',
arch: 'x64',
dir: __dirname,
version: grunt.config.get('pkg.config.electron-version'),
'app-version': grunt.config.get('pkg.version'),
'app-bundle-id': 'com.maisin.boost',
asar: true,
prune: true,
overwrite: true,
out: path.join(__dirname, 'dist'),
ignore: /submodules\/ace\/(?!src-min)|submodules\/ace\/(?=src-min-noconflict)|node_modules\/devicon\/icons|dist|.env/
}
switch (platform) {
case 'win':
Object.assign(opts, {
platform: 'win32',
icon: path.join(__dirname, 'resources/app.ico'),
'version-string': {
CompanyName: 'MAISIN&CO., Inc.',
LegalCopyright: '© 2015 MAISIN&CO., Inc. All rights reserved.',
FileDescription: 'Boostnote',
OriginalFilename: 'Boostnote',
FileVersion: grunt.config.get('pkg.version'),
ProductVersion: grunt.config.get('pkg.version'),
ProductName: 'Boostnote',
InternalName: 'Boostnote'
}
})
packager(opts, function (err, appPath) {
if (err) {
grunt.log.writeln(err)
done(err)
return
}
done()
})
break
case 'osx':
Object.assign(opts, {
platform: 'darwin',
icon: path.join(__dirname, 'resources/app.icns'),
'app-category-type': 'public.app-category.developer-tools'
})
packager(opts, function (err, appPath) {
if (err) {
grunt.log.writeln(err)
done(err)
return
}
done()
})
break
}
})
grunt.registerTask('codesign', function (platform) {
var done = this.async()
if (process.platform !== 'darwin') {
done(false)
return
}
ChildProcess.exec('codesign --verbose --deep --force --sign \"\" dist/Boostnote-darwin-x64/Boostnote.app', function (err, stdout, stderr) {
grunt.log.writeln(stdout)
if (err) {
grunt.log.writeln(err)
grunt.log.writeln(stderr)
done(false)
return
}
done()
})
})
grunt.registerTask('create-osx-installer', function () {
var done = this.async()
var stream = appdmg({
target: 'dist/Boostnote-mac.dmg',
basepath: __dirname,
specification: {
'title': 'Boostnote',
'icon': 'resources/dmg.icns',
'background': 'resources/boostnote-install.png',
'icon-size': 80,
'contents': [
{ 'x': 448, 'y': 344, 'type': 'link', 'path': '/Applications' },
{ 'x': 192, 'y': 344, 'type': 'file', 'path': 'dist/Boostnote-darwin-x64/Boostnote.app' }
]
}
})
stream.on('finish', function () {
done()
})
stream.on('error', function (err) {
grunt.log.writeln(err)
done(false)
})
})
grunt.registerTask('build', function (platform) {
if (!platform) {
platform = process.platform === 'darwin' ? 'osx' : process.platform === 'win32' ? 'win' : null
}
switch (platform) {
case 'win':
grunt.task.run(['compile', 'pack:win', 'create-windows-installer'])
break
case 'osx':
grunt.task.run(['compile', 'pack:osx', 'codesign', 'create-osx-installer', 'zip:osx'])
break
}
})
// Default task(s).
grunt.registerTask('default', ['build'])
}

60
hotkey.js Normal file
View File

@@ -0,0 +1,60 @@
const electron = require('electron')
const app = electron.app
const ipc = electron.ipcMain
const globalShortcut = electron.globalShortcut
const jetpack = require('fs-jetpack')
const mainWindow = require('./atom-lib/main-window')
const nodeIpc = require('@rokt33r/node-ipc')
var userDataPath = app.getPath('userData')
if (!jetpack.cwd(userDataPath).exists('keymap.json')) {
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
}
try {
global.keymap = JSON.parse(jetpack.cwd(userDataPath).read('keymap.json', 'utf-8'))
} catch (err) {
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
global.keymap = {}
}
if (global.keymap.toggleFinder == null) global.keymap.toggleFinder = 'ctrl+tab+shift'
var toggleFinderKey = global.keymap.toggleFinder
try {
globalShortcut.register(toggleFinderKey, function () {
emitToFinder('open-finder')
mainWindow.webContents.send('open-finder', {})
})
} catch (err) {
console.log(err.name)
}
ipc.on('hotkeyUpdated', function (event, newKeymap) {
console.log('got new keymap')
console.log(newKeymap)
globalShortcut.unregisterAll()
global.keymap = newKeymap
jetpack.cwd(userDataPath).file('keymap.json', {content: JSON.stringify(global.keymap)})
var toggleFinderKey = global.keymap.toggleFinder != null ? global.keymap.toggleFinder : 'ctrl+tab+shift'
try {
globalShortcut.register(toggleFinderKey, function () {
emitToFinder('open-finder')
mainWindow.webContents.send('open-finder', {})
})
mainWindow.webContents.send('APP_SETTING_DONE', {})
} catch (err) {
console.error(err)
mainWindow.webContents.send('APP_SETTING_ERROR', {
message: 'Failed to apply hotkey: Invalid format'
})
}
})
function emitToFinder (type, data) {
var payload = {
type: type,
data: data
}
nodeIpc.server.broadcast('message', payload)
}

View File

@@ -1,4 +1,6 @@
// Action types
export const USER_UPDATE = 'USER_UPDATE'
export const CLEAR_NEW_ARTICLE = 'CLEAR_NEW_ARTICLE'
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
@@ -24,6 +26,13 @@ export const EDIT_MODE = 'EDIT_MODE'
// Article status
export const NEW = 'NEW'
export function updateUser (input) {
return {
type: USER_UPDATE,
data: input
}
}
// DB
export function clearNewArticle () {
return {
@@ -137,3 +146,23 @@ export function toggleTutorial () {
type: TOGGLE_TUTORIAL
}
}
export default {
updateUser,
clearNewArticle,
updateArticle,
destroyArticle,
createFolder,
updateFolder,
destroyFolder,
replaceFolder,
switchFolder,
switchMode,
switchArticle,
setSearchFilter,
setTagFilter,
clearSearch,
lockStatus,
unlockStatus,
toggleTutorial
}

View File

@@ -1,8 +1,11 @@
import _ from 'lodash'
import moment from 'moment'
import keygen from 'boost/keygen'
import dataStore from 'boost/dataStore'
import { request, WEB_URL } from 'boost/api'
import { request, SERVER_URL } from 'boost/api'
import clientKey from 'boost/clientKey'
const electron = require('electron')
const version = electron.remote.app.getVersion()
function isSameDate (a, b) {
a = moment(a).utcOffset(+540).format('YYYYMMDD')
@@ -16,6 +19,7 @@ export function init () {
if (records == null) {
saveAllRecords([])
}
emit(null)
postRecords()
if (window != null) {
@@ -24,16 +28,6 @@ export function init () {
}
}
export function getClientKey () {
let clientKey = localStorage.getItem('clientKey')
if (!_.isString(clientKey) || clientKey.length !== 40) {
clientKey = keygen()
localStorage.setItem('clientKey', clientKey)
}
return clientKey
}
export function getAllRecords () {
return JSON.parse(localStorage.getItem('activityRecords'))
}
@@ -63,10 +57,10 @@ export function postRecords (data) {
console.log('posting...', records)
let input = {
clientKey: getClientKey(),
clientKey: clientKey.get(),
records
}
return request.post(WEB_URL + 'apis/activity')
return request.post(SERVER_URL + 'apis/activity')
.send(input)
.then(res => {
let records = getAllRecords()
@@ -81,7 +75,7 @@ export function postRecords (data) {
})
}
export function emit (type, data) {
export function emit (type, data = {}) {
let records = getAllRecords()
let index = _.findIndex(records, record => {
@@ -94,7 +88,6 @@ export function emit (type, data) {
records.push(todayRecord)
}
else todayRecord = records[index]
console.log(type)
switch (type) {
case 'ARTICLE_CREATE':
case 'ARTICLE_UPDATE':
@@ -104,6 +97,8 @@ export function emit (type, data) {
case 'FOLDER_DESTROY':
case 'FINDER_OPEN':
case 'FINDER_COPY':
case 'MAIN_DETAIL_COPY':
case 'ARTICLE_SHARE':
todayRecord[type] = todayRecord[type] == null
? 1
: todayRecord[type] + 1
@@ -111,9 +106,26 @@ export function emit (type, data) {
break
}
// Count ARTICLE_CREATE and ARTICLE_UPDATE again by syntax
if ((type === 'ARTICLE_CREATE' || type === 'ARTICLE_UPDATE') && data.mode != null) {
let recordKey = type + '_BY_SYNTAX'
if (todayRecord[recordKey] == null) todayRecord[recordKey] = {}
todayRecord[recordKey][data.mode] = todayRecord[recordKey][data.mode] == null
? 1
: todayRecord[recordKey][data.mode] + 1
}
let storeData = dataStore.getData()
todayRecord.FOLDER_COUNT = _.isArray(storeData.folders) ? storeData.folders.length : 0
todayRecord.ARTICLE_COUNT = _.isArray(storeData.articles) ? storeData.articles.length : 0
todayRecord.CLIENT_VERSION = version
todayRecord.SYNTAX_COUNT = storeData.articles.reduce((sum, article) => {
if (sum[article.mode] == null) sum[article.mode] = 1
else sum[article.mode]++
return sum
}, {})
saveAllRecords(records)
}
@@ -121,6 +133,5 @@ export function emit (type, data) {
export default {
init,
emit,
getClientKey,
postRecords
}

View File

@@ -1,191 +1,22 @@
import superagent from 'superagent'
import superagentPromise from 'superagent-promise'
import auth from 'boost/auth'
// import auth from 'boost/auth'
export const API_URL = 'http://boost-api4.elasticbeanstalk.com/'
export const WEB_URL = 'https://b00st.io/'
// export const WEB_URL = 'http://localhost:3333/'
export const SERVER_URL = 'https://b00st.io/'
// export const SERVER_URL = 'http://localhost:3333/'
export const request = superagentPromise(superagent, Promise)
export function login (input) {
export function shareViaPublicURL (input) {
return request
.post(API_URL + 'auth/login')
.send(input)
}
export function signup (input) {
return request
.post(API_URL + 'auth/register')
.send(input)
}
export function updateUserInfo (input) {
return request
.put(API_URL + 'auth/user')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function updatePassword (input) {
return request
.post(API_URL + 'auth/password')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function fetchCurrentUser () {
return request
.get(API_URL + 'auth/user')
.set({
Authorization: 'Bearer ' + auth.token()
})
}
export function fetchArticles (userId) {
return request
.get(API_URL + 'teams/' + userId + '/articles')
.set({
Authorization: 'Bearer ' + auth.token()
})
}
export function createArticle (input) {
return request
.post(API_URL + 'articles/')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function saveArticle (input) {
return request
.put(API_URL + 'articles/' + input.id)
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function destroyArticle (articleId) {
return request
.del(API_URL + 'articles/' + articleId)
.set({
Authorization: 'Bearer ' + auth.token()
})
}
export function createTeam (input) {
return request
.post(API_URL + 'teams')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function updateTeamInfo (teamId, input) {
return request
.put(API_URL + 'teams/' + teamId)
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function destroyTeam (teamId) {
return request
.del(API_URL + 'teams/' + teamId)
.set({
Authorization: 'Bearer ' + auth.token()
})
}
export function searchUser (key) {
return request
.get(API_URL + 'search/users')
.query({key: key})
}
export function setMember (teamId, input) {
return request
.post(API_URL + 'teams/' + teamId + '/members')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function deleteMember (teamId, input) {
return request
.del(API_URL + 'teams/' + teamId + '/members')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function createFolder (input) {
return request
.post(API_URL + 'folders/')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function updateFolder (id, input) {
return request
.put(API_URL + 'folders/' + id)
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function destroyFolder (id) {
return request
.del(API_URL + 'folders/' + id)
.set({
Authorization: 'Bearer ' + auth.token()
})
}
export function sendEmail (input) {
return request
.post(API_URL + 'mail')
.set({
Authorization: 'Bearer ' + auth.token()
})
.post(SERVER_URL + 'apis/share')
// .set({
// Authorization: 'Bearer ' + auth.token()
// })
.send(input)
}
export default {
API_URL,
WEB_URL,
request,
login,
signup,
updateUserInfo,
updatePassword,
fetchCurrentUser,
fetchArticles,
createArticle,
saveArticle,
destroyArticle,
createTeam,
updateTeamInfo,
destroyTeam,
searchUser,
setMember,
deleteMember,
createFolder,
updateFolder,
destroyFolder,
sendEmail
SERVER_URL,
shareViaPublicURL
}

23
lib/clientKey.js Normal file
View File

@@ -0,0 +1,23 @@
import _ from 'lodash'
import keygen from 'boost/keygen'
function getClientKey () {
let clientKey = localStorage.getItem('clientKey')
if (!_.isString(clientKey) || clientKey.length !== 40) {
clientKey = keygen()
setClientKey(clientKey)
}
return clientKey
}
function setClientKey (newKey) {
localStorage.setItem('clientKey', newKey)
}
getClientKey()
export default {
get: getClientKey,
set: setClientKey
}

View File

@@ -30,6 +30,16 @@ module.exports = React.createClass({
editor.renderer.setShowGutter(true)
editor.setTheme('ace/theme/xcode')
editor.clearSelection()
editor.moveCursorTo(0, 0)
editor.commands.addCommand({
name: 'Emacs cursor up',
bindKey: {mac: 'Ctrl-P'},
exec: function (editor) {
editor.navigateUp(1)
if (editor.getCursorPosition().row < editor.getFirstVisibleRow()) editor.scrollToLine(editor.getCursorPosition().row, false, false)
},
readOnly: true
})
editor.setReadOnly(!!this.props.readOnly)
@@ -50,16 +60,14 @@ module.exports = React.createClass({
this.props.onChange(e, value)
}
}.bind(this))
this.setState({editor: editor})
},
componentDidUpdate: function (prevProps) {
if (this.state.editor.getValue() !== this.props.code) {
this.state.editor.setValue(this.props.code)
this.state.editor.clearSelection()
if (this.editor.getValue() !== this.props.code) {
this.editor.setValue(this.props.code)
this.editor.clearSelection()
}
if (prevProps.mode !== this.props.mode) {
var session = this.state.editor.getSession()
var session = this.editor.getSession()
let mode = _.findWhere(modes, {name: this.props.mode})
let syntaxMode = mode != null
? mode.mode
@@ -67,6 +75,18 @@ module.exports = React.createClass({
session.setMode('ace/mode/' + syntaxMode)
}
},
getFirstVisibleRow: function () {
return this.editor.getFirstVisibleRow()
},
getCursorPosition: function () {
return this.editor.getCursorPosition()
},
moveCursorTo: function (row, col) {
this.editor.moveCursorTo(row, col)
},
scrollToLine: function (num) {
this.editor.scrollToLine(num, false, false)
},
render: function () {
return (
<div ref='target' className={this.props.className == null ? 'CodeEditor' : 'CodeEditor ' + this.props.className}></div>

View File

@@ -161,8 +161,8 @@ export default class ModeSelect extends React.Component {
let filteredOptions = modes
.filter(mode => {
let search = this.state.search
let nameMatched = mode.name.match(search)
let aliasMatched = _.some(mode.alias, alias => alias.match(search))
let nameMatched = mode.name.match(_.escapeRegExp(search))
let aliasMatched = _.some(mode.alias, alias => alias.match(_.escapeRegExp(search)))
return nameMatched || aliasMatched
})
.map((mode, index) => {

View File

@@ -1,85 +0,0 @@
import React, { PropTypes, findDOMNode } from 'react'
import linkState from 'boost/linkState'
import { sendEmail } from 'boost/api'
export default class ContactModal extends React.Component {
constructor (props) {
super(props)
this.linkState = linkState
this.state = {
isSent: false,
mail: {
title: '',
content: ''
}
}
}
onKeyCast (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
case 'submitContactModal':
if (this.state.isSent) {
this.props.close()
return
}
this.sendEmail()
break
}
}
componentDidMount () {
findDOMNode(this.refs.title).focus()
}
sendEmail () {
sendEmail(this.state.mail)
.then(function (res) {
this.setState({isSent: !this.state.isSent})
}.bind(this))
.catch(function (err) {
console.error(err)
})
}
render () {
return (
<div className='ContactModal modal'>
<div className='modal-header'><h1>Contact form</h1></div>
{!this.state.isSent ? (
<div className='contactForm'>
<div className='modal-body'>
<div className='formField'>
<input ref='title' valueLink={this.linkState('mail.title')} placeholder='Title'/>
</div>
<div className='formField'>
<textarea valueLink={this.linkState('mail.content')} placeholder='Content'/>
</div>
</div>
<div className='modal-footer'>
<div className='formControl'>
<button onClick={this.sendEmail} className='sendButton'>Send</button>
<button onClick={this.props.close}>Cancel</button>
</div>
</div>
</div>
) : (
<div className='confirmation'>
<div className='confirmationMessage'>Thanks for sharing your opinion!</div>
<button className='doneButton' onClick={this.props.close}>Done</button>
</div>
)}
</div>
)
}
}
ContactModal.propTypes = {
close: PropTypes.func
}

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import linkState from 'boost/linkState'
import { createFolder } from 'boost/actions'
import store from 'boost/store'
@@ -15,6 +16,10 @@ export default class CreateNewFolder extends React.Component {
}
}
componentDidMount () {
ReactDOM.findDOMNode(this.refs.folderName).focus()
}
handleCloseButton (e) {
this.props.close()
}
@@ -84,7 +89,7 @@ export default class CreateNewFolder extends React.Component {
<div className='title'>Create new folder</div>
<input onKeyDown={e => this.handleKeyDown(e)} className='ipt' type='text' valueLink={this.linkState('name')} placeholder='Enter folder name'/>
<input ref='folderName' onKeyDown={e => this.handleKeyDown(e)} className='ipt' type='text' valueLink={this.linkState('name')} placeholder='Enter folder name'/>
<div className='colorSelect'>
{colorElements}
</div>

View File

@@ -1,8 +1,13 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import store from 'boost/store'
import { unlockStatus, clearNewArticle } from 'boost/actions'
export default class EditedAlert extends React.Component {
componentDidMount () {
ReactDOM.findDOMNode(this.refs.no).focus()
}
handleNoButtonClick (e) {
this.props.close()
}
@@ -22,8 +27,8 @@ export default class EditedAlert extends React.Component {
<div className='message'>Do you really want to leave without finishing?</div>
<div className='control'>
<button onClick={e => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close'/> No</button>
<button onClick={e => this.handleYesButtonClick(e)} className='primary'><i className='fa fa-fw fa-check'/> Yes</button>
<button ref='no' onClick={e => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close'/> No</button>
<button ref='yes' onClick={e => this.handleYesButtonClick(e)} className='primary'><i className='fa fa-fw fa-check'/> Yes</button>
</div>
</div>
)

View File

@@ -1,5 +1,6 @@
import React from 'react'
import React, { PropTypes } from 'react'
import linkState from 'boost/linkState'
import { updateUser } from 'boost/actions'
const electron = require('electron')
const ipc = electron.ipcRenderer
@@ -9,24 +10,32 @@ export default class AppSettingTab extends React.Component {
constructor (props) {
super(props)
let keymap = remote.getGlobal('keymap')
let userName = props.user != null ? props.user.name : null
this.state = {
toggleFinder: keymap.toggleFinder,
alert: null
user: {
name: userName,
alert: null
},
userAlert: null,
keymap: {
toggleFinder: keymap.toggleFinder
},
keymapAlert: null
}
}
componentDidMount () {
this.handleSettingDone = () => {
this.setState({alert: {
this.setState({keymapAlert: {
type: 'success',
message: 'Successfully done!'
}})
}
this.handleSettingError = err => {
this.setState({alert: {
this.setState({keymapAlert: {
type: 'error',
message: err.message
message: err.message != null ? err.message : 'Error occurs!'
}})
}
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
@@ -40,7 +49,7 @@ export default class AppSettingTab extends React.Component {
submitHotKey () {
ipc.send('hotkeyUpdated', {
toggleFinder: this.state.toggleFinder
toggleFinder: this.state.keymap.toggleFinder
})
}
@@ -54,25 +63,56 @@ export default class AppSettingTab extends React.Component {
}
}
handleNameSaveButtonClick (e) {
let { dispatch } = this.props
dispatch(updateUser({name: this.state.user.name}))
this.setState({
userAlert: {
type: 'success',
message: 'Successfully done!'
}
})
}
render () {
let alert = this.state.alert
let alertElement = alert != null ? (
<p className={`alert ${alert.type}`}>
{alert.message}
let keymapAlert = this.state.keymapAlert
let keymapAlertElement = keymapAlert != null
? (
<p className={`alert ${keymapAlert.type}`}>
{keymapAlert.message}
</p>
) : null
let userAlert = this.state.userAlert
let userAlertElement = userAlert != null
? (
<p className={`alert ${userAlert.type}`}>
{userAlert.message}
</p>
) : null
return (
<div className='AppSettingTab content'>
<div className='section'>
<div className='sectionTitle'>User's info</div>
<div className='sectionInput'>
<label>User name</label>
<input valueLink={this.linkState('user.name')} type='text'/>
</div>
<div className='sectionConfirm'>
<button onClick={e => this.handleNameSaveButtonClick(e)}>Save</button>
{userAlertElement}
</div>
</div>
<div className='section'>
<div className='sectionTitle'>Hotkey</div>
<div className='sectionInput'>
<label>Toggle Finder(popup)</label>
<input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('toggleFinder')} type='text'/>
<input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('keymap.toggleFinder')} type='text'/>
</div>
<div className='sectionConfirm'>
<button onClick={e => this.handleSaveButtonClick(e)}>Save</button>
{alertElement}
{keymapAlertElement}
</div>
<div className='description'>
<ul>
@@ -101,3 +141,6 @@ export default class AppSettingTab extends React.Component {
}
AppSettingTab.prototype.linkState = linkState
AppSettingTab.propTypes = {
dispatch: PropTypes.func
}

View File

@@ -62,7 +62,7 @@ class Preferences extends React.Component {
}
renderContent () {
let { folders, dispatch } = this.props
let { user, folders, dispatch } = this.props
switch (this.state.currentTab) {
case HELP:
@@ -80,12 +80,20 @@ class Preferences extends React.Component {
)
case APP:
default:
return (<AppSettingTab/>)
return (
<AppSettingTab
user={user}
dispatch={dispatch}
/>
)
}
}
}
Preferences.propTypes = {
user: PropTypes.shape({
name: PropTypes.string
}),
folders: PropTypes.array,
dispatch: PropTypes.func
}
@@ -93,9 +101,10 @@ Preferences.propTypes = {
Preferences.prototype.linkState = linkState
function remap (state) {
let { folders, status } = state
let { user, folders, status } = state
return {
user,
folders,
status
}

View File

@@ -92,7 +92,7 @@ export default class Tutorial extends React.Component {
There is a short-cut key [control + shift + tab] to open the Finder.<br/>
It is available to save your articles on the Clipboard<br/>
by selecting your file with pressing Enter key,<br/>
and to paste the contents of the Clipboard with [Command-V]
and to paste the contents of the Clipboard with [{process.platform === 'darwin' ? 'Command' : 'Control'}-V]
<img width='480' src='../../resources/finder.png'/>
</div>

View File

@@ -1,4 +1,6 @@
import keygen from 'boost/keygen'
import _ from 'lodash'
const electron = require('electron')
const remote = electron.remote
const jetpack = require('fs-jetpack')
@@ -10,15 +12,74 @@ function getLocalPath () {
return path.join(remote.app.getPath('userData'), 'local.json')
}
function forgeInitialRepositories () {
let defaultRepo = {
key: keygen(),
name: 'local',
type: 'userData',
user: {
name: 'New user'
}
}
if (process.platform === 'darwin') {
defaultRepo.user.name = remote.process.env.USER
} else if (process.platform === 'win32') {
defaultRepo.user.name = remote.process.env.USERNAME
}
return [defaultRepo]
}
function getRepositories () {
let raw = localStorage.getItem('repositories')
try {
let parsed = JSON.parse(raw)
if (!_.isArray(parsed)) {
throw new Error('repositories data is currupte. re-init data.')
}
return parsed
} catch (e) {
console.log(e)
let newRepos = forgeInitialRepositories()
saveRepositories(newRepos)
return newRepos
}
}
function saveRepositories (repos) {
localStorage.setItem('repositories', JSON.stringify(repos))
}
export function getUser (repoName) {
if (repoName == null) {
return getRepositories()[0]
}
return null
}
export function saveUser (repoName, user) {
let repos = getRepositories()
if (repoName == null) {
Object.assign(repos[0].user, user)
}
saveRepositories(repos)
}
export function init () {
console.log('initialize data store')
// set repositories info
getRepositories()
// set local.json
let data = jetpack.read(getLocalPath(), 'json')
if (data == null) {
// for 0.4.1 -> 0.4.2
if (localStorage.getItem('local') != null) {
data = JSON.parse(localStorage.getItem('local'))
jetpack.write(getLocalPath(), data)
localStorage.removeItem('local')
console.log('update 0.4.1 => 0.4.2')
return
}
@@ -70,6 +131,8 @@ export default (function () {
init()
}
return {
getUser,
saveUser,
init,
getData,
setArticles,

View File

@@ -2,6 +2,6 @@ var crypto = require('crypto')
module.exports = function () {
var shasum = crypto.createHash('sha1')
shasum.update(((new Date()).getTime()).toString())
shasum.update(((new Date()).getTime() + Math.round(Math.random()*1000)).toString())
return shasum.digest('hex')
}

View File

@@ -8,20 +8,31 @@ var md = markdownit({
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(lang, str).value;
return hljs.highlight(lang, str).value
} catch (__) {}
}
try {
return hljs.highlightAuto(str).value;
return hljs.highlightAuto(str).value
} catch (__) {}
return ''; // use external default escaping
return ''
}
})
md.use(emoji)
let originalRenderToken = md.renderer.renderToken
md.renderer.renderToken = function renderToken (tokens, idx, options) {
let token = tokens[idx]
let result = originalRenderToken.call(md.renderer, tokens, idx, options)
if (token.map != null) {
return result + '<a class=\'lineAnchor\' data-key=\'' + token.map[0] + '\'></a>'
}
return result
}
export default function markdown (content) {
if (content == null) content = ''
return md.render(content.toString())
}

View File

@@ -12,6 +12,9 @@ import {
UNLOCK_STATUS,
TOGGLE_TUTORIAL,
// user
USER_UPDATE,
// Article action type
ARTICLE_UPDATE,
ARTICLE_DESTROY,
@@ -42,10 +45,22 @@ const initialStatus = {
let data = dataStore.getData()
let initialArticles = data.articles
let initialFolders = data.folders
let initialUser = dataStore.getUser().user
let isStatusLocked = false
let isCreatingNew = false
function user (state = initialUser, action) {
switch (action.type) {
case USER_UPDATE:
let updated = Object.assign(state, action.data)
dataStore.saveUser(null, updated)
return updated
default:
return state
}
}
function folders (state = initialFolders, action) {
state = state.slice()
switch (action.type) {
@@ -119,6 +134,7 @@ function folders (state = initialFolders, action) {
state.splice(a, 1, folderB)
state.splice(b, 1, folderA)
}
dataStore.setFolders(state)
return state
default:
return state
@@ -165,10 +181,10 @@ function articles (state = initialArticles, action) {
let targetIndex = _.findIndex(state, _article => article.key === _article.key)
if (targetIndex < 0) state.unshift(article)
else state.splice(targetIndex, 1, article)
else Object.assign(state[targetIndex], article)
if (article.status !== 'NEW') dataStore.setArticles(state)
else isCreatingNew = true
else isCreatingNew = true
return state
}
case ARTICLE_DESTROY:
@@ -250,6 +266,7 @@ function status (state = initialStatus, action) {
}
export default combineReducers({
user,
folders,
articles,
status

0
lib/updater.js Normal file
View File

View File

@@ -68,7 +68,7 @@ const modes = [
{
name: 'csharp',
label: 'C#',
alias: ['cs'],
alias: ['cs', 'c#'],
mode: 'csharp'
},
{

366
main.js
View File

@@ -2,21 +2,102 @@ const electron = require('electron')
const app = electron.app
const Menu = electron.Menu
const ipc = electron.ipcMain
const globalShortcut = electron.globalShortcut
const autoUpdater = electron.autoUpdater
const jetpack = require('fs-jetpack')
const path = require('path')
const ChildProcess = require('child_process')
electron.crashReporter.start()
const _ = require('lodash')
// electron.crashReporter.start()
var mainWindow = null
var finderProcess
var finderProcess = null
var finderWindow = null
var update = null
// app.on('window-all-closed', function () {
// if (process.platform !== 'darwin') app.quit()
// })
const appRootPath = path.join(process.execPath, '../..')
const updateDotExePath = path.join(appRootPath, 'Update.exe')
const exeName = path.basename(process.execPath)
function spawnUpdate (args, cb) {
var stdout = ''
var updateProcess = null
try {
updateProcess = ChildProcess.spawn(updateDotExePath, args)
} catch (e) {
process.nextTick(function () {
cb(e)
})
}
updateProcess.stdout.on('data', function (data) {
stdout += data
})
error = null
updateProcess.on('error', function (_error) {
error = _error
})
updateProcess.on('close', function (code, signal) {
if (code !== 0) {
error = new Error("Command failed: #{signal ? code}")
error.code = code
error.stdout = stdout
}
cb(error, stdout)
})
}
var handleStartupEvent = function () {
if (process.platform !== 'win32') {
return false
}
var squirrelCommand = process.argv[1]
switch (squirrelCommand) {
case '--squirrel-install':
spawnUpdate(['--createShortcut', exeName], function (err) {
quitApp()
})
return true
case '--squirrel-updated':
quitApp()
return true
case '--squirrel-uninstall':
spawnUpdate(['--removeShortcut', exeName], function (err) {
quitApp()
})
quitApp()
return true
case '--squirrel-obsolete':
quitApp()
return true
}
}
if (handleStartupEvent()) {
return
}
var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) {
if (mainWindow) {
if (process.platform === 'win32') {
mainWindow.minimize()
mainWindow.restore()
}
mainWindow.focus()
}
return true
})
if (shouldQuit) {
quitApp()
return
}
var appQuit = false
var version = app.getVersion()
@@ -32,171 +113,186 @@ function notify (title, body) {
}
}
autoUpdater
.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
update = quitAndUpdate
var isUpdateReady = false
var GhReleases = require('electron-gh-releases')
if (mainWindow != null) {
notify('Ready to Update! ' + releaseName, 'Click update button on Main window.')
mainWindow.webContents.send('update-available', 'Update available!')
}
})
.on('error', function (err, message) {
console.error(err)
if (!versionNotified) {
notify('Updater error!', message)
}
})
// .on('checking-for-update', function () {
// // Connecting
// console.log('checking...')
// })
.on('update-available', function () {
notify('Update is available!', 'Download started.. wait for the update ready.')
})
.on('update-not-available', function () {
if (mainWindow != null && !versionNotified) {
versionNotified = true
notify('Latest Build!! ' + versionText, 'Hope you to enjoy our app :D')
var ghReleasesOpts = {
repo: 'BoostIO/boost-releases',
currentVersion: app.getVersion()
}
const updater = new GhReleases(ghReleasesOpts)
// Check for updates
// `status` returns true if there is a new update available
function checkUpdate () {
updater.check((err, status) => {
if (err) {
console.error(err)
if (!versionNotified) notify('Updater error!', message)
}
if (!err) {
if (status) {
notify('Update is available!', 'Download started.. wait for the update ready.')
updater.download()
} else {
if (!versionNotified) {
versionNotified = true
notify('Latest Build!! ' + versionText, 'Hope you to enjoy our app :D')
}
}
}
})
}
updater.on('update-downloaded', (info) => {
if (mainWindow != null) {
notify('Ready to Update!', 'Click update button on Main window.')
mainWindow.webContents.send('update-available', 'Update available!')
isUpdateReady = true
}
})
const nodeIpc = require('@rokt33r/node-ipc')
nodeIpc.config.id = 'node'
nodeIpc.config.retry = 1500
// nodeIpc.config.silent = true
function spawnFinder() {
if (process.platform === 'darwin') {
var finderArgv = [path.join(__dirname, 'finder.js'), '--finder']
if (_.find(process.argv, a => a === '--hot')) finderArgv.push('--hot')
finderProcess = ChildProcess
.execFile(process.execPath, finderArgv)
}
}
nodeIpc.serve(
path.join(app.getPath('userData'), 'boost.service'),
function () {
nodeIpc.server.on(
'connect',
function (socket) {
socket.on('close', function () {
console.log('socket dead')
if (!appQuit) spawnFinder()
})
}
)
nodeIpc.server.on(
'message',
function (data, socket) {
console.log('>>', data)
format(data)
}
)
nodeIpc.server.on(
'error',
function (err) {
console.log('>>', err)
}
)
}
)
function format (payload) {
switch (payload.type) {
case 'show-main-window':
if (process.platform === 'darwin') {
mainWindow.show()
} else {
mainWindow.minimize()
mainWindow.restore()
}
break
case 'copy-finder':
mainWindow.webContents.send('copy-finder')
break
case 'quit-app':
quitApp()
break
}
}
function quitApp () {
appQuit = true
if (finderProcess) finderProcess.kill()
app.quit()
}
app.on('ready', function () {
app.on('before-quit', function () {
if (finderProcess) finderProcess.kill()
console.log('before quite')
appQuit = true
if (finderProcess) finderProcess.kill()
})
autoUpdater.setFeedURL('https://orbital.b00st.io/rokt33r/boost-dev/latest?version=' + version)
var template = require('./atom-lib/menu-template')
if (process.platform === 'win32') {
template.unshift({
label: 'Boostnote',
submenu: [
{
label: 'Quit',
accelerator: 'Control+Q',
click: function (e) {
quitApp()
}
}
]
})
}
var menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
if (process.platform === 'darwin') {
Menu.setApplicationMenu(menu)
}
setInterval(function () {
if (update == null) autoUpdater.checkForUpdates()
checkUpdate()
}, 1000 * 60 * 60 * 24)
ipc.on('check-update', function (event, msg) {
if (update == null) autoUpdater.checkForUpdates()
if (update == null) checkUpdate()
})
ipc.on('update-app', function (event, msg) {
if (update != null) {
if (isUpdateReady) {
appQuit = true
update()
updater.install()
}
})
checkUpdate()
mainWindow = require('./atom-lib/main-window')
if (process.platform === 'win32') {
mainWindow.setMenu(menu)
}
mainWindow.on('close', function (e) {
if (appQuit) return true
e.preventDefault()
mainWindow.hide()
})
mainWindow.webContents.on('did-finish-load', function () {
if (finderProcess == null) {
finderProcess = ChildProcess
.execFile(process.execPath, [path.resolve(__dirname, 'finder.js'), '--finder'])
finderProcess.stdout.setEncoding('utf8')
finderProcess.stderr.setEncoding('utf8')
finderProcess.stdout.on('data', format)
finderProcess.stderr.on('data', errorFormat)
}
if (update != null) {
mainWindow.webContents.send('update-available', 'whoooooooh!')
} else {
autoUpdater.checkForUpdates()
}
})
if (finderProcess == null && process.platform === 'darwin') {
console.log('fired only once ')
spawnFinder()
} else {
finderWindow = require('./atom-lib/finder-window')
app.on('activate', function () {
if (mainWindow == null) return null
mainWindow.show()
})
function format (payload) {
// console.log('from finder >> ', payload)
try {
payload = JSON.parse(payload)
} catch (e) {
console.log('Not parsable payload : ', payload)
return
}
switch (payload.type) {
case 'log':
console.log('FINDER(stdout): ' + payload.data)
break
case 'show-main-window':
if (mainWindow != null) {
mainWindow.show()
}
break
case 'request-data':
mainWindow.webContents.send('request-data')
break
case 'quit-app':
appQuit = true
app.quit()
break
}
}
function errorFormat (output) {
console.error('FINDER(stderr):' + output)
}
function emitToFinder (type, data) {
if (!finderProcess) {
console.log('finder process is not ready')
return
}
var payload = {
type: type,
data: data
}
finderProcess.stdin.write(JSON.stringify(payload), 'utf-8')
}
var userDataPath = app.getPath('userData')
if (!jetpack.cwd(userDataPath).exists('keymap.json')) {
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
}
try {
global.keymap = JSON.parse(jetpack.cwd(userDataPath).read('keymap.json', 'utf-8'))
} catch (err) {
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
global.keymap = {}
}
if (global.keymap.toggleFinder == null) global.keymap.toggleFinder = 'ctrl+tab+shift'
var toggleFinderKey = global.keymap.toggleFinder
try {
globalShortcut.register(toggleFinderKey, function () {
emitToFinder('open-finder')
finderWindow.on('close', function (e) {
if (appQuit) return true
e.preventDefault()
finderWindow.hide()
})
} catch (err) {
console.log(err.name)
}
ipc.on('hotkeyUpdated', function (event, newKeymap) {
console.log('got new keymap')
console.log(newKeymap)
globalShortcut.unregisterAll()
global.keymap = newKeymap
jetpack.cwd(userDataPath).file('keymap.json', {content: JSON.stringify(global.keymap)})
var toggleFinderKey = global.keymap.toggleFinder != null ? global.keymap.toggleFinder : 'ctrl+tab+shift'
try {
globalShortcut.register(toggleFinderKey, function () {
emitToFinder('open-finder')
})
mainWindow.webContents.send('APP_SETTING_DONE', {})
} catch (err) {
console.error(err)
mainWindow.webContents.send('APP_SETTING_ERROR', {
message: 'Failed to apply hotkey: Invalid format'
})
nodeIpc.server.start(function (err) {
if (err.code === 'EADDRINUSE') {
notify('Error occurs!', 'You have to kill other Boostnote processes.')
quitApp()
}
})
require('./hotkey')
})

1
node_modules/boost generated vendored
View File

@@ -1 +0,0 @@
../lib

View File

@@ -1,19 +1,15 @@
{
"name": "boost",
"version": "0.4.2-rc.0",
"description": "Boost App",
"version": "0.5.0",
"description": "Boostnote",
"main": "index.js",
"scripts": {
"start": "BOOST_ENV=development electron ./main.js",
"webpack": "webpack-dev-server --hot --inline --config webpack.config.js",
"compile": "NODE_ENV=production webpack --config webpack.config.production.js",
"build": "electron-packager ./ Boost --app-version=$npm_package_version $npm_package_config_platform $npm_package_config_version $npm_package_config_ignore --overwrite --asar",
"codesign": "codesign --verbose --deep --force --sign \"MAISIN solutions Inc.\" Boost-darwin-x64/Boost.app"
"start": "electron ./index.js",
"hot": "electron ./index.js --hot",
"webpack": "webpack-dev-server --hot --inline --config webpack.config.js"
},
"config": {
"version": "--version=0.35.1 --app-bundle-id=com.maisin.boost",
"platform": "--platform=darwin --arch=x64 --prune --icon=resources/app.icns",
"ignore": "--ignore=Boost-darwin-x64 --ignore=node_modules/devicon/icons --ignore=submodules/ace/(?!src-min)|submodules/ace/(?=src-min-noconflict)"
"electron-version": "0.35.4"
},
"repository": {
"type": "git",
@@ -31,13 +27,14 @@
"short code"
],
"author": "Dick Choi <fluke8259@gmail.com> (http://kazup.co)",
"license": "No License",
"bugs": {
"url": "https://github.com/Rokt33r/codexen-app/issues"
},
"homepage": "https://github.com/Rokt33r/codexen-app#readme",
"dependencies": {
"@rokt33r/node-ipc": "^5.0.4",
"devicon": "^2.0.0",
"electron-gh-releases": "^2.0.2",
"font-awesome": "^4.3.0",
"fs-jetpack": "^0.7.0",
"highlight.js": "^8.9.1",
@@ -56,6 +53,9 @@
"css-loader": "^0.19.0",
"electron-packager": "^5.1.0",
"electron-prebuilt": "^0.35.1",
"electron-release": "^2.2.0",
"grunt": "^0.4.5",
"grunt-electron-installer": "^1.2.0",
"nib": "^1.1.0",
"react": "^0.14.0",
"react-dom": "^0.14.0",
@@ -72,6 +72,9 @@
"webpack": "^1.12.2",
"webpack-dev-server": "^1.12.0"
},
"optionalDependencies": {
"appdmg": "^0.3.5"
},
"standard": {
"ignore": [],
"globals": [

BIN
resources/app.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

BIN
resources/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
resources/boostnote-install.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
resources/boostnote-install.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 KiB

BIN
resources/dmg.icns Normal file

Binary file not shown.

BIN
resources/dmg.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

BIN
resources/dmg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -3,6 +3,7 @@ var path = require('path')
var JsonpTemplatePlugin = webpack.JsonpTemplatePlugin
var FunctionModulePlugin = require('webpack/lib/FunctionModulePlugin')
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin')
var opt = {
path: path.join(__dirname, 'compiled'),
filename: '[name].js',
@@ -10,6 +11,7 @@ var opt = {
libraryTarget: 'commonjs2',
publicPath: 'http://localhost:8080/assets/'
}
var config = {
module: {
loaders: [
@@ -34,7 +36,10 @@ var config = {
output: opt,
resolve: {
extensions: ['', '.js', '.jsx'],
packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main']
packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'],
alias: {
'boost': path.resolve(__dirname, 'lib')
}
},
plugins: [
new webpack.NoErrorsPlugin(),

View File

@@ -1,15 +1,18 @@
var webpack = require('webpack')
module.exports = {
entry: {
main: './browser/main/index.js',
finder: './browser/finder/index.js'
},
output: {
path: 'compiled',
filename: '[name].js',
// sourceMapFilename: '[name].map',
libraryTarget: 'commonjs2'
},
var path = require('path')
var JsonpTemplatePlugin = webpack.JsonpTemplatePlugin
var FunctionModulePlugin = require('webpack/lib/FunctionModulePlugin')
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin')
var opt = {
path: path.join(__dirname, 'compiled'),
filename: '[name].js',
sourceMapFilename: '[name].map',
libraryTarget: 'commonjs2',
publicPath: 'http://localhost:8080/assets/'
}
var config = {
module: {
loaders: [
{
@@ -24,11 +27,26 @@ module.exports = {
}
]
},
entry: {
main: './browser/main/index.js',
finder: './browser/finder/index.js'
},
output: opt,
resolve: {
extensions: ['', '.js', '.jsx'],
packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'],
alias: {
'boost': path.resolve(__dirname, 'lib')
}
},
plugins: [
new webpack.NoErrorsPlugin(),
new NodeTargetPlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
'NODE_ENV': JSON.stringify('production'),
'BABEL_ENV': JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin({
@@ -49,8 +67,14 @@ module.exports = {
'highlight.js',
'markdown-it-emoji',
'fs-jetpack'
],
resolve: {
extensions: ['', '.js', '.jsx', 'styl']
}
]
}
config.target = function renderer (compiler) {
compiler.apply(
new JsonpTemplatePlugin(opt),
new FunctionModulePlugin(opt)
)
}
module.exports = config