├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── build ├── css │ └── bootstrap.min.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── html │ ├── change_logs.html │ ├── emoticon.html │ ├── features.html │ ├── group.html │ ├── notification.html │ ├── popup.html │ ├── room.html │ ├── setting.html │ └── shortcut.html ├── img │ ├── icon128.png │ ├── icon16.png │ ├── icon256.png │ ├── icon32.png │ └── icon64.png ├── js │ ├── extensions │ │ ├── background.js │ │ ├── contentscript.js │ │ └── injectPreloadHook.js │ ├── externals │ │ ├── libs.js │ │ ├── pages.js │ │ └── popup.js │ ├── internals │ │ └── all.js │ └── libraries │ │ ├── caretposition.js │ │ ├── fuse.min.js │ │ └── jquery.min.js └── manifest.json ├── changelogs.md ├── features.md ├── gulpfile.js ├── images ├── 1.png ├── 2.png ├── 3.png ├── 4.png └── 5.png ├── mix-manifest.json ├── package-lock.json ├── package.json ├── src ├── css │ ├── bootstrap.min.css │ └── highlight.min.css └── js │ ├── extensions │ ├── background.js │ ├── contentscript.js │ └── injectPreloadHook.js │ ├── externals │ ├── emoticon.js │ ├── group.js │ ├── notification.js │ ├── popup.js │ ├── room.js │ ├── setting.js │ └── shortcut.js │ ├── helpers │ ├── ChatworkFacade.js │ ├── ChromeStorageLocal.js │ ├── Common.js │ ├── Const.js │ ├── EmoStorage.js │ └── Storage.js │ ├── internals │ ├── Advertisement.js │ ├── Chatpp.code-workspace │ ├── Emoticon.js │ ├── Mention.js │ ├── NotificationDisabler.js │ ├── NotifyAll.js │ ├── RoomInformation.js │ ├── Shortcut.js │ ├── ViewEnhancer.js │ └── main.js │ └── libraries │ ├── bootbox.min.js │ ├── bootstrap.min.js │ ├── chatwork.min.js │ └── chatwork.min.new.js ├── webpack.mix.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | src/js/libraries/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "es6": true, 9 | "amd": true, 10 | "jquery": true, 11 | "browser": true, 12 | "node": true 13 | }, 14 | "globals": { 15 | "chrome": true, 16 | "CW": true, 17 | "AC": true, 18 | "FL": true, 19 | "TK": true, 20 | "RL": true, 21 | "RD": true, 22 | "RS": true, 23 | "ST": true, 24 | "CS": true, 25 | "SC": true, 26 | "AL": true, 27 | "ST": true, 28 | "RM": true, 29 | "L": true, 30 | "_is_mac": true, 31 | "LANGUAGE": true, 32 | "TimeLineView": true, 33 | "RoomView": true, 34 | "Room": true, 35 | "bootbox": true, 36 | "Fuse": true, 37 | "Measurement": true, 38 | "hljs": true, 39 | "emoticons": true, 40 | "tokenizer": true 41 | }, 42 | "rules": { 43 | "no-alert": 0, 44 | "no-array-constructor": 0, 45 | "no-bitwise": 0, 46 | "no-caller": 0, 47 | "no-case-declarations": 2, 48 | "no-catch-shadow": 0, 49 | "no-class-assign": 2, 50 | "no-cond-assign": 2, 51 | "no-confusing-arrow": 0, 52 | "no-console": 2, 53 | "no-const-assign": 2, 54 | "no-constant-condition": 2, 55 | "no-continue": 0, 56 | "no-control-regex": 2, 57 | "no-debugger": 2, 58 | "no-delete-var": 2, 59 | "no-div-regex": 0, 60 | "no-dupe-class-members": 2, 61 | "no-dupe-keys": 2, 62 | "no-dupe-args": 2, 63 | "no-duplicate-case": 2, 64 | "no-else-return": 0, 65 | "no-empty": 2, 66 | "no-empty-character-class": 2, 67 | "no-empty-function": 0, 68 | "no-empty-pattern": 2, 69 | "no-eq-null": 0, 70 | "no-eval": 0, 71 | "no-ex-assign": 2, 72 | "no-extend-native": 0, 73 | "no-extra-bind": 0, 74 | "no-extra-boolean-cast": 2, 75 | "no-extra-label": 0, 76 | "no-extra-parens": 0, 77 | "no-extra-semi": 2, 78 | "no-fallthrough": 2, 79 | "no-floating-decimal": 0, 80 | "no-func-assign": 2, 81 | "no-implicit-coercion": 0, 82 | "no-implicit-globals": 0, 83 | "no-implied-eval": 0, 84 | "no-inline-comments": 0, 85 | "no-inner-declarations": 2, 86 | "no-invalid-regexp": 2, 87 | "no-invalid-this": 0, 88 | "no-irregular-whitespace": 2, 89 | "no-iterator": 0, 90 | "no-label-var": 0, 91 | "no-labels": 0, 92 | "no-lone-blocks": 0, 93 | "no-lonely-if": 0, 94 | "no-loop-func": 0, 95 | "no-mixed-requires": 0, 96 | "no-mixed-spaces-and-tabs": 2, 97 | "linebreak-style": 0, 98 | "no-multi-spaces": 0, 99 | "no-multi-str": 0, 100 | "no-multiple-empty-lines": 0, 101 | "no-native-reassign": 0, 102 | "no-negated-condition": 0, 103 | "no-negated-in-lhs": 2, 104 | "no-nested-ternary": 0, 105 | "no-new": 0, 106 | "no-new-func": 0, 107 | "no-new-object": 0, 108 | "no-new-require": 0, 109 | "no-new-symbol": 2, 110 | "no-new-wrappers": 0, 111 | "no-obj-calls": 2, 112 | "no-octal": 2, 113 | "no-octal-escape": 0, 114 | "no-param-reassign": 0, 115 | "no-path-concat": 0, 116 | "no-plusplus": 0, 117 | "no-process-env": 0, 118 | "no-process-exit": 0, 119 | "no-proto": 0, 120 | "no-redeclare": 2, 121 | "no-regex-spaces": 2, 122 | "no-restricted-imports": 0, 123 | "no-restricted-modules": 0, 124 | "no-restricted-syntax": 0, 125 | "no-return-assign": 0, 126 | "no-script-url": 0, 127 | "no-self-assign": 2, 128 | "no-self-compare": 0, 129 | "no-sequences": 0, 130 | "no-shadow": 0, 131 | "no-shadow-restricted-names": 0, 132 | "no-whitespace-before-property": 0, 133 | "no-spaced-func": 0, 134 | "no-sparse-arrays": 2, 135 | "no-sync": 0, 136 | "no-ternary": 0, 137 | "no-trailing-spaces": 0, 138 | "no-this-before-super": 2, 139 | "no-throw-literal": 0, 140 | "no-undef": 2, 141 | "no-undef-init": 0, 142 | "no-undefined": 0, 143 | "no-unexpected-multiline": 2, 144 | "no-underscore-dangle": 0, 145 | "no-unmodified-loop-condition": 0, 146 | "no-unneeded-ternary": 0, 147 | "no-unreachable": 2, 148 | "no-unused-expressions": 0, 149 | "no-unused-labels": 2, 150 | "no-unused-vars": 2, 151 | "no-use-before-define": 0, 152 | "no-useless-call": 0, 153 | "no-useless-concat": 2, 154 | "no-useless-constructor": 0, 155 | "no-void": 0, 156 | "no-var": 2, 157 | "no-warning-comments": 0, 158 | "no-with": 0, 159 | "no-magic-numbers": 0, 160 | "array-bracket-spacing": 0, 161 | "array-callback-return": 0, 162 | "arrow-body-style": 2, 163 | "arrow-parens": 2, 164 | "arrow-spacing": 2, 165 | "accessor-pairs": 0, 166 | "block-scoped-var": 0, 167 | "block-spacing": 0, 168 | "brace-style": 0, 169 | "callback-return": 0, 170 | "camelcase": 0, 171 | "comma-dangle": 2, 172 | "comma-spacing": 0, 173 | "comma-style": 0, 174 | "complexity": [0, 11], 175 | "computed-property-spacing": 0, 176 | "consistent-return": 0, 177 | "consistent-this": 0, 178 | "constructor-super": 2, 179 | "curly": 0, 180 | "default-case": 0, 181 | "dot-location": 0, 182 | "dot-notation": 0, 183 | "eol-last": 2, 184 | "eqeqeq": 0, 185 | "func-names": 0, 186 | "func-style": 0, 187 | "generator-star-spacing": 2, 188 | "global-require": 0, 189 | "guard-for-in": 0, 190 | "handle-callback-err": 0, 191 | "id-length": 0, 192 | "indent": [2, 4, {"SwitchCase": 1}], 193 | "init-declarations": 0, 194 | "jsx-quotes": 0, 195 | "key-spacing": 0, 196 | "keyword-spacing": 0, 197 | "lines-around-comment": 0, 198 | "max-depth": 0, 199 | "max-len": 0, 200 | "max-nested-callbacks": 0, 201 | "max-params": 0, 202 | "max-statements": 0, 203 | "new-cap": 0, 204 | "new-parens": 0, 205 | "newline-after-var": 0, 206 | "newline-per-chained-call": 0, 207 | "object-curly-spacing": [0, "never"], 208 | "object-shorthand": 2, 209 | "one-var": 0, 210 | "one-var-declaration-per-line": 0, 211 | "operator-assignment": 0, 212 | "operator-linebreak": 0, 213 | "padded-blocks": 0, 214 | "prefer-arrow-callback": 2, 215 | "prefer-const": 0, 216 | "prefer-reflect": 0, 217 | "prefer-rest-params": 0, 218 | "prefer-spread": 2, 219 | "prefer-template": 2, 220 | "quote-props": 0, 221 | "quotes": [2, "double", "avoid-escape"], 222 | "radix": 0, 223 | "id-match": 0, 224 | "id-blacklist": 0, 225 | "require-jsdoc": 0, 226 | "require-yield": 0, 227 | "semi": 0, 228 | "semi-spacing": 0, 229 | "sort-vars": 0, 230 | "sort-imports": 0, 231 | "space-before-blocks": 0, 232 | "space-before-function-paren": 0, 233 | "space-in-parens": 0, 234 | "space-infix-ops": 0, 235 | "space-unary-ops": 0, 236 | "spaced-comment": 0, 237 | "strict": 2, 238 | "template-curly-spacing": 0, 239 | "use-isnan": 2, 240 | "valid-jsdoc": 0, 241 | "valid-typeof": 2, 242 | "vars-on-top": 0, 243 | "wrap-iife": 0, 244 | "wrap-regex": 0, 245 | "yield-star-spacing": 0, 246 | "yoda": 0 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | If you can understand Vietnamese, please refer [this post in Viblo](https://viblo.asia/thangtd90/posts/157G5noZvAje), 3 | which describes in detail about how to contribute to Chat++. 4 | 5 | ## Setup 6 | ### Requirement 7 | - Nodejs: Version 10 or 11 recommended 8 | 9 | ### Instalation 10 | - Install `nodejs` first, if you do not have. 11 | - You can use `npm` or `yarn` to install dependencies. Here, `yarn install` is being used to install the required node modules. 12 | - Chat++ uses [ESLint](http://eslint.org/) for checking code styles. It uses `eslint-babel` parser instead of the default ESLint parser. 13 | You have to install them first. 14 | ``` 15 | npm install -g eslint babel-eslint 16 | ``` 17 | - You can refer the [.eslintrc.json](./.eslintrc.json) file for the rules that Chat++ is following. Check [ESLint Rules Document](http://eslint.org/docs/rules/) 18 | for the rules in detail. 19 | - [Laravel Mix](https://laravel-mix.com/), a Webpack wrapper, is being used to compile assets. You can check out the usage at their [documentation page](https://laravel-mix.com/docs/2.1/mixjs). 20 | - Run `npm run dev` to build codes from `src` folder. 21 | - Run `npm test` or `eslint src` to check whether your codes satisfy the coding conventions or not. 22 | 23 | ## Report an Issue 24 | 25 | Please follow the guidelines below when creating an issue so that your issue can be more promptly resolved: 26 | 27 | * Provide us as much information as you can. If possible, please describe the steps for reproducing the issue. A screenshot or a gif image to explain the issue is very appreciated. :+1: 28 | 29 | * Please search through [existing issues](../../issues/) to see if your issue is already reported or fixed to make sure you are not reporting a duplicated issue. 30 | 31 | ## Contribute to the project 32 | - With Pull Requests about the new features, bug fixes, code refactoring ... please send to **develop** branch. 33 | - With Pull Requests for the **Firefox version** ... please send to **firefox** branch. 34 | 35 | They are always welcome. :+1: 36 | 37 | 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![chatpp](./build/img/icon32.png) Chat++ 5 2 | ================= 3 | 4 | Chat++ is an all-in-one Chatwork Enhancement Toolkit. 5 | 6 | It is a free Chrome Extension which provides more funny emoticons as well as many other features for Chatwork. 7 | 8 | * [Homepage](https://chatpp.thangtd.com) 9 | * [Chrome Web Store](https://chrome.google.com/webstore/detail/chat%20%20-for-chatwork/amhfnpimdfcdcpnchjionbddjjbmofnl) 10 | * [Firefox AMO](https://addons.mozilla.org/en-US/firefox/addon/chatpp-for-chatwork/) 11 | * [Twitter](https://twitter.com/chatplusplus) 12 | * [Qiita](http://qiita.com/thangtd90/items/4433ca440f9ca4b0938f) (in Japanese) 13 | * [Viblo](https://viblo.asia/thangtd90/posts/157G5noZvAje) (in Vietnamese) 14 | 15 | Change Logs 16 | -------------- 17 | View all change logs [here](./changelogs.md) 18 | 19 | Contribution 20 | -------------- 21 | View contribution guidelines [here](./CONTRIBUTING.md), or check it out at [Viblo](https://viblo.asia/thangtd90/posts/157G5noZvAje) 22 | 23 | Main Features 24 | -------------- 25 | View feature list (in English and Vietnamese) [here](./features.md) 26 | 27 | ![thumb1](./images/1.png) 28 | ![thumb2](./images/2.png) 29 | ![thumb3](./images/3.png) 30 | ![thumb4](./images/4.png) 31 | ![thumb5](./images/5.png) 32 | -------------------------------------------------------------------------------- /build/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/build/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /build/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/build/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /build/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/build/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /build/html/emoticon.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Emoticon | Chat++ 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 |
19 |
20 |
21 |

22 | Angelic 23 | 24 | Emoticons Page 25 |
26 |

27 |

*** A Chatwork Enhancement Toolkit ***

28 |
29 |
30 |

Add new Emoticons Data

31 |
32 | 33 |
34 |
35 |
36 |
37 |
38 |
39 | 40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 |

Emoticons Data

51 |
52 |

Notices

53 |

54 | ■ Chat Plus Plus only supports links from Dropbox. Please upload your file to Dropbox to get the link starting with https://dl.dropboxusercontent.com/" or "https://www.dropbox.com/ first.
55 | ■ If there are two or more emoticons in different data but have the same name, only one is used. 56 | It is the emoticon belongs to the data that have the highest priority.
57 | ■ Use the "Move Up" button to arrange your data priority. The data in upper place will have higher priority
58 | ■ You can create your own Emoticons data file and then share it with your friends. 59 | Of course if you and your friends have the same data, you will see the same emoticons on Chatwork!
60 | ■ You can easily create a Emoticons Data file for Chat++ by referring the below data files, 61 | or using the Chat++ Emoticon Set Editor, a great site made by Nguyen Duc Tung. 62 | Great thanks to him for his hard work
63 |

64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
Data NameData VersionData URLAction
79 |
80 |
81 | 82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | -------------------------------------------------------------------------------- /build/html/features.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Chat++ Features

11 |
12 |

Tiếng Việt

13 |
14 |

English

15 |
16 |

Emoticons

17 | 18 | 22 | 23 |

Mention

24 | 25 | 63 | 64 |

Shortcuts

65 | 66 | 84 | 85 |

Message Display Improvement

86 | 87 | 91 | 92 |

Other

93 | 94 | 103 | 104 |

Vietnamese

105 |
106 |

Tính năng Emoticons

107 | 108 | 112 | 113 |

Tính năng Mention

114 | 115 | 151 | 152 |

Shortcuts

153 | 154 | 172 | 173 |

Cải thiện hiển thị Message

174 | 175 | 179 | 180 |

Other

181 | 182 | 192 |
193 |
194 |
195 | 196 | -------------------------------------------------------------------------------- /build/html/group.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Group | Chat++ 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |
17 |
18 |

19 | Angelic 20 | 21 | Group Mention Page 22 |
23 |

24 |

*** A Chatwork Enhancement Toolkit ***

25 |
26 |
27 |
28 |
29 | 30 |
31 |

Create Mention Group

32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 |
44 | 45 |
46 |

Notices

47 | 62 |
63 | 64 |
65 |

Group Data

66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
Group NameGroup MemberAction
78 |
79 |
80 |
81 | -------------------------------------------------------------------------------- /build/html/notification.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notification | Chat++ 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |
17 |
18 |

19 | Angelic 20 | 21 | Notification Page 22 |
23 |

24 |

*** A Chatwork Enhancement Toolkit ***

25 |
26 |
27 |
28 |

Disabled Desktop Notification Rooms

29 |
30 |
31 |

Notices

32 |

33 | In this page you can list rooms that you don't want to receive desktop notification.
34 | After registering Rooms, remember to reload Chatwork to make all the changes become available ^^! 35 |

36 |
37 |
38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 | 46 | -------------------------------------------------------------------------------- /build/html/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 |
18 |
19 |
20 | Angelic 21 |
22 |
23 |

24 |
A Chatwork Enhancement Toolkit
25 |
Follow me - Change Logs - Features
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |
37 |
38 |

Homepage: http://chatpp.thangtd.com

39 |

Author: Tran Duc Thang - Nguyen Anh Tien

40 |
41 |

Emoticon:

42 |

Mention:

43 |

Shortcut:

44 |
45 |

ChatPP icon designed by Tran Ba Trong.

46 |

If you like Chat++, please rate it :)

47 |

If you'd like to contribute to Chat++, please refer the Contribution Guidelines

48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /build/html/room.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Room | Chat++ 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |
17 |
18 |

19 | Angelic 20 | 21 | Room Page 22 |
23 |

24 |

*** A Chatwork Enhancement Toolkit ***

25 |
26 |
27 |
28 |

Register Rooms

29 |
30 |
31 |

Notices

32 |

You can register a room for the shortcut from 0 to 9. When a room is assigned to a number, 33 | you can use that number to quickly jump to that room
34 | The following inputs are acceptable:
35 | ■ https://www.chatwork.com/#!rid345
36 | ■ rid345
37 | ■ 345
38 | After registering Rooms, remember to reload Chatwork to make all the changes become available ^^! 39 |

40 |
41 |
42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 |
71 |
72 |
73 |
74 | 75 |
76 | -------------------------------------------------------------------------------- /build/html/setting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Setting | Chat++ 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 |
13 |
14 |
15 |

16 | Angelic 17 | 18 | Setting Page 19 |
20 |

21 |

*** A Chatwork Enhancement Toolkit ***

22 |
23 |
24 |
25 |

Change Features Status

26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
FeatureStatusAction
Emoticon
Mention
Shortcut
Legacy theme
59 |
60 |
61 | -------------------------------------------------------------------------------- /build/html/shortcut.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Shortcut | Chat++ 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 |
17 |
18 |

19 | Angelic 20 | 21 | Shortcut Page 22 |
23 |

24 |

*** A Chatwork Enhancement Toolkit ***

25 |
26 |
27 |
28 |
29 | 30 |
31 |

List Shortcuts

32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |
FeatureShortcutNote
Focus Chat BoxspaceChat++
Focus Search Text BoxfDefault
Focus Task BoxtDefault
Open Contact WindowcDefault
Open Group Chat WindowgDefault
Go To User's Own Chat GroupaChat++
Scroll to the end of Chat BoxsChat++
Reply MessagerChat++
Edit MessageeChat++
Edit Image Upload Message (remove some unnecessary information of an upload image)Shift + eChat++
Quote MessageqChat++
Go To Next Mention MessagejChat++
Go To Previous Mention MessagekChat++
Go to the first Room in the listzChat++
Go to the first nonstick Room in the listxChat++
Go to the Room below in the listvChat++
Go to the Room above in the listbChat++
Go to the first Room that has new unread Message(s)nChat++
Go to the first Room that has new unread Mention Message(s)mChat++
140 |
141 |
142 | -------------------------------------------------------------------------------- /build/img/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/build/img/icon128.png -------------------------------------------------------------------------------- /build/img/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/build/img/icon16.png -------------------------------------------------------------------------------- /build/img/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/build/img/icon256.png -------------------------------------------------------------------------------- /build/img/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/build/img/icon32.png -------------------------------------------------------------------------------- /build/img/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/build/img/icon64.png -------------------------------------------------------------------------------- /build/js/extensions/background.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // define getter function for harmony exports 37 | /******/ __webpack_require__.d = function(exports, name, getter) { 38 | /******/ if(!__webpack_require__.o(exports, name)) { 39 | /******/ Object.defineProperty(exports, name, { 40 | /******/ configurable: false, 41 | /******/ enumerable: true, 42 | /******/ get: getter 43 | /******/ }); 44 | /******/ } 45 | /******/ }; 46 | /******/ 47 | /******/ // getDefaultExport function for compatibility with non-harmony modules 48 | /******/ __webpack_require__.n = function(module) { 49 | /******/ var getter = module && module.__esModule ? 50 | /******/ function getDefault() { return module['default']; } : 51 | /******/ function getModuleExports() { return module; }; 52 | /******/ __webpack_require__.d(getter, 'a', getter); 53 | /******/ return getter; 54 | /******/ }; 55 | /******/ 56 | /******/ // Object.prototype.hasOwnProperty.call 57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 58 | /******/ 59 | /******/ // __webpack_public_path__ 60 | /******/ __webpack_require__.p = "/"; 61 | /******/ 62 | /******/ // Load entry module and return exports 63 | /******/ return __webpack_require__(__webpack_require__.s = 18); 64 | /******/ }) 65 | /************************************************************************/ 66 | /******/ ({ 67 | 68 | /***/ 0: 69 | /***/ (function(module, exports) { 70 | 71 | var Const = { 72 | LOCAL_STORAGE_DATA_KEY: "YACEP_EMO_DATA", 73 | LOCAL_STORAGE_INFO_KEY: "YACEP_EMO_INFO", 74 | LOCAL_STORAGE_GROUP_MENTION: "CHATPP_GROUP_MENTION", 75 | LOCAL_STORAGE_ROOM_SHORTCUT: "CHATPP_ROOM_SHORTCUT", 76 | LOCAL_STORAGE_DISABLE_NOTIFY_ROOM: "CHATPP_DISABLE_NOTIFY_ROOM", 77 | CHROME_LOCAL_KEY: "CHATPP_CHROME_LOCAL_DATA", 78 | CHROME_SYNC_KEY: "CHATPP_CHROME_SYNC_DATA", 79 | CHROME_SYNC_GROUP_KEY: "CHATPP_CHROME_SYNC_GROUP", 80 | CHROME_SYNC_ROOM_KEY: "CHATPP_CHROME_SYNC_ROOM", 81 | CHROME_SYNC_DISABLE_NOTIFY_ROOM_KEY: "CHATPP_CHROME_SYNC_DISABLE_NOTIFY_ROOM", 82 | DEFAULT_DATA_URL: "https://dl.dropboxusercontent.com/s/lmxis68cfh4v1ho/default.json?dl=1", 83 | ADVERTISEMENT_URL: "https://dl.dropboxusercontent.com/s/jsmceot0pqi8lpk/chatppad.json?dl=1", 84 | VERSION_CHROME: "VERSION_CHROME", 85 | VERSION_FIREFOX: "VERSION_FIREFOX", 86 | VERSION_NAME_DEV: "dev", 87 | VERSION_NAME_RELEASE: "final", 88 | DEFAULT_IMG_HOST: "https://chatpp.thangtd.com/", 89 | DELAY_TIME: 6000, 90 | FORCE_TURN_OFF_THUMBNAIL: 1, 91 | ADVERTISEMENT_LOAD_TIMEOUT: 1000 * 60 * 30, 92 | TO_ALL_MARK: "TO ALL >>>" 93 | }; 94 | 95 | module.exports = Const; 96 | 97 | /***/ }), 98 | 99 | /***/ 1: 100 | /***/ (function(module, exports, __webpack_require__) { 101 | 102 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 103 | 104 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 105 | 106 | var Const = __webpack_require__(0); 107 | 108 | var Common = function () { 109 | function Common() { 110 | _classCallCheck(this, Common); 111 | 112 | this.version = Const.VERSION_CHROME; 113 | this.app_detail = this.getAppDetail(); 114 | this.official_emoticons_data = { 115 | Default: { 116 | name: "Default", 117 | link: "https://dl.dropboxusercontent.com/s/lmxis68cfh4v1ho/default.json?dl=1", 118 | description: "The default Emoticons data of Chat++" 119 | }, 120 | Vietnamese: { 121 | name: "Vietnamese", 122 | link: "https://dl.dropboxusercontent.com/s/2b085bilbno4ri1/vietnamese.json?dl=1", 123 | description: "Yet another data for people who want to use Vietnamese Emoticons" 124 | }, 125 | Japanese: { 126 | name: "Japanese", 127 | link: "https://dl.dropboxusercontent.com/s/fdq05pwwtsccrn6/japanese.json?dl=1", 128 | description: "Yet another data for people who want to use Japanese Emoticons" 129 | }, 130 | Skype: { 131 | name: "Skype", 132 | link: "https://dl.dropboxusercontent.com/s/8ew2mdh0v2vcad8/skype.json?dl=1", 133 | description: "Skype Original Emoticons" 134 | } 135 | }; 136 | } 137 | 138 | _createClass(Common, [{ 139 | key: "isChromeVersion", 140 | value: function isChromeVersion() { 141 | return this.version === Const.VERSION_CHROME; 142 | } 143 | }, { 144 | key: "isFirefoxVersion", 145 | value: function isFirefoxVersion() { 146 | return this.version === Const.VERSION_FIREFOX; 147 | } 148 | }, { 149 | key: "isDevVersion", 150 | value: function isDevVersion() { 151 | var app_name = this.app_detail.name; 152 | return app_name.indexOf(Const.VERSION_NAME_DEV, app_name.length - Const.VERSION_NAME_DEV.length) !== -1; 153 | } 154 | }, { 155 | key: "checkDevVersionInternal", 156 | value: function checkDevVersionInternal() { 157 | return localStorage["chatpp_version_name"] === Const.VERSION_NAME_DEV; 158 | } 159 | }, { 160 | key: "getStorage", 161 | value: function getStorage(local) { 162 | if (!local && this.isChromeVersion()) { 163 | return chrome.storage.sync; 164 | } 165 | 166 | return chrome.storage.local; 167 | } 168 | }, { 169 | key: "sync", 170 | value: function sync(key, data, callback) { 171 | var sync = {}; 172 | sync[key] = data; 173 | var storage = this.getStorage(); 174 | storage.set(sync, function () { 175 | if (callback) { 176 | callback(); 177 | } 178 | }); 179 | } 180 | }, { 181 | key: "getEmoticonDataUrl", 182 | value: function getEmoticonDataUrl(data_name, default_url) { 183 | if (data_name && this.official_emoticons_data[data_name]) { 184 | default_url = this.official_emoticons_data[data_name]["link"]; 185 | } 186 | 187 | return default_url ? default_url.replace("http://i.imgur.com/", "https://i.imgur.com/") : null; 188 | } 189 | }, { 190 | key: "getObjectLength", 191 | value: function getObjectLength(object) { 192 | return Object.keys(object).length; 193 | } 194 | }, { 195 | key: "htmlEncode", 196 | value: function htmlEncode(value) { 197 | return $("
").text(value).html(); 198 | } 199 | }, { 200 | key: "getEmoUrl", 201 | value: function getEmoUrl(img) { 202 | var url = Const.DEFAULT_IMG_HOST + "img/emoticons/" + img; 203 | if (img.indexOf("https://") == 0 || img.indexOf("http://") == 0) { 204 | url = img; 205 | } 206 | return this.htmlEncode(url); 207 | } 208 | }, { 209 | key: "parseRoomId", 210 | value: function parseRoomId(text) { 211 | var room = text.match(/\d+/g); 212 | if (!room || room.length == 0) { 213 | return null; 214 | } 215 | room = room[0]; 216 | var regex = /^[0-9]{6,10}$/g; 217 | if (regex.test(room)) { 218 | return room; 219 | } 220 | return null; 221 | } 222 | }, { 223 | key: "parseUsersId", 224 | value: function parseUsersId(text) { 225 | var regex = /\[[a-zA-Z]+:([0-9]+)\]/g; 226 | var match = void 0; 227 | var users = []; 228 | while ((match = regex.exec(text)) != null) { 229 | users.push(match[1]); 230 | } 231 | 232 | return users; 233 | } 234 | }, { 235 | key: "reload", 236 | value: function reload() { 237 | location.reload(); 238 | } 239 | }, { 240 | key: "getAppDetail", 241 | value: function getAppDetail() { 242 | return chrome.app.getDetails(); 243 | } 244 | }, { 245 | key: "getAppVersion", 246 | value: function getAppVersion() { 247 | return this.app_detail.version; 248 | } 249 | }, { 250 | key: "getAppVersionName", 251 | value: function getAppVersionName() { 252 | if (this.isDevVersion()) { 253 | return Const.VERSION_NAME_DEV; 254 | } 255 | 256 | return Const.VERSION_NAME_RELEASE; 257 | } 258 | }, { 259 | key: "getAppFullName", 260 | value: function getAppFullName() { 261 | var version_name = this.getAppVersionName(); 262 | 263 | return this.app_detail.short_name + " " + this.app_detail.version + " " + version_name; 264 | } 265 | }, { 266 | key: "openNewUrl", 267 | value: function openNewUrl(url) { 268 | chrome.tabs.create({ url: url }); 269 | } 270 | }, { 271 | key: "getExtensionPageUrl", 272 | value: function getExtensionPageUrl(url) { 273 | return chrome.extension.getURL(url); 274 | } 275 | }, { 276 | key: "openNewExtensionPageUrl", 277 | value: function openNewExtensionPageUrl(url) { 278 | this.openNewUrl(this.getExtensionPageUrl(url)); 279 | } 280 | }, { 281 | key: "validateUrl", 282 | value: function validateUrl(url) { 283 | var regexp = /(https):\/\/(\w+:?\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; 284 | return regexp.test(url); 285 | } 286 | }, { 287 | key: "validateDropboxUrl", 288 | value: function validateDropboxUrl(url) { 289 | if (!this.validateUrl(url)) { 290 | return false; 291 | } 292 | var supported_urls = ["https://dl.dropboxusercontent.com/", "https://www.dropbox.com/"]; 293 | var _iteratorNormalCompletion = true; 294 | var _didIteratorError = false; 295 | var _iteratorError = undefined; 296 | 297 | try { 298 | for (var _iterator = supported_urls[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 299 | var supported_url = _step.value; 300 | 301 | if (url.startsWith(supported_url)) { 302 | return true; 303 | } 304 | } 305 | } catch (err) { 306 | _didIteratorError = true; 307 | _iteratorError = err; 308 | } finally { 309 | try { 310 | if (!_iteratorNormalCompletion && _iterator.return) { 311 | _iterator.return(); 312 | } 313 | } finally { 314 | if (_didIteratorError) { 315 | throw _iteratorError; 316 | } 317 | } 318 | } 319 | 320 | return false; 321 | } 322 | }, { 323 | key: "isPage", 324 | value: function isPage(page_name) { 325 | return $("#page-name").data("page-name") === page_name; 326 | } 327 | }, { 328 | key: "setPageTitle", 329 | value: function setPageTitle() { 330 | $("#chatpp_name").html(this.getAppFullName()); 331 | } 332 | }, { 333 | key: "setStatus", 334 | value: function setStatus(key, value) { 335 | if (key.indexOf("_status") === -1) { 336 | key = key + "_status"; 337 | } 338 | localStorage[key] = !!value; 339 | } 340 | }, { 341 | key: "getStatus", 342 | value: function getStatus(key) { 343 | if (key.indexOf("_status") === -1) { 344 | key = key + "_status"; 345 | } 346 | return localStorage[key] === "true" || localStorage[key] === true; 347 | } 348 | }, { 349 | key: "regexEscape", 350 | value: function regexEscape(string) { 351 | return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); 352 | } 353 | }, { 354 | key: "generateEmoticonRegex", 355 | value: function generateEmoticonRegex(text, regex) { 356 | regex = regex || this.htmlEncode(this.regexEscape(text)); 357 | return new RegExp(regex, "g"); 358 | } 359 | }, { 360 | key: "random", 361 | value: function random(items) { 362 | if (!items.length) { 363 | return null; 364 | } 365 | 366 | return items[Math.floor(Math.random() * items.length)]; 367 | } 368 | }, { 369 | key: "randomString", 370 | value: function randomString(n) { 371 | var text = ""; 372 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 373 | 374 | for (var i = 0; i < n; i++) { 375 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 376 | }return text; 377 | } 378 | }]); 379 | 380 | return Common; 381 | }(); 382 | 383 | var common = new Common(); 384 | module.exports = common; 385 | 386 | /***/ }), 387 | 388 | /***/ 18: 389 | /***/ (function(module, exports, __webpack_require__) { 390 | 391 | module.exports = __webpack_require__(19); 392 | 393 | 394 | /***/ }), 395 | 396 | /***/ 19: 397 | /***/ (function(module, exports, __webpack_require__) { 398 | 399 | var common = __webpack_require__(1); 400 | var ChromeStorageLocal = __webpack_require__(3); 401 | var chrome_storage_local = new ChromeStorageLocal(); 402 | var Const = __webpack_require__(0); 403 | 404 | chrome.runtime.onInstalled.addListener(function () { 405 | chrome_storage_local.get(function (data) { 406 | var version = void 0; 407 | if (data) { 408 | version = data["version"]; 409 | } 410 | if (!version || version != common.app_detail.version) { 411 | chrome.browserAction.setBadgeText({ text: "new" }); 412 | } 413 | }); 414 | }); 415 | 416 | chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { 417 | if (request.contentScriptQuery == "fetchEmoticonsData") { 418 | var url = "https://dl.dropboxusercontent.com/" + request.query; 419 | getJSON(url, function (error, responseData) { 420 | if (error !== null) { 421 | var data = { 422 | success: false, 423 | data_name: request.data_name 424 | }; 425 | sendResponse(data); 426 | } 427 | responseData.success = true; 428 | sendResponse(responseData); 429 | }); 430 | } 431 | 432 | if (request.contentScriptQuery == "fetchAdvertisementData") { 433 | getJSON(Const.ADVERTISEMENT_URL, function (error, responseData) { 434 | sendResponse(responseData); 435 | }); 436 | } 437 | 438 | return true; 439 | }); 440 | 441 | function getJSON(url, callback) { 442 | var xhr = new XMLHttpRequest(); 443 | xhr.open("GET", url, true); 444 | xhr.responseType = "json"; 445 | xhr.onload = function () { 446 | var status = xhr.status; 447 | if (status === 200) { 448 | callback(null, xhr.response); 449 | } else { 450 | callback(status, xhr.response); 451 | } 452 | }; 453 | xhr.send(); 454 | } 455 | 456 | /***/ }), 457 | 458 | /***/ 2: 459 | /***/ (function(module, exports, __webpack_require__) { 460 | 461 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 462 | 463 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 464 | 465 | var common = __webpack_require__(1); 466 | 467 | var Storage = function () { 468 | function Storage(local) { 469 | _classCallCheck(this, Storage); 470 | 471 | this.storage = common.getStorage(local); 472 | } 473 | 474 | _createClass(Storage, [{ 475 | key: "get", 476 | value: function get(key, callback) { 477 | this.storage.get(key, function (info) { 478 | info = info ? info[key] : undefined; 479 | callback(info); 480 | }); 481 | } 482 | }, { 483 | key: "set", 484 | value: function set(key, data, callback) { 485 | var sync = {}; 486 | sync[key] = data; 487 | this.storage.set(sync, function () { 488 | if (callback) { 489 | callback(); 490 | } 491 | }); 492 | } 493 | }, { 494 | key: "setData", 495 | value: function setData(data, callback) { 496 | this.storage.set(data, function () { 497 | if (callback) { 498 | callback(); 499 | } 500 | }); 501 | } 502 | }]); 503 | 504 | return Storage; 505 | }(); 506 | 507 | module.exports = Storage; 508 | 509 | /***/ }), 510 | 511 | /***/ 3: 512 | /***/ (function(module, exports, __webpack_require__) { 513 | 514 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 515 | 516 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 517 | 518 | var Storage = __webpack_require__(2); 519 | var Const = __webpack_require__(0); 520 | 521 | var ChromeStorageLocal = function () { 522 | function ChromeStorageLocal() { 523 | _classCallCheck(this, ChromeStorageLocal); 524 | 525 | this.storage = new Storage(true); 526 | this.key = Const.CHROME_LOCAL_KEY; 527 | } 528 | 529 | _createClass(ChromeStorageLocal, [{ 530 | key: "get", 531 | value: function get(callback) { 532 | this.storage.get(this.key, callback); 533 | } 534 | }, { 535 | key: "set", 536 | value: function set(data, callback) { 537 | this.set(this.key, data, callback); 538 | } 539 | }, { 540 | key: "setData", 541 | value: function setData(data, callback) { 542 | this.storage.setData(data, callback); 543 | } 544 | }]); 545 | 546 | return ChromeStorageLocal; 547 | }(); 548 | 549 | module.exports = ChromeStorageLocal; 550 | 551 | /***/ }) 552 | 553 | /******/ }); -------------------------------------------------------------------------------- /build/js/extensions/injectPreloadHook.js: -------------------------------------------------------------------------------- 1 | var preload_hook_code = ` 2 | window.CHATPP_defineProperty = Object.defineProperty; 3 | window.esmodules = []; 4 | defineProperty_handler = { 5 | apply: function (target, thisArg, args) { 6 | r = target.apply(thisArg, args); 7 | if (args[1] == 'a') { 8 | window.esmodules.push(r); 9 | }; 10 | if (args[1] == 'searchUrlTokens') { 11 | console.log('Found Notation Module, restore Object.defineProperty!'); 12 | window.notation_module = r; 13 | Object.defineProperty = window.CHATPP_defineProperty; 14 | } 15 | return r; 16 | }, 17 | } 18 | Object.defineProperty = new Proxy(Object.defineProperty, defineProperty_handler) 19 | 20 | // backup Object.defineProperties 21 | window.CHATPP_defineProperties = Object.defineProperties; 22 | defineProperties_handler = { 23 | apply: function (target, thisArg, args) { 24 | r = target.apply(thisArg, args); 25 | if (r.EC14) { 26 | console.log('Final tagHash after last emo inserted, restore Object.defineProperties!'); 27 | window.emoticon_tag_hash_list = r; 28 | Object.defineProperties = window.CHATPP_defineProperties; 29 | } 30 | return r; 31 | }, 32 | } 33 | 34 | Object.defineProperties = new Proxy(Object.defineProperties, defineProperties_handler); 35 | `; 36 | 37 | var script = document.createElement('script'); 38 | script.textContent = preload_hook_code; 39 | (document.head || document.documentElement).appendChild(script); 40 | script.remove(); 41 | -------------------------------------------------------------------------------- /build/js/libraries/caretposition.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | caretposition.js 4 | 5 | Copyright (c) 2012- Hiroki Akiyama http://akiroom.com/ 6 | caretposition.js is free software distributed under the terms of the MIT license. 7 | */ 8 | 9 | Measurement = new function() { 10 | this.caretPos = function(textarea, mode) { 11 | var targetElement = textarea; 12 | if (typeof jQuery != 'undefined') { 13 | if (textarea instanceof jQuery) { 14 | targetElement = textarea.get(0); 15 | } 16 | } 17 | // HTML Sanitizer 18 | var escapeHTML = function (s) { 19 | var obj = document.createElement('pre'); 20 | obj[typeof obj.textContent != 'undefined'?'textContent':'innerText'] = s; 21 | return obj.innerHTML; 22 | }; 23 | 24 | // Get caret character position. 25 | var getCaretPosition = function (element) { 26 | var CaretPos = 0; 27 | var startpos = -1; 28 | var endpos = -1; 29 | if (document.selection) { 30 | // IE Support(not yet) 31 | var docRange = document.selection.createRange(); 32 | var textRange = document.body.createTextRange(); 33 | textRange.moveToElementText(element); 34 | 35 | var range = textRange.duplicate(); 36 | range.setEndPoint('EndToStart', docRange); 37 | startpos = range.text.length; 38 | 39 | var range = textRange.duplicate(); 40 | range.setEndPoint('EndToEnd', docRange); 41 | endpos = range.text.length; 42 | } else if (element.selectionStart || element.selectionStart == '0') { 43 | // Firefox support 44 | startpos = element.selectionStart; 45 | endpos = element.selectionEnd; 46 | } 47 | return {start: startpos, end: endpos}; 48 | }; 49 | 50 | // Get element css style. 51 | var getStyle = function (element) { 52 | var style = element.currentStyle || document.defaultView.getComputedStyle(element, ''); 53 | return style; 54 | }; 55 | 56 | // Get element absolute position 57 | var getElementPosition = function (element) { 58 | // Get scroll amount. 59 | var html = document.documentElement; 60 | var body = document.body; 61 | var scrollLeft = (body.scrollLeft || html.scrollLeft); 62 | var scrollTop = (body.scrollTop || html.scrollTop); 63 | 64 | // Adjust "IE 2px bugfix" and scroll amount. 65 | var rect = element.getBoundingClientRect(); 66 | var left = rect.left - html.clientLeft + scrollLeft; 67 | var top = rect.top - html.clientTop + scrollTop; 68 | var right = rect.right - html.clientLeft + scrollLeft; 69 | var bottom = rect.bottom - html.clientTop + scrollTop; 70 | return {left: parseInt(left), top: parseInt(top), 71 | right: parseInt(right), bottom:parseInt(bottom)}; 72 | }; 73 | 74 | /***************************\ 75 | * Main function start here! * 76 | \***************************/ 77 | 78 | var undefined; 79 | var salt = "salt.akiroom.com"; 80 | var textAreaPosition = getElementPosition(targetElement); 81 | var dummyName = targetElement.id + "_dummy"; 82 | var dummyTextArea = document.getElementById(dummyName); 83 | if (!dummyTextArea) { 84 | // Generate dummy textarea. 85 | dummyTextArea = document.createElement("div"); 86 | dummyTextArea.id = dummyName; 87 | var textAreaStyle = getStyle(targetElement) 88 | dummyTextArea.style.cssText = textAreaStyle.cssText; 89 | 90 | // Fix for browser differece. 91 | var isWordWrap = false; 92 | if (targetElement.wrap == "off") { 93 | // chrome, firefox wordwrap=off 94 | dummyTextArea.style.overflow = "auto"; 95 | dummyTextArea.style.whiteSpace = "pre"; 96 | isWordWrap = false; 97 | } else if (targetElement.wrap == undefined) { 98 | if (textAreaStyle.wordWrap == "break-word") 99 | // safari, wordwrap=on 100 | isWordWrap = true; 101 | else 102 | // safari, wordwrap=off 103 | isWordWrap = false; 104 | } else { 105 | // firefox wordwrap=on 106 | dummyTextArea.style.overflowY = "auto"; 107 | isWordWrap = true; 108 | } 109 | dummyTextArea.style.visibility = 'hidden'; 110 | dummyTextArea.style.position = 'absolute'; 111 | dummyTextArea.style.top = '0px'; 112 | dummyTextArea.style.left = '0px'; 113 | 114 | // Firefox Support 115 | dummyTextArea.style.width = textAreaStyle.width; 116 | dummyTextArea.style.height = textAreaStyle.height; 117 | dummyTextArea.style.fontSize = textAreaStyle.fontSize; 118 | dummyTextArea.style.maxWidth = textAreaStyle.width; 119 | dummyTextArea.style.backgroundColor = textAreaStyle.backgroundColor; 120 | dummyTextArea.style.fontFamily = textAreaStyle.fontFamily; 121 | dummyTextArea.style.padding = textAreaStyle.padding; 122 | dummyTextArea.style.paddingTop = textAreaStyle.paddingTop; 123 | dummyTextArea.style.paddingRight = textAreaStyle.paddingRight; 124 | dummyTextArea.style.paddingBottom = textAreaStyle.paddingBottom; 125 | dummyTextArea.style.paddingLeft = textAreaStyle.paddingLeft; 126 | 127 | 128 | targetElement.parentNode.appendChild(dummyTextArea); 129 | } 130 | 131 | // Set scroll amount to dummy textarea. 132 | dummyTextArea.scrollLeft = targetElement.scrollLeft; 133 | dummyTextArea.scrollTop = targetElement.scrollTop; 134 | 135 | // Set code strings. 136 | var codeStr = targetElement.value; 137 | 138 | // Get caret character position. 139 | var selPos = getCaretPosition(targetElement); 140 | var leftText = codeStr.slice(0, selPos.start); 141 | var selText = codeStr.slice(selPos.start, selPos.end); 142 | var rightText = codeStr.slice(selPos.end, codeStr.length); 143 | if (selText == '') selText = "a"; 144 | 145 | // Set keyed text. 146 | var processText = function (text) { 147 | // Get array of [Character reference] or [Character] or [NewLine]. 148 | var m = escapeHTML(text).match(/((&|<|>|"|')|.|\n)/g); 149 | if (m) 150 | return m.join('').replace(/\n/g, '
'); 151 | else 152 | return ''; 153 | }; 154 | 155 | // Set calculation text for in dummy text area. 156 | dummyTextArea.innerHTML = (processText(leftText) + 157 | '' + processText(selText) + '' + 158 | processText(rightText)); 159 | 160 | // Get caret absolutely pixel position. 161 | var dummyTextAreaPos = getElementPosition(dummyTextArea); 162 | var caretPos = getElementPosition(document.getElementById(dummyName+"_i")); 163 | switch (mode) { 164 | case 'self': 165 | // Return absolutely pixel position - (0,0) is most top-left of TEXTAREA. 166 | return {left: caretPos.left-dummyTextAreaPos.left, top: caretPos.top-dummyTextAreaPos.top}; 167 | case 'body': 168 | case 'screen': 169 | case 'stage': 170 | case 'page': 171 | default: 172 | // Return absolutely pixel position - (0,0) is most top-left of PAGE. 173 | return {left: textAreaPosition.left+caretPos.left-dummyTextAreaPos.left, top: textAreaPosition.top+caretPos.top-dummyTextAreaPos.top}; 174 | } 175 | }; 176 | }; 177 | -------------------------------------------------------------------------------- /build/js/libraries/fuse.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Fuse - Lightweight fuzzy-search 4 | * 5 | * Copyright (c) 2012 Kirollos Risk . 6 | * All Rights Reserved. Apache Software License 2.0 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | !function(t){function e(t,n){this.list=t,this.options=n=n||{};var i,o,s,r;for(i=0,r=["sort","includeScore","shouldSort"],o=r.length;o>i;i++)s=r[i],this.options[s]=s in n?n[s]:e.defaultOptions[s];for(i=0,r=["searchFn","sortFn","keys","getFn"],o=r.length;o>i;i++)s=r[i],this.options[s]=n[s]||e.defaultOptions[s]}var n=function(t,e){if(e=e||{},this.options=e,this.options.location=e.location||n.defaultOptions.location,this.options.distance="distance"in e?e.distance:n.defaultOptions.distance,this.options.threshold="threshold"in e?e.threshold:n.defaultOptions.threshold,this.options.maxPatternLength=e.maxPatternLength||n.defaultOptions.maxPatternLength,this.pattern=e.caseSensitive?t:t.toLowerCase(),this.patternLen=t.length,this.patternLen>this.options.maxPatternLength)throw new Error("Pattern length is too long");this.matchmask=1<i;)this._bitapScore(e,l+o)<=f?i=o:d=o,o=Math.floor((d-i)/2+i);for(d=o,s=Math.max(1,l-o+1),r=Math.min(l+o,c)+this.patternLen,a=Array(r+2),a[r+1]=(1<=s;n--)if(p=this.patternAlphabet[t.charAt(n-1)],a[n]=0===e?(a[n+1]<<1|1)&p:(a[n+1]<<1|1)&p|((h[n+1]|h[n])<<1|1)|h[n+1],a[n]&this.matchmask&&(g=this._bitapScore(e,n-1),f>=g)){if(f=g,u=n-1,m.push(u),!(u>l))break;s=Math.max(1,2*l-u)}if(this._bitapScore(e+1,l)>f)break;h=a}return{isMatch:u>=0,score:g}};var i=function(t,e,n){var s,r,a;if(e){a=e.indexOf("."),-1!==a?(s=e.slice(0,a),r=e.slice(a+1)):s=e;var h=t[s];if(h)if(r||"string"!=typeof h&&"number"!=typeof h)if(o.isArray(h))for(var p=0,c=h.length;c>p;p++)i(h[p],r,n);else r&&i(h,r,n);else n.push(h)}else n.push(t);return n},o={deepValue:function(t,e){return i(t,e,[])},isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)}};e.defaultOptions={id:null,caseSensitive:!1,includeScore:!1,shouldSort:!0,searchFn:n,sortFn:function(t,e){return t.score-e.score},getFn:o.deepValue,keys:[]},e.prototype.search=function(t){var e,n,i,s,r=new this.options.searchFn(t,this.options),a=this.list,h=a.length,p=this.options,c=this.options.keys,l=c.length,f=[],u={},d=[],g=function(t,e,n){if(void 0!==t&&null!==t)if("string"==typeof t)i=r.search(t),i.isMatch&&(s=u[n],s?s.score=Math.min(s.score,i.score):(u[n]={item:e,score:i.score},f.push(u[n])));else if(o.isArray(t))for(var a=0;am;m++)g(a[m],m,m);else for(var m=0;h>m;m++)for(n=a[m],e=0;l>e;e++)g(p.getFn(n,c[e]),n,m);p.shouldSort&&f.sort(p.sortFn);for(var y=p.includeScore?function(t){return f[t]}:function(t){return f[t].item},v=p.id?function(t){f[t].item=p.getFn(f[t].item,p.id)[0]}:function(){},m=0,b=f.length;b>m;m++)v(m),d.push(y(m));return d},"object"==typeof exports?module.exports=e:"function"==typeof define&&define.amd?define(function(){return e}):t.Fuse=e}(this); -------------------------------------------------------------------------------- /build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Chat++ for Chatwork", 3 | "short_name": "Chat++", 4 | "version": "5.10.2", 5 | "manifest_version": 2, 6 | "description": "An all-in-one Chatwork Enhancement Toolkit which provides many useful features!", 7 | "content_scripts": [ 8 | { 9 | "matches": [ 10 | "https://*.chatwork.com/*", 11 | "https://kcw.kddi.ne.jp/*" 12 | ], 13 | "run_at": "document_start", 14 | "js": ["js/extensions/injectPreloadHook.js"] 15 | }, 16 | { 17 | "matches": [ 18 | "https://www.chatwork.com/*", 19 | "https://kcw.kddi.ne.jp/*" 20 | ], 21 | "js": [ 22 | "js/libraries/jquery.min.js", 23 | "js/extensions/contentscript.js" 24 | ], 25 | "run_at": "document_end" 26 | } 27 | ], 28 | "background": { 29 | "scripts": ["js/extensions/background.js"] 30 | }, 31 | "web_accessible_resources": [ 32 | "js/extensions/preloadHook.js", 33 | "js/libraries/caretposition.js", 34 | "js/libraries/fuse.min.js", 35 | "js/internals/all.js" 36 | ], 37 | "browser_action": { 38 | "default_icon": "img/icon64.png", 39 | "default_popup": "html/popup.html" 40 | }, 41 | "permissions": [ 42 | "storage", 43 | "https://www.chatwork.com/*", 44 | "https://kcw.kddi.ne.jp/*" 45 | ], 46 | "options_page": "html/emoticon.html", 47 | "icons": { 48 | "32": "img/icon32.png", 49 | "64": "img/icon64.png", 50 | "128": "img/icon128.png" 51 | }, 52 | "homepage_url": "http://chatpp.thangtd.com" 53 | } 54 | -------------------------------------------------------------------------------- /changelogs.md: -------------------------------------------------------------------------------- 1 | Chat++ Change Logs 2 | ## 5.10.2 3 | - Fix XSS bug 4 | 5 | ## 5.10.1 6 | - Fix emoticons title 7 | 8 | ## 5.10.0 9 | - Update new mechanism to display Chat++'s emoticons, by modifying Node text. Thanks to **Bui The Hanh** for the idea 10 | 11 | ## 5.9.5 12 | - Chatwork updated again, which made some feature break. This release fixed these errors. 13 | - Fix mention feature 14 | - Fix quick tag feature (`[info]`, `[title]`, `[code]` ...) 15 | 16 | ## 5.9.4 17 | - Fix legacy's styles 18 | 19 | ## 5.9.3 20 | - Chatwork updated again, which made Chat++ break. This small path brings some Chat++'s features back. However the emoticon feature does not work, and it will be fixed in another update later, when we manage to find how to handle Chatwork's changes. 21 | 22 | ## 5.9.2 23 | - Chatwork updated their UI again, which made Chat++'s custom buttons in Chat Send Tool disapprear. The new version bring them back 24 | 25 | ## 5.9.1 26 | 27 | - Fix bug duplicate Quote message 28 | 29 | ## 5.9.0 30 | 31 | - Chatwork updated again, which applied an extremely confusing logic which then made the quote feature broken. We added our own quote message feature to fix this. 32 | 33 | ## 5.8.3 34 | 35 | - Fix bug with duplicate messages in Reply Message Panel 36 | - Vertically align text in the same line with Chat++ emoticons 37 | 38 | ## 5.8.2 39 | 40 | - Fix bug missing message content when rendering Chat++ emoticons 41 | 42 | ## 5.8.1 43 | 44 | - Update new Mechanism to bring the Emoticons feature back again. However, the new Mechanism does not have as good performance as the old one (which does not work anymore), so you you should consider turning the Emoticon Feature off if you find that it makes your Chatwork become too slow. 45 | - **Known Problem:** Chat++'s emoticons in the current room are not displayed when Chatwork is first loaded. We will try to fix this problem in the next release. 46 | 47 | ## 5.8.0 48 | 49 | - Temporarily disable emoticon feature due to Chatwork's code changed 50 | 51 | ## 5.7.1 52 | 53 | - Chatwork updated their codes again, which made some features of Chat++ broken (including emoticons feature). This patch fixed the problem. 54 | 55 | ## 5.7.0 56 | 57 | - Add new feature to add all Members mentioned in Chat area into the current Room. To use this feature, firstly mention all members in the Chat input area, then click Chatpp's `+` button in the Chat text toolbar. 58 | 59 | ## 5.6.3 60 | 61 | - Bring old Emoticons mechanism back 62 | - Bring shortcut feature for some buttons (Edit, Link, Task, Quote) back 63 | 64 | ## 5.6.2 65 | 66 | - Little change to make DOM replaced mechanism more stable 67 | 68 | ## 5.6.1 69 | 70 | - Chatwork updated again!!!, which conflicts with current Chat++'s mechanism to display Emoticons, and causes errors when editting message. Therefore, we temporarily switch to DOM replaced mechanism, which is much less efficient, but at least, it works (^^;). We will try to find other way to bring the old mechanism back. 71 | 72 | ## 5.6.0 73 | 74 | - Update new mechanism to bring the Emoticons feature back, by disabling rendering by AST. This method intervenes quite deeply into the way Chatwork's frontend works, and it probably will not be valid anymore in the future. However, we will try to fix again when it is broken. 75 | 76 | ## 5.5.1 77 | 78 | - Temporarily disable new emoticons mechanism due to a critical bug with editting message. The feature will be turned on again when we can find the way to fix the problem. 79 | 80 | ## 5.5.0 81 | 82 | - Update Chat++ Emoticons mechanism. Instead of pushing emoticons to Chatwork's default emoticons list (which do not work anymore), Chat++ directly update text from DOM 83 | - TO DO List: 84 | - Update Emoticons in reply popup 85 | - Update Emoticons in search popup 86 | - Update Emoticons in room description 87 | 88 | ## 5.4.10 89 | 90 | - Fix Legacy Style 91 | 92 | ## 5.4.9 93 | 94 | - Remove reply button description ("Replied to", "Đã trả lời cho", "返信元" ...) 95 | 96 | ## 5.4.8 97 | 98 | - Remove unnecessary extension's permission 99 | 100 | ## 5.4.7 101 | 102 | - On 03, March, Chatwork suddenly changed their design, which looks terrible. This patch brings some legacy styles back! Hope that Chatwork can realize their mistake and have an action soon. 103 | 104 | ## 5.4.6 105 | 106 | - Temporarily fix bug can not load Chat++ due to the change in Frontend Codes of Chatwork 107 | 108 | ## 5.4.5 109 | 110 | - Fix `TO ALL >>>` does not highlight messsage 111 | 112 | ## 5.4.4 113 | 114 | - Fix display wrong icon in emotion tab and remove unnecessary action in emotion when change room 115 | 116 | ## 5.4.3 117 | 118 | - Bring the Emoticon List button back 119 | 120 | ## 5.4.2 121 | 122 | - Fix bug can not edit message (write new or delete old) without inserting `Enter` 123 | 124 | ## 5.4.1 125 | 126 | - Fix bug can not use Mention feature when Emoticon feature is disabled 127 | - Remove Thumbnail and Highlight feature because of not being used much, and bad performance 128 | 129 | ## 5.4.0 130 | 131 | - Chatwork updated their codes, with many big changes to the Frontend. Therefore, many features of Chat++ broke. This big update fix the problem with Emoticon feature, and add a temporarily solution for the Mention feature 132 | - There may be some minor feature which are not working. There will be some other paths for them later 133 | 134 | ## 5.3.1 135 | 136 | - Fix bug that can not add new Emoticon link 137 | 138 | ## 5.3.0 139 | 140 | - As from Chrome 73, [Chrome removed the ability to make cross-origin requests in content scripts](https://www.chromium.org/Home/chromium-security/extension-content-script-fetches). Chat Plus Plus had to make changes to fetch emoticons data. 141 | - Chat Plus Plus now only supports Emoticon Links from Dropbox. All other links will not be accepted. 142 | 143 | ## 5.2.1 144 | 145 | - Fix bug can not select emoticon when using Vietnamese Unikey 146 | - Fix bug can not select emoticon at new line 147 | 148 | ## 5.2.0 149 | 150 | - Remove `E` (Emoticon), `S` (Shortcut), `M` (Mention) buttons in chatbox toolbar 151 | - Add `[info]`, `[title]` and `[code]` buttons in chatbox toolbar 152 | - Fix bug with mouse hover when mention suggestion displayed 153 | - Separate emoticons in to tabs in the Emoticon List popup 154 | - Add emoticons popup suggestion with `::` keyword 155 | 156 | ## 5.1.22 157 | 158 | - Fix bug: can not load some external libraries 159 | - Fix a problem with CSS style 160 | 161 | ## 5.1.21 162 | 163 | - Rearrange emoticons list into tabs base on Emoticon Data set. 164 | 165 | ## 5.1.20 166 | 167 | - Move to message with [toall] by using shortcut. 168 | 169 | ## 5.1.19 170 | 171 | - Mix-gulp Laravel mix 172 | 173 | ## 5.1.18 174 | 175 | - Fix the problem with using innerHTML 176 | 177 | ## 5.1.17 178 | 179 | - Fix problem with "Remove user from same rooms" feature 180 | 181 | ## 5.1.16 182 | 183 | - Fix thumbnails aren't displayed issue. Check [PR#63](https://github.com/wataridori/chatpp/pull/63) 184 | 185 | ## 5.1.15 186 | 187 | - Fix bug with Chat++ Emoticon size (due to Chatwork's codes changed) 188 | 189 | ## 5.1.14 190 | 191 | - Fix bug can not delete a user from a room (due to Chatwork's codes changed) 192 | 193 | ## 5.1.13 194 | 195 | - Fix several bugs with Room Info button, Emoticon list button, Mention Suggestion popup style (due to Chatwork's codes changed) 196 | 197 | ## 5.1.12 198 | 199 | - Fix bug with popup notification 200 | - Add external css to fix the problem when displaying url with big emoticons 201 | 202 | ## 5.1.11 203 | 204 | - Fix bug with sending message by Enter key 205 | 206 | ## 5.1.10 207 | 208 | - Add external css to fix the problem when displaying user avatar with big emoticons 209 | 210 | ## 5.1.9 211 | 212 | - Fix bug when turning off Emoticon feature 213 | 214 | ## 5.1.8 215 | 216 | - Add CC feature by using `@_cc_`. See [PR#60](../../pull/60) 217 | - Fix problem with some special emoticons 218 | 219 | ## 5.1.7 220 | 221 | - Add support for both Chatwork's old and new Javascript code 222 | 223 | ## 5.1.6 224 | 225 | - Fix the problem with Emoticon feature 226 | 227 | ## 5.1.5 228 | 229 | - Temporarily disable Emoticon feature due to the changes in ChatWork's Javascript Codes 230 | 231 | ## 5.1.4 232 | 233 | - Remove **TO ALL** button due to the change of ChatWork 234 | (which makes **TO ALL** button does not work anymore) 235 | 236 | ## 5.1.3 237 | 238 | - Improve **TO ALL** feature mechanism 239 | 240 | ## 5.1.2 241 | 242 | - Improve **TO ALL** feature mechanism 243 | 244 | ## 5.1.1 245 | 246 | - Fix bugs with **TO ALL** feature 247 | - Improve the feature for removing user from same rooms 248 | 249 | ## 5.1.0 250 | 251 | - Add **TO ALL** feature 252 | - Add button to search for the same rooms with other users on their profile popup 253 | (which is appeared when clicks on users avatar) 254 | - Add feature to quickly remove a user from all the rooms that you are an Administrator 255 | - Change the mechanism for scrolling to bottom by using shortcut (`s`) 256 | 257 | ## 5.0.8 258 | 259 | - Fix bugs with scroll to mentioned messages by using shortcut (`j` and `k`) 260 | - Change the mechanism for scrolling to bottom by using shortcut (`s`) 261 | 262 | ## 5.0.7 263 | 264 | - Add function for searching the same rooms by person 265 | 266 | ## 5.0.6 267 | 268 | - Fix bug do not display new line with code tag 269 | 270 | ## 5.0.5 271 | 272 | - Wrap long text inside code tag by default, when Code Highlight Feature is turned on. 273 | 274 | ## 5.0.4 275 | 276 | - Rearrange third party libraries structure according to requirement from Firefox. 277 | 278 | ## 5.0.3 279 | 280 | - Add notice about the **Thumbnail** and **Code Highlight** features. 281 | 282 | ## 5.0.2 283 | 284 | - Fix bug with group remove button in Group page 285 | 286 | ## 5.0.1 287 | 288 | - Fix bug with some special emoticons 289 | 290 | ## 5.0.0 291 | 292 | - Chat++ is rewritten in [ECMAScript 6](http://www.ecma-international.org/publications/standards/Ecma-262.htm), 293 | transformed by [babel](https://babeljs.io/), built by [gulp](http://gulpjs.com/). 294 | Refer the [Contribution Guidelines](./CONTRIBUTING.md) file for more information. 295 | - Emoticon Data File Structure changed. The `regex` field is not necessary anymore. (However, you can add it if you want) 296 | 297 | ``` 298 | // Before 299 | "emoticons": [ 300 | {"key": "(rofl2)", "regex": "\\(rofl2\\))", "src": "https://i.imgur.com/UD2NE5U.gif"}, 301 | ] 302 | 303 | // From 5.0.0 304 | "emoticons": [ 305 | {"key": "(rofl2)", "src": "https://i.imgur.com/UD2NE5U.gif"}, 306 | ] 307 | ``` 308 | 309 | - New mention methods. Use `@admin` to mention all room's admins, 310 | use `@random` to randomly mention a member inside the room. 311 | - No longer show error messages when there are some emoticon data that can not be loaded. 312 | Display a `ERRORS` text next to the Emoticon text instead. 313 | - Immediately turn on or off emoticons when toggle the `E` button in the Chat toolbar. 314 | - Add popup to show all external emoticons 315 | - Bug Fixes 316 | 317 | ## 4.3.2 318 | 319 | - Fix bug with "new" text in Chat++ icon 320 | 321 | ## 4.3.1 322 | 323 | - Fix bug with sending message by Shift + Enter 324 | 325 | ## 4.3.0 326 | 327 | - Disabled Desktop Notification Feature (base on registered rooms) 328 | - Quick Title Tag, Quick Info Tag 329 | 330 | ```` 331 | ``` 332 | ``` => tag [code][/code] (default) 333 | 334 | ``i 335 | ``` => tag [info][/info] 336 | 337 | ``t 338 | ``` => tag [title][/title] 339 | ```` 340 | 341 | - Change emoticon alt attribute value (From only emoticon text to emoticon text plus emoticon data name) 342 | From now, you can check the emoticon data that an emoticon belongs to by hovering the mouse over the emoticon 343 | - Display a notification text (**new**) inside the Chat++ icon when Chat++ is updated 344 | 345 | ## 4.2.0 346 | 347 | - Display detailed error when can not load or parse Emoticons data in both Emoticons page and Chatwork 348 | - Rewrite Emoticons data loading flow. Now if there are errors with Emoticons data files, only that data will be disabled. Chat++'s features, including emoticon, will still work 349 | (In previous versions, failing in loading or parsing Emoticons data file will cause Chat++ become unable to work) 350 | - Display confirmation alert when clicking at "Reset" button in Emoticons page 351 | - Display countdown message before Chat++ is loaded 352 | - Bug fix 353 | 354 | ## 4.1.0 355 | 356 | - Code Highlight: Support more languages 357 | - Support specifying the language of the code 358 | - Code block will be applied word wrap by default. Support "nowrap" option 359 | Example 360 | 361 | ``` 362 | ruby 363 | #ruby code, long text is wrapped by default 364 | puts "This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text" 365 | ``` 366 | 367 | ``` 368 | ruby nowrap 369 | #ruby code, do not wrap long text 370 | puts "This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text. This is a super long text" 371 | ``` 372 | 373 | - Use dark theme for code highlight 374 | - Thumbnail feature is OFF by default. Please turn it on if you want to use it. 375 | - Thumbnail size (height) is rescaled from 150px to 125px 376 | - Display gyazo's https image thumbnail 377 | - Show thumbnail in Task view, as well as in Chat room description 378 | - Now, many Chat++ features such as external emoticons, code highlight, thumbnail display ... will be automatically applied after loading Chatwork 379 | (In previous versions, you must switch room to make everything become effective) 380 | 381 | ## 4.0.0 - Big Update 382 | 383 | - View list issues at [Version Four Milestone](../../milestones/Version%20Four) 384 | - Highlight Code 385 | - Display image thumbnail. Support direct image link, gyazo link, Facebook image link ... 386 | - Many new Shortcuts that help users to quickly switch Chat room. 387 | - Go to the first Room in the list 388 | - Go to the first nonstick Room in the list 389 | - Go to the Room below in the list 390 | - Go to the Room above in the list 391 | - Go to the first Room that has new unread Message(s) 392 | - Go to the first Room that has new unread Mention Message(s) 393 | - New setting page 394 | - New change logs page 395 | - New feature list page 396 | - Update Emoticons page. It is now easier to arrange data priority 397 | - Bug Fixes. 398 | 399 | ## 3.0.2 400 | 401 | - Fix bug with room title 402 | 403 | ## 3.0.1 404 | 405 | - Add Vietnamese Emoticon Data 406 | 407 | ## 3.0.0 - Big Update 408 | 409 | - View list issues at [Happy Birthday Release Milestone](../../milestones/Happy%20Birthday%20Release) 410 | - Room Shortcut Feature Added. 411 | - Mention Jump Feature Added. 412 | - Drop OFFENSIVE mode. 413 | - Allow users to reply their own message. 414 | - Add room info button. 415 | - Several Bug Fixes. 416 | 417 | ## 2.0.0 - Big Update 418 | 419 | - View list issues at [Happy Lunar New Year Release Milestone](../../milestones/Happy%20Lunar%20New%20Year%20Release) 420 | - Mention syntax changed. (use `_` for omitting username, use `.` for picon) 421 | - Mention users that are in friend list, but not present in Chat box (using `@#`) 422 | - Register Groups name and members. Mention Group. 423 | - Shortcut Feature Added. 424 | - Several Bug Fixes. 425 | 426 | ## 1.0.3 427 | 428 | - Add Skype data to official list 429 | - Fix bug with mention popup 430 | 431 | ## 1.0.2 432 | 433 | - Add official emoticons data list in option page 434 | - Temporarily remove change log 435 | 436 | ## 1.0.1 437 | 438 | - Mention user with Nickname. 439 | - Fix bug: Can not initialize Default Emoticons Data at the first load. 440 | - Fix bug: Two emoticons data have the same priority. 441 | 442 | ## 1.0.0 443 | 444 | - Change version for release purpose. 445 | - View list issues at [Happy New Year Release Milestone](../../milestones/Happy%20New%20Year%20Release) 446 | 447 | ## 0.1.3 448 | 449 | - Fix bug with arrows inputted 450 | - Apply multiple emoticons data 451 | 452 | ## 0.1.2 453 | 454 | - Fix bug with `version_type` in Manifest 455 | 456 | ## 0.1.0 457 | 458 | - Add secret emoticons 459 | 460 | ## 0.0.9 461 | 462 | - Add `@_all` and `@__all` feature 463 | - Add `@_me` and `@__me` feature 464 | 465 | ## 0.0.8 466 | 467 | - Add `@me` feature 468 | - Add `@all` feature 469 | - Add show picon only when type `@__` 470 | - Add show TO and picon only when type `@_` 471 | - Move popup to left when is hidden 472 | 473 | ## 0.0.7 474 | 475 | - Press ESC don't hide the pop up 476 | - Send message when choose press Enter to send setting 477 | 478 | ## 0.0.6 479 | 480 | - Now It is possible to change Mention Status from popup page. The status is sync via google account like Emoticon Status. 481 | 482 | ## 0.0.5 483 | 484 | - Reverse emoticons change logs order in option page. 485 | - Add button to turn ON/OFF Mention Feature. 486 | -------------------------------------------------------------------------------- /features.md: -------------------------------------------------------------------------------- 1 | Chat++ Features 2 | ================= 3 | 4 | ### [English](#english-1) 5 | ### [Tiếng Việt](#vietnamese) 6 | 7 | ## English 8 | 9 | ### Emoticons 10 | * More funny Emoticons for Chatwork. 11 | 12 | * You can easily create your own Emoticons data and share it with your friends. 13 | 14 | ### Mention 15 | * Easily Mention a person by typing `@` (like the way you mention person in Facebook, Twitter or Github ...) 16 | 17 | * Mention all people in a group by typing `@all` 18 | 19 | * Mention your self by typing `@me` 20 | 21 | * Mention admins in room by typing `@admin` 22 | 23 | * Mention a random member by typing `@random` 24 | 25 | * Mention people in your friend list even if they are not present in current chat room by typing `@#` 26 | 27 | * Mention a person or all people __without name__ by adding `_` 28 | 29 | * Just display user picon __(without mentioning)__ by adding `.` 30 | 31 | * Register a group (which contains many people) and then mention all people in group by typing only Group name 32 | 33 | * Some examples 34 | ``` 35 | Mention all @all 36 | Mention yourself without name @_me 37 | Display only yourself picon @.me 38 | Display only yourself picon without name @._me 39 | Mention a person outside the room (but in your friend list) @#a person 40 | Mention a person outside the room (but in your friend list) without displaying his/her name @#_a person 41 | Mention all people in a registered group @group name 42 | Display only picon of people in group, without name @._group name 43 | ``` 44 | 45 | * **TO ALL**: By clicking "TO ALL" (or "全員に通知") button which is set next to the message sending button, 46 | a message that mentions all people in the room will be created, then automatically deleted.
47 | It's only used to gain notification from Chatwork. Your real message will be sent later.
48 | This feature is expected to be used in the room that has too many people so the normal way to mention all can make browser freeze.
49 | You can make TO ALL message without notification by **starting** your message with `TO ALL >>>`
50 | For example: 51 | ``` 52 | TO ALL >>> This message will have special effect 53 | ``` 54 | Currently, this feature is limited to only the **administrators** of the rooms that have more than **100** users. 55 | 56 | ### Shortcuts 57 | * Many additional shortcuts, such as 58 | * Scroll to the end of Chat room 59 | * Reply Message 60 | * Edit Message 61 | * Quote Message 62 | * Go to user's own Chat room 63 | * Go to next Mention Message 64 | * Go to previous Mention Message 65 | * Go to the first Room in the list 66 | * Go to the first nonstick Room in the list 67 | * Go to the Room below in the list 68 | * Go to the Room above in the list 69 | * Go to the first Room that has new unread Message(s) 70 | * Go to the first Room that has new unread Mention Message(s) 71 | 72 | * Register a room for the keyboard from 0 to 9. When a room is assigned to a number, you can use the number to quickly jump to that room 73 | 74 | ### Message Display Improvement 75 | * Show thumbnail below the image link (support direct image link, gyazo link and Facebook image link) 76 | 77 | * Highlight code 78 | 79 | ### Other 80 | * Easily check Room Information. 81 | 82 | * Search for same rooms with another user 83 | 84 | * Remove a user from all the rooms that you are a administrator 85 | 86 | * Disabled Desktop Notification Feature (base on registered rooms) 87 | 88 | * Quick Info tag, quick Title tag 89 | 90 | * If for some reasons, you do not like a Chat++'s feature, you can easily turn it off. It will not effect other features. 91 | 92 | * And more awesome features ... 93 | 94 | ## Vietnamese 95 | 96 | ### Tính năng Emoticons 97 | * Thêm vào nhiều Emoticons vui nhộn cho Chatwork. 98 | 99 | * Bạn có thể dễ dàng tạo ra một bộ data Emoticons của riêng mình, và chia sẻ nó với bạn bè của mình. 100 | 101 | ### Tính năng Mention 102 | * Dễ dàng mention một người bằng cách gõ `@` (Giống như cách mà bạn mention trên Facebook, Twitter hay Github ...) 103 | 104 | * Mention tất cả mọi người trong một group bằng cách gõ `@all` 105 | 106 | * Mention bản thân mình bằng cách gõ `@me` 107 | 108 | * Mention tất cả các admins trong room bằng cách gõ `@admin` 109 | 110 | * Mention một thành viên ngẫu nhiên trong room bằng cách gõ `@random` 111 | 112 | * Mention cả những người không có trong room nhưng có trong friend list của bạn bằng cách gõ `@#` 113 | 114 | * Mention một người nhưng loại bỏ đi phần tên bằng cách thêm vào `_` 115 | 116 | * Chỉ hiện lên picon của một người (không mention) bằng cách thêm vào `.` 117 | 118 | * Đăng ký một group (gồm nhiều người trong đó), và rồi mention tất cả mọi người trong group bằng cách gõ tên group ấy 119 | 120 | * Một vài ví dụ 121 | ``` 122 | Mention tất cả mọi người trong room mà không chèn vào tên @_all 123 | Hiện picon của bản thân @.me 124 | Hiện picon của bản thân mà không thêm vào tên @._me 125 | Mention một người ở bên ngoài room, nhưng có trong danh sách bạn bè của mình @#person 126 | Mention một người ở bên ngoài room, nhưng có trong danh sách bạn bè của mình, không thêm vào tên @#_person 127 | Mention tất cả mọi người ở trong một group đã đăng ký @group name 128 | Hiện picon của tất cả mọi người ở trong một group đã đăng ký mà không thêm vào tên tên @._group name 129 | ``` 130 | 131 | * **TO ALL**: Bằng việc sử dụng nút **"TO ALL"** (hay **"全員に通知"**), nút được đặt cạnh nút send message thông thường,
132 | bạn sẽ có thể gửi một message mentions tất cả các thành viên trong room, và rồi message đó sẽ tự động được xoá.
133 | Message đó được sử dụng với mục đích là lấy notification từ Chatwork. Nội dung thật sự của bạn sẽ được gửi ngay sau đấy.
134 | Tính năng này được hy vọng sẽ là giải pháp cho tình trạng mention tất cả các thành viên trong một room có quá nhiều người dẫn đến việc làm freeze trình duyệt.
135 | Ngoài ra, bạn cũng có thể tạo message TO ALL có hiệu ứng đặt biệt mà không có notification bằng cách bắt đầu message của bình với `TO ALL >>>`
136 | Ví dụ: 137 | ``` 138 | TO ALL >>> Đây là một message có hiệu ứng đặc biệt 139 | ``` 140 | Hiện tại, chức năng này chỉ có thể được sử dụng bởi **admin** của một room có hơn **100** thành viên. 141 | 142 | ### Shortcuts 143 | * Thêm vào rất nhiều những phím tắt, ví dụ: 144 | * Scroll xuống cuối của Chat room 145 | * Reply Message 146 | * Edit Message 147 | * Quote Message 148 | * Di chuyển đến room chat của bạn 149 | * Di chuyển đến message mà bạn được mention tiếp theo 150 | * Di chuyển đến message mà bạn được mention phía trước 151 | * Di chuyển đến room đầu tiên trong danh sách 152 | * Di chuyển đến room đầu tiên ngoài những room được stick trong danh sách 153 | * Di chuyển đến room phía dưới room hiện tại 154 | * Di chuyển đến room phía trên room hiện tại 155 | * Di chuyển đến room có message chưa đọc ở trên cùng 156 | * Di chuyển đến room có mention message chưa đọc ở trên cùng 157 | 158 | * Đăng ký một room với một trong các phím số từ 0 đến 9. Khi một room được gắn với một số nào đó thì bạn có thể dùng số đó để di chuyển đến room đã đăng ký. 159 | 160 | ### Cải thiện hiển thị Message 161 | * Hiện ảnh thumbnail ở phía dưới link ảnh (hỗ trợ link ảnh direct, link ảnh gyazo, hay link ảnh Facebook) 162 | 163 | * Highlight code 164 | 165 | ### Other 166 | * Dễ dàng check thông tim về một room 167 | 168 | * Tìm kiếm những room chung với một user khác 169 | 170 | * Xoá một user ra khỏi các room mà bạn là Administrator 171 | 172 | * Tắt Desktop Notification dựa trên những room đăng ký 173 | 174 | * Tạo nhanh Info tag, Title tag 175 | 176 | * Nếu vì lý do nào đó mà bạn không thích một tính năng của Chat++, thì bạn có thể tắt tính năng đó đi. 177 | Việc làm đó sẽ không ảnh hưởng đến các tính năng khác. 178 | 179 | * Và nhiều tính năng tuyệt vời khác ... -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var elixir = require("laravel-elixir"); 2 | 3 | elixir.config.assetsPath = "src"; 4 | elixir.config.publicPath = "build"; 5 | elixir.config.sourcemaps = false; 6 | 7 | elixir(function(mix) { 8 | mix.scripts([ 9 | "libraries/bootstrap.min.js", 10 | "libraries/bootbox.min.js" 11 | ], "build/js/externals/libs.js") 12 | .browserify("externals/popup.js", "build/js/externals/popup.js") 13 | .browserify([ 14 | "externals/emoticon.js", 15 | "externals/shortcut.js", 16 | "externals/notification.js", 17 | "externals/room.js", 18 | "externals/setting.js", 19 | "externals/group.js", 20 | ], "build/js/externals/pages.js") 21 | .browserify("extensions/contentscript.js", "build/js/extensions/contentscript.js") 22 | .browserify("extensions/background.js", "build/js/extensions/background.js") 23 | .browserify("internals/main.js", "build/js/internals/all.js"); 24 | }); 25 | -------------------------------------------------------------------------------- /images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/images/1.png -------------------------------------------------------------------------------- /images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/images/2.png -------------------------------------------------------------------------------- /images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/images/3.png -------------------------------------------------------------------------------- /images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/images/4.png -------------------------------------------------------------------------------- /images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wataridori/chatpp/832285c75bc176c1ab98939c363bb2c72b418fe5/images/5.png -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/build/js/internals/all.js": "/build/js/internals/all.js", 3 | "/build/js/externals/pages.js": "/build/js/externals/pages.js", 4 | "/build/js/extensions/contentscript.js": "/build/js/extensions/contentscript.js", 5 | "/build/js/externals/popup.js": "/build/js/externals/popup.js", 6 | "/build/js/extensions/background.js": "/build/js/extensions/background.js", 7 | "/build/js/externals/libs.js": "/build/js/externals/libs.js", 8 | "/build/js/extensions/injectPreloadHook.js": "/build/js/extensions/injectPreloadHook.js" 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatpp", 3 | "description": "A Chatwork Enhancement Toolkit", 4 | "keywords": [ 5 | "Chatwork", 6 | "Chatpp", 7 | "Chrome", 8 | "Extension", 9 | "Firefox", 10 | "Extension" 11 | ], 12 | "author": "Tran Duc Thang - Nguyen Anh Tien", 13 | "license": "GPL-3.0", 14 | "bugs": { 15 | "url": "https://github.com/wataridori/chatpp/issues" 16 | }, 17 | "homepage": "https://chatpp.thangtd.com", 18 | "private": true, 19 | "dependencies": { 20 | "babel-plugin-transform-class-properties": "^6.4.0", 21 | "babel-preset-es2015": "^6.24.1", 22 | "babel-preset-react": "^6.3.13", 23 | "good-listener": "^1.2.2", 24 | "gulp": "^3.9.1", 25 | "jquery": "^3.3.1", 26 | "laravel-elixir": "6.0.0-9", 27 | "laravel-mix": "^2.1.11", 28 | "select": "^1.1.2", 29 | "tiny-emitter": "^2.0.2" 30 | }, 31 | "scripts": { 32 | "test": "eslint src", 33 | "dev": "NODE_ENV=development node_modules/webpack/bin/webpack.js --config=node_modules/laravel-mix/setup/webpack.config.js --progress --hide-modules", 34 | "production": "NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 35 | }, 36 | "devDependencies": { 37 | "babel-eslint": "^6.0.0", 38 | "laravel-elixir-browserify-official": "^0.1.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/css/highlight.min.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;padding:.5em;background:#2b2a27;color:#ede0ce}.hljs-comment,.hljs-javadoc,.hljs-template_comment{color:#7a7267}.hljs-keyword,.hljs-request,.hljs-status,.nginx .hljs-title,.ruby .hljs-function .hljs-keyword{color:#26A6A6}.apache .hljs-cbracket,.coffeescript .hljs-attribute,.hljs-attr_selector,.hljs-cdata,.hljs-date,.hljs-filter .hljs-argument,.hljs-function .hljs-keyword,.hljs-list .hljs-title,.hljs-string,.hljs-sub .hljs-keyword,.hljs-tag .hljs-value,.method,.tex .hljs-command{color:#bcd42a}.hljs-subst{color:#DAEFA3}.hljs-regexp{color:#E9C062}.hljs-decorator,.hljs-pi,.hljs-prompt,.hljs-shebang,.hljs-sub .hljs-identifier,.hljs-tag,.hljs-tag .hljs-keyword,.hljs-title{color:#ff5d38}.hljs-number,.hljs-symbol,.ruby .hljs-symbol .hljs-string{color:#bcd42a}.clojure .hljs-attribute,.hljs-params,.hljs-variable{color:#26a6a6}.css .hljs-tag,.hljs-pseudo,.hljs-rules .hljs-property,.tex .hljs-special{color:#ff5d38}.css .hljs-class,.hljs-rules .hljs-keyword{color:#26a6a6}.hljs-rules .hljs-value{color:#ff5d38}.css .hljs-id{color:#8B98AB}.apache .hljs-sqbracket,.hljs-annotation,.nginx .hljs-built_in{color:#9B859D}.hljs-pragma,.hljs-preprocessor{color:#8996A8}.css .hljs-value .hljs-number,.hljs-hexcolor{color:#DD7B3B}.css .hljs-function{color:#DAD085}.diff .hljs-header,.hljs-chunk,.tex .hljs-formula{background-color:#0E2231;color:#F8F8F8;font-style:italic}.diff .hljs-change{background-color:#4A410D;color:#F8F8F8}.hljs-addition{background-color:#253B22;color:#F8F8F8}.hljs-deletion{background-color:#420E09;color:#F8F8F8}.coffeescript .javascript,.javascript .xml,.tex .hljs-formula,.xml .css,.xml .hljs-cdata,.xml .javascript,.xml .vbscript{opacity:.5} -------------------------------------------------------------------------------- /src/js/extensions/background.js: -------------------------------------------------------------------------------- 1 | let common = require("../helpers/Common.js"); 2 | let ChromeStorageLocal = require("../helpers/ChromeStorageLocal.js"); 3 | let chrome_storage_local = new ChromeStorageLocal(); 4 | let Const = require("../helpers/Const.js"); 5 | 6 | chrome.runtime.onInstalled.addListener(() => { 7 | chrome_storage_local.get((data) => { 8 | let version; 9 | if (data) { 10 | version = data["version"]; 11 | } 12 | if (!version || version != common.app_detail.version) { 13 | chrome.browserAction.setBadgeText({text: "new"}); 14 | } 15 | }); 16 | }); 17 | 18 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 19 | if (request.contentScriptQuery == "fetchEmoticonsData") { 20 | let url = `https://dl.dropboxusercontent.com/${request.query}`; 21 | getJSON(url, (error, responseData) => { 22 | if (error !== null) { 23 | let data = { 24 | success: false, 25 | data_name: request.data_name 26 | }; 27 | sendResponse(data); 28 | } 29 | responseData.success = true; 30 | sendResponse(responseData); 31 | }); 32 | } 33 | 34 | if (request.contentScriptQuery == "fetchAdvertisementData") { 35 | getJSON(Const.ADVERTISEMENT_URL, (error, responseData) => { 36 | sendResponse(responseData); 37 | }); 38 | } 39 | 40 | return true; 41 | }); 42 | 43 | function getJSON(url, callback) { 44 | let xhr = new XMLHttpRequest(); 45 | xhr.open("GET", url, true); 46 | xhr.responseType = "json"; 47 | xhr.onload = function() { 48 | let status = xhr.status; 49 | if (status === 200) { 50 | callback(null, xhr.response); 51 | } else { 52 | callback(status, xhr.response); 53 | } 54 | }; 55 | xhr.send(); 56 | } 57 | -------------------------------------------------------------------------------- /src/js/extensions/contentscript.js: -------------------------------------------------------------------------------- 1 | let Const = require("../helpers/Const.js"); 2 | let common = require("../helpers/Common.js"); 3 | let Storage = require("../helpers/Storage.js"); 4 | let EmoStorage = require("../helpers/EmoStorage.js"); 5 | let ChromeStorageLocal = require("../helpers/ChromeStorageLocal.js"); 6 | 7 | let storage = new Storage(); 8 | let emo_storage = new EmoStorage(); 9 | let emoticons = []; 10 | let emo_info = {}; 11 | let urls = {}; 12 | 13 | init(true); 14 | function init(inject_script) { 15 | storage.get(Const.CHROME_SYNC_KEY, (info) => { 16 | if (!$.isEmptyObject(info)) { 17 | for (let key in info) { 18 | let emo_data = info[key]; 19 | let url = common.getEmoticonDataUrl(emo_data.data_name, emo_data.data_url); 20 | if (url) { 21 | urls[emo_data.data_name] = url; 22 | } 23 | } 24 | } 25 | if ($.isEmptyObject(urls)) { 26 | urls["Default"] = Const.DEFAULT_DATA_URL; 27 | } 28 | if (info === undefined) { 29 | info = {}; 30 | } 31 | if (!info.force_update_version || info.force_update_version < Const.FORCE_TURN_OFF_THUMBNAIL) { 32 | info.force_update_version = Const.FORCE_TURN_OFF_THUMBNAIL; 33 | info.thumbnail_status = false; 34 | info.emoticon_status = true; 35 | info.theme_status = false; 36 | } 37 | emo_info = info; 38 | localStorage.force_update_version = info.force_update_version; 39 | let features = ["mention", "shortcut", "thumbnail", "emoticon", "legacy_theme"]; 40 | let features_default_false = ["legacy_theme"]; 41 | features.forEach((feature) => { 42 | let feature_name = `${feature}_status`; 43 | if (info[feature_name] === undefined) { 44 | info[feature_name] = features_default_false.includes(feature) ? false : true; 45 | } 46 | common.setStatus(feature, info[feature_name]); 47 | }); 48 | 49 | emo_storage.setFeatureStatus(info); 50 | if (info.emoticon_status == false) { 51 | addInjectedScript(); 52 | } else { 53 | getData(info, inject_script); 54 | } 55 | if(info.legacy_theme_status) { 56 | setTimeout(() => { 57 | $("").appendTo("head"); 58 | $("").appendTo("head"); 59 | $("").appendTo("head"); 60 | }, Const.DELAY_TIME + 1 61 | ); 62 | } 63 | }); 64 | 65 | localStorage[Const.LOCAL_STORAGE_GROUP_MENTION] = []; 66 | storage.get(Const.CHROME_SYNC_GROUP_KEY, (data) => { 67 | if (!$.isEmptyObject(data)) { 68 | localStorage[Const.LOCAL_STORAGE_GROUP_MENTION] = JSON.stringify(data); 69 | } 70 | }); 71 | 72 | localStorage[Const.LOCAL_STORAGE_ROOM_SHORTCUT] = []; 73 | storage.get(Const.CHROME_SYNC_ROOM_KEY, (data) => { 74 | if (!$.isEmptyObject(data)) { 75 | localStorage[Const.LOCAL_STORAGE_ROOM_SHORTCUT] = JSON.stringify(data); 76 | } 77 | }); 78 | 79 | localStorage[Const.LOCAL_STORAGE_DISABLE_NOTIFY_ROOM] = []; 80 | storage.get(Const.CHROME_SYNC_DISABLE_NOTIFY_ROOM_KEY, (data) => { 81 | if (!$.isEmptyObject(data)) { 82 | localStorage[Const.LOCAL_STORAGE_DISABLE_NOTIFY_ROOM] = JSON.stringify(data); 83 | } 84 | }); 85 | } 86 | 87 | function getData(info, inject_script) { 88 | let loaded_count = 0; 89 | let emo_count = common.getObjectLength(urls); 90 | let failed = false; 91 | localStorage.removeItem("failed_data"); 92 | $.each(urls, (data_name, url) => { 93 | let query = getUrlQuery(url); 94 | if (!query) { 95 | return false; 96 | } 97 | chrome.runtime.sendMessage({contentScriptQuery: "fetchEmoticonsData", query, data_name}, (data) => { 98 | if (data.success) { 99 | if (typeof(data.data_version) !== "undefined" && typeof(data.emoticons) !== "undefined") { 100 | data.data_url = urls[data.data_name]; 101 | let priority = (emo_info[data.data_name] && emo_info[data.data_name].priority) ? emo_info[data.data_name].priority : 0; 102 | emo_storage.pushData(data, priority); 103 | pushEmoticons(data.emoticons, priority, data.data_name); 104 | } 105 | } else { 106 | failed = true; 107 | delete emo_info[data.data_name]; 108 | pushFailedData(data.data_name); 109 | } 110 | 111 | if (++loaded_count === emo_count) { 112 | if (!failed) { 113 | emo_storage.syncData(); 114 | } 115 | let chrome_storage_local = new ChromeStorageLocal(); 116 | chrome_storage_local.get((local_data) => { 117 | let version_name = ""; 118 | if (!$.isEmptyObject(local_data)) { 119 | version_name = local_data["version_name"]; 120 | } 121 | // let current_time = (new Date).toLocaleString(); 122 | // console.log("You are using Chat++!. Date sync: " + current_time + ". Version: " + version_name); 123 | localStorage[Const.LOCAL_STORAGE_DATA_KEY] = JSON.stringify(emoticons); 124 | localStorage["chatpp_version_name"] = version_name; 125 | localStorage["emoticon_data_version"] = parseDataName(emo_info); 126 | if (inject_script !== undefined && inject_script) { 127 | addInjectedScript(); 128 | } else { 129 | // runFunction("reloadEmoticions()"); 130 | } 131 | }); 132 | } 133 | }); 134 | }); 135 | } 136 | 137 | function getUrlQuery(url) { 138 | let supported_urls = ["https://dl.dropboxusercontent.com/", "https://www.dropbox.com/"]; 139 | for (let supported_url of supported_urls) { 140 | if (url.startsWith(supported_url)) { 141 | return url.substring(supported_url.length); 142 | } 143 | } 144 | 145 | return false; 146 | } 147 | 148 | function pushFailedData(data_name) { 149 | let data = localStorage["failed_data"] ? JSON.parse(localStorage["failed_data"]) : []; 150 | data.push(data_name); 151 | localStorage["failed_data"] = JSON.stringify(data); 152 | } 153 | 154 | function parseDataName(data) { 155 | if (data.data_name !== undefined && data.data_version !== undefined) { 156 | return `${data.data_name}_${data.data_version}`; 157 | } 158 | let data_name = ""; 159 | for (let key in data) { 160 | if (data[key].data_name !== undefined) { 161 | data_name += `${data[key].data_name}_${data[key].data_version} `; 162 | } 163 | } 164 | return data_name; 165 | } 166 | 167 | function pushEmoticons(emos, priority, data_name) { 168 | for (let i = 0; i < emos.length; i++) { 169 | let repeated = false; 170 | emos[i].priority = priority; 171 | emos[i].data_name = data_name; 172 | for (let j = 0; j < emoticons.length; j++) { 173 | if (emoticons[j].key === emos[i].key) { 174 | if (emoticons[j].src !== emos[i].src && emoticons[j].priority < emos[i].priority) { 175 | emoticons[j] = emos[i]; 176 | } 177 | repeated = true; 178 | break; 179 | } 180 | } 181 | if (!repeated) { 182 | emoticons.push(emos[i]); 183 | } 184 | } 185 | } 186 | 187 | function addInjectedScript() { 188 | loadAdvertisement(); 189 | preLoad(); 190 | injectJsFile("libraries/caretposition.js"); 191 | injectJsFile("libraries/fuse.min.js"); 192 | setTimeout(() => { 193 | injectJsFile("internals/all.js"); 194 | }, Const.DELAY_TIME 195 | ); 196 | 197 | setInterval(loadAdvertisement, Const.ADVERTISEMENT_LOAD_TIMEOUT); 198 | } 199 | 200 | function preLoad() { 201 | let chat_send_tool = $("#_chatSendArea ul").first(); 202 | chat_send_tool.append( 203 | $("
  • ", { id: "_chatppPreLoad", css: { 204 | "display": "inline-block" 205 | } }).append( 206 | $("", { id: "chatppPreLoad" }) 207 | ) 208 | ); 209 | let chatpp_pre_load = $("#chatppPreLoad"); 210 | let delay_time = Const.DELAY_TIME / 1000; 211 | let pre_load_interval = setInterval(() => { 212 | if (--delay_time <= 0) { 213 | $("#_chatppPreLoad").remove(); 214 | window.clearInterval(pre_load_interval); 215 | } 216 | let text = `Chat++ will be loaded in about ${delay_time} second${delay_time > 1 ? "s" : ""}`; 217 | chatpp_pre_load.html(text); 218 | }, 1000); 219 | } 220 | 221 | function injectJsFile(file_name) { 222 | let script = document.createElement("script"); 223 | script.src = common.getExtensionPageUrl(`js/${file_name}`); 224 | (document.documentElement).appendChild(script); 225 | } 226 | 227 | function injectCssFile(file_name) { 228 | let css_link = $("", { 229 | rel: "stylesheet", 230 | type: "text/css", 231 | href: common.getExtensionPageUrl(`css/${file_name}`) 232 | }); 233 | css_link.appendTo("head"); 234 | } 235 | 236 | function loadAdvertisement() { 237 | chrome.runtime.sendMessage({contentScriptQuery: "fetchAdvertisementData"}, (data) => { 238 | if (!$.isEmptyObject(data)) { 239 | localStorage["chatpp_advertisement"] = JSON.stringify(data); 240 | } 241 | }); 242 | } 243 | -------------------------------------------------------------------------------- /src/js/extensions/injectPreloadHook.js: -------------------------------------------------------------------------------- 1 | var preload_hook_code = ` 2 | window.CHATPP_defineProperty = Object.defineProperty; 3 | window.esmodules = []; 4 | defineProperty_handler = { 5 | apply: function (target, thisArg, args) { 6 | r = target.apply(thisArg, args); 7 | if (args[1] == 'a') { 8 | window.esmodules.push(r); 9 | }; 10 | if (args[1] == 'searchUrlTokens') { 11 | console.log('Found Notation Module, restore Object.defineProperty!'); 12 | window.notation_module = r; 13 | Object.defineProperty = window.CHATPP_defineProperty; 14 | } 15 | return r; 16 | }, 17 | } 18 | Object.defineProperty = new Proxy(Object.defineProperty, defineProperty_handler) 19 | 20 | // backup Object.defineProperties 21 | window.CHATPP_defineProperties = Object.defineProperties; 22 | defineProperties_handler = { 23 | apply: function (target, thisArg, args) { 24 | r = target.apply(thisArg, args); 25 | if (r.EC14) { 26 | console.log('Final tagHash after last emo inserted, restore Object.defineProperties!'); 27 | window.emoticon_tag_hash_list = r; 28 | Object.defineProperties = window.CHATPP_defineProperties; 29 | } 30 | return r; 31 | }, 32 | } 33 | 34 | Object.defineProperties = new Proxy(Object.defineProperties, defineProperties_handler); 35 | `; 36 | 37 | var script = document.createElement('script'); 38 | script.textContent = preload_hook_code; 39 | (document.head || document.documentElement).appendChild(script); 40 | script.remove(); 41 | -------------------------------------------------------------------------------- /src/js/externals/emoticon.js: -------------------------------------------------------------------------------- 1 | let common = require("../helpers/Common.js"); 2 | let Const = require("../helpers/Const.js"); 3 | let EmoStorage = require("../helpers/EmoStorage.js"); 4 | let emo_storage = new EmoStorage(); 5 | let emoticons = []; 6 | let emo_info = {}; 7 | let urls = {}; 8 | let init = false; 9 | 10 | let official_emoticons_data = common.official_emoticons_data; 11 | 12 | $(() => { 13 | if (!common.isPage("emoticon")) { 14 | return; 15 | } 16 | common.setPageTitle(); 17 | emo_storage.get(Const.CHROME_SYNC_KEY, (info) => { 18 | if (!$.isEmptyObject(info)) { 19 | emo_info = info; 20 | emo_storage.setFeatureStatus(emo_info); 21 | for (let key in info) { 22 | let emo_data = info[key]; 23 | let url = common.getEmoticonDataUrl(emo_data.data_name, emo_data.data_url); 24 | if (url) { 25 | urls[emo_data.data_name] = url; 26 | } 27 | } 28 | } 29 | if ($.isEmptyObject(urls)) { 30 | init = true; 31 | urls["Default"] = common.getEmoticonDataUrl("Default"); 32 | } 33 | fillDataTable(); 34 | 35 | if (init) { 36 | getData(urls, reload); 37 | } else { 38 | getData(urls, fillTable); 39 | showOfficialData(); 40 | } 41 | }); 42 | 43 | $("#btn-reset").click(() => { 44 | bootbox.dialog({ 45 | title: "Reset Emoticon Data", 46 | message: "Your are trying to reset emoticon data, which will clear your current data information.
    " + 47 | "This action cannot be undone. Are you sure ?
    ", 48 | buttons: { 49 | success: { 50 | label: "OK!", 51 | className: "btn-success", 52 | callback() { 53 | emo_info = {}; 54 | getData({}, reload); 55 | } 56 | }, 57 | danger: { 58 | label: "Cancel!", 59 | className: "btn-danger" 60 | } 61 | } 62 | }); 63 | }) 64 | 65 | $("#btn-load").click(() => { 66 | if ($("#data-select").val() == "default") { 67 | getData(Const.DEFAULT_DATA_URL, reload); 68 | } else { 69 | let url = $("#data-url").val(); 70 | if (!common.validateDropboxUrl(url)) { 71 | bootbox.alert("Invalid URL! Make sure your inputted URL is correct, and a Dropbox link!"); 72 | } else { 73 | bootbox.dialog({ 74 | message: `The data from ${url} may contain undesirable emoticons and we will not be responsible for it` , 75 | title: "Your are trying to load data that is not officially supported by Chat++.
    Do you want to continue ?
    ", 76 | buttons: { 77 | success: { 78 | label: "OK!", 79 | className: "btn-success", 80 | callback() { 81 | urls["added"] = url; 82 | getData(urls, reload); 83 | } 84 | }, 85 | danger: { 86 | label: "Cancel!", 87 | className: "btn-danger", 88 | callback() { 89 | 90 | } 91 | } 92 | } 93 | }); 94 | } 95 | } 96 | }); 97 | 98 | $("#data-select").change((e) => { 99 | let val = $(e.currentTarget).val(); 100 | if (val == "default") { 101 | $("#url-input-div").hide("slow"); 102 | } else { 103 | $("#url-input-div").show("slow"); 104 | } 105 | }); 106 | }); 107 | 108 | function getData(urls, callback) { 109 | if ($.isEmptyObject(urls)) { 110 | urls["Default"] = Const.DEFAULT_DATA_URL; 111 | } 112 | let loaded_urls = []; 113 | $.each(urls, (i, url) => { 114 | if (loaded_urls.indexOf(url) === -1) { 115 | loaded_urls.push(url); 116 | } else { 117 | return; 118 | } 119 | $.getJSON(url) 120 | .done((data) => { 121 | if (typeof(data.data_version) !== "undefined" && typeof(data.emoticons) !== "undefined") { 122 | data.data_url = urls[data.data_name] ? urls[data.data_name] : urls["added"]; 123 | let priority = getPriority(data.data_name); 124 | emo_storage.pushData(data, priority); 125 | pushEmoticons(data.emoticons, priority, data.data_name); 126 | if (emo_storage.data_count === common.getObjectLength(urls)) { 127 | emo_storage.syncData(callback); 128 | } 129 | } else { 130 | bootbox.alert("Invalid data structure!"); 131 | } 132 | }).fail(() => { 133 | let message = "There is an error occurred when loading or parsing the following url:
    " + 134 | `${url}` + 135 | "
    It may be because of the failure in downloading file or invalid file format.
    " + 136 | "Check your file data carefully and try to reload again.
    " 137 | bootbox.alert(message); 138 | }); 139 | }); 140 | } 141 | 142 | function reload() { 143 | location.reload(); 144 | } 145 | 146 | function getPriority(data_name) { 147 | let max = 0; 148 | for (let key in emo_info) { 149 | let val = emo_info[key]; 150 | if (val.data_name === data_name) { 151 | return val.priority; 152 | } 153 | if (val.priority >= max) { 154 | max = val.priority + 1; 155 | } 156 | } 157 | 158 | return max; 159 | } 160 | 161 | function showOfficialData() { 162 | let official = $("#official-data"); 163 | for (let data_name in official_emoticons_data) { 164 | if (emo_info[data_name] === undefined) { 165 | let new_button = `
    ` 166 | + `${official_emoticons_data[data_name].description}` 167 | + "

    "; 168 | official.append(new_button); 169 | } 170 | } 171 | 172 | $(".btn-official-data").click((e) => { 173 | let data_name = $(e.currentTarget).data("name"); 174 | let url = official_emoticons_data[data_name].link; 175 | if (common.validateUrl(url)) { 176 | urls[data_name] = url; 177 | getData(urls, reload); 178 | } 179 | }); 180 | } 181 | 182 | function pushEmoticons(emos, priority, data_name) { 183 | for (let i = 0; i < emos.length; i++) { 184 | let disable = false; 185 | emos[i].priority = priority; 186 | for (let j = 0; j < emoticons.length; j++) { 187 | if (emoticons[j].emo.key === emos[i].key) { 188 | if (emoticons[j].emo.priority < emos[i].priority) { 189 | emoticons[j].status = true; 190 | } else { 191 | disable = true; 192 | } 193 | break; 194 | } 195 | } 196 | emoticons.push({ 197 | emo: emos[i], 198 | status: disable, 199 | data_name 200 | }); 201 | } 202 | } 203 | 204 | function clearTable() { 205 | $(".table-emo > tbody").html(""); 206 | } 207 | 208 | function fillTable() { 209 | clearTable(); 210 | let info = emo_storage.data; 211 | if (info.data_name != "Default" && info.data_url) { 212 | $("#data-select").val("custom"); 213 | $("#data-url").val(info.data_url); 214 | $("#url-input-div").show("slow"); 215 | $("#btn-show-changelog").show("slow"); 216 | } 217 | 218 | let table_text = ""; 219 | let current_data = null; 220 | let last_key = 0; 221 | let last_data_name = null; 222 | $.each(emoticons, (key, data) => { 223 | if (!current_data || current_data !== data.data_name) { 224 | if (table_text) { 225 | $(`#table-emo-${last_data_name}`).find("tbody").append(table_text); 226 | } 227 | current_data = data.data_name; 228 | table_text = ""; 229 | last_key = key; 230 | } 231 | last_data_name = data.data_name; 232 | if ((key - last_key) % 4 === 0) { 233 | table_text += ""; 234 | } 235 | table_text += createTableTd(data); 236 | if ((key - last_key) % 4 === 3) { 237 | table_text += ""; 238 | } 239 | }); 240 | $(`#table-emo-${last_data_name}`).find("tbody").append(table_text); 241 | } 242 | 243 | function createEmoticonsTable(name) { 244 | let table = 245 | "
    " + 246 | "
    " + 247 | `
    ${name}
    ` + 248 | `` + 249 | "" + 250 | "" + 251 | "" + 252 | "" + 253 | "" + 254 | "" + 255 | "" + 256 | "" + 257 | "" + 258 | "" + 259 | "
    EmoEmoEmoEmo
    " + 260 | "
    " 261 | "
    "; 262 | 263 | $("#emoticons-table").append(table); 264 | } 265 | 266 | function createTableTd(data) { 267 | let src = common.htmlEncode(common.getEmoUrl(data.emo.src)); 268 | let row = ""; 269 | let class_name = data.status ? "danger" : "info"; 270 | row += `${data.emo.key}`; 271 | row += ``; 272 | return row; 273 | } 274 | 275 | function createDataTableText(emo_data) { 276 | $("#table-data > tbody").html(""); 277 | let table_text = ""; 278 | let first = true; 279 | $.each(emo_data.slice().reverse(), (key, data) => { 280 | if (data.data_name !== undefined && data.data_url !== undefined) { 281 | let disabled = first ? "disabled" : ""; 282 | first = false; 283 | table_text += ""; 284 | table_text += `${data.data_name}`; 285 | table_text += `${data.data_version}`; 286 | table_text += `${createATag(data.data_url)}`; 287 | table_text += "" + 288 | `` + 289 | ``; 290 | table_text += ""; 291 | } 292 | }); 293 | $("#table-data").find("tbody").append(table_text); 294 | } 295 | 296 | function fillDataTable() { 297 | let emo_info_array = emoDataObjectToArray(emo_info); 298 | createDataTableText(emo_info_array); 299 | $.each(emo_info_array.slice().reverse(), (key, data) => { 300 | if (data.data_name !== undefined && data.data_url !== undefined) { 301 | createEmoticonsTable(data.data_name); 302 | } 303 | }); 304 | $("#btn-save").click(() => { 305 | let new_emo_storage = new EmoStorage(); 306 | for (let i in emo_info_array) { 307 | new_emo_storage.pushData(emo_info_array[i], emo_info_array[i].priority); 308 | } 309 | new_emo_storage.syncData(reload); 310 | }); 311 | $("#table-data").on("click", "button", (e) => { 312 | let button = $(e.currentTarget); 313 | if (button.hasClass("btn-data-move-up")) { 314 | let priority = button.data("priority"); 315 | let temp; 316 | let up = priority + 1; 317 | if (emo_info_array[up]) { 318 | temp = emo_info_array[up]; 319 | emo_info_array[up] = emo_info_array[priority]; 320 | emo_info_array[priority] = temp; 321 | emo_info_array[up].priority = up; 322 | emo_info_array[priority].priority = priority; 323 | } 324 | createDataTableText(emo_info_array); 325 | } 326 | 327 | if (button.hasClass("btn-data-remove")) { 328 | let name = button.data("name"); 329 | emo_storage.removeData(name); 330 | emo_storage.syncData(reload); 331 | } 332 | }); 333 | } 334 | 335 | function rearrangePriority(data) { 336 | let i = 0; 337 | let new_data = []; 338 | for (let j in data) { 339 | data[j].priority = i; 340 | new_data[i++] = data[j]; 341 | } 342 | 343 | return new_data; 344 | } 345 | 346 | function emoDataObjectToArray(data) { 347 | let data_array = []; 348 | $.each(data, (key, emo) => { 349 | if (emo.priority !== undefined) { 350 | data_array[emo.priority] = emo; 351 | } 352 | }); 353 | 354 | return rearrangePriority(data_array); 355 | } 356 | 357 | function createATag(url) { 358 | if (!url) { 359 | return ""; 360 | } 361 | return $("", { 362 | href: url, 363 | text: url, 364 | target: "_blank" 365 | }).prop("outerHTML"); 366 | } 367 | 368 | -------------------------------------------------------------------------------- /src/js/externals/group.js: -------------------------------------------------------------------------------- 1 | let Const = require("../helpers/Const.js"); 2 | let common = require("../helpers/Common.js"); 3 | let Storage = require("../helpers/Storage.js"); 4 | 5 | let storage = new Storage(); 6 | 7 | let groups = []; 8 | 9 | $(() => { 10 | if (!common.isPage("group")) { 11 | return; 12 | } 13 | common.setPageTitle(); 14 | 15 | storage.get(Const.CHROME_SYNC_GROUP_KEY, (data) => { 16 | if (!$.isEmptyObject(data)) { 17 | groups = data; 18 | syncData(); 19 | } 20 | }); 21 | 22 | let data_group_name = $("#data-group-name"); 23 | let data_group_member = $("#data-group-members"); 24 | 25 | $("#button-group-add").click(() => { 26 | let info = {}; 27 | info.group_name = data_group_name.val().trim(); 28 | let group_members = getGroupMembers($("#data-group-members").val()); 29 | if (validateGroupName(info.group_name) && group_members.length > 0) { 30 | info.group_members = group_members.join(", "); 31 | pushGroup(info); 32 | syncData(); 33 | clearInput(); 34 | data_group_name.focus(); 35 | } 36 | }); 37 | 38 | data_group_name.keyup((e) => { 39 | if (e.keyCode == 13) { 40 | data_group_member.focus(); 41 | } 42 | }); 43 | 44 | data_group_member.keyup((e) => { 45 | if (e.keyCode == 13) { 46 | $("#button-group-add").trigger("click"); 47 | } 48 | }); 49 | }); 50 | 51 | function clearInput() { 52 | $("#data-group-name").val(""); 53 | $("#data-group-members").val(""); 54 | } 55 | 56 | function fillDataTable() { 57 | let table_text = ""; 58 | let table_body = $("#table-data").find("tbody"); 59 | table_body.html(""); 60 | $.each(groups, (key, data) => { 61 | if (data.group_name !== undefined && data.group_members !== undefined) { 62 | table_text += ``; 63 | table_text += `${data.group_name}`; 64 | table_text += `${data.group_members}`; 65 | table_text += ``; 67 | table_text += ""; 68 | } 69 | }); 70 | table_body.append(table_text); 71 | $(".btn-data-remove").click((e) => { 72 | let name = $(e.currentTarget).data("name"); 73 | removeGroup(name); 74 | syncData(); 75 | }); 76 | } 77 | 78 | function pushGroup(info) { 79 | let found = false; 80 | $.each(groups, (index, data) => { 81 | if (info.group_name == data.group_name) { 82 | groups[index] = info; 83 | found = true; 84 | } 85 | }); 86 | 87 | if (!found) { 88 | groups.push(info); 89 | } 90 | } 91 | 92 | function removeGroup(name) { 93 | let found = -1; 94 | $.each(groups, (index, data) => { 95 | if (name == data.group_name) { 96 | found = index; 97 | } 98 | }); 99 | 100 | if (found >= 0) { 101 | groups.splice(found, 1); 102 | } 103 | } 104 | 105 | function validateGroupName(data) { 106 | if (data.length > 15 || data.length < 2) { 107 | return false; 108 | } 109 | 110 | return data.split(" ").length - 1 <= 2; 111 | } 112 | 113 | function getGroupMembers(data) { 114 | let members = data.split(","); 115 | let valid_members = []; 116 | for (let i = 0; i < members.length; i++) { 117 | let member = members[i]; 118 | member = member.trim(); 119 | if (member && $.isNumeric(member)) { 120 | if (valid_members.indexOf(member) === -1) { 121 | valid_members.push(member); 122 | } 123 | } 124 | } 125 | 126 | let regex = /\[[a-zA-Z]+:([0-9]+)\]/g; 127 | let match; 128 | while ((match = regex.exec(data)) != null) { 129 | valid_members.push(match[1]); 130 | } 131 | return valid_members; 132 | } 133 | 134 | function syncData(callback) { 135 | if (callback === undefined) { 136 | callback = fillDataTable; 137 | } 138 | storage.set(Const.CHROME_SYNC_GROUP_KEY, groups, callback); 139 | } 140 | 141 | -------------------------------------------------------------------------------- /src/js/externals/notification.js: -------------------------------------------------------------------------------- 1 | let Const = require("../helpers/Const.js"); 2 | let common = require("../helpers/Common.js"); 3 | let Storage = require("../helpers/Storage.js"); 4 | 5 | let storage = new Storage(); 6 | let disabled_notify_rooms = []; 7 | 8 | $(() => { 9 | if (!common.isPage("notification")) { 10 | return; 11 | } 12 | common.setPageTitle(); 13 | 14 | storage.get(Const.CHROME_SYNC_DISABLE_NOTIFY_ROOM_KEY, (data) => { 15 | if (!$.isEmptyObject(data)) { 16 | disabled_notify_rooms = data; 17 | loadData(); 18 | } 19 | }); 20 | 21 | $("#save-btn").click(() => { 22 | let rooms = $("textarea").val().split(","); 23 | disabled_notify_rooms = []; 24 | $.each(rooms, (index, room) => { 25 | let room_id = common.parseRoomId(room); 26 | if (room_id) { 27 | disabled_notify_rooms.push(room_id); 28 | } 29 | }) 30 | storage.set(Const.CHROME_SYNC_DISABLE_NOTIFY_ROOM_KEY, disabled_notify_rooms, common.reload); 31 | }); 32 | }); 33 | 34 | function loadData() { 35 | $("textarea").val(disabled_notify_rooms.join()); 36 | } 37 | -------------------------------------------------------------------------------- /src/js/externals/popup.js: -------------------------------------------------------------------------------- 1 | let common = require("../helpers/Common.js"); 2 | let Storage = require("../helpers/Storage.js"); 3 | let ChromeStorageLocal = require("../helpers/ChromeStorageLocal.js"); 4 | let Const = require("../helpers/Const.js"); 5 | 6 | let local_stored_data = {}; 7 | 8 | $(() => { 9 | setVersionType(); 10 | 11 | $("#chatpp_version").html(common.getAppFullName()); 12 | 13 | let pages = ["setting", "emoticon", "room", "group", "shortcut", "change_logs", "features", "notification"]; 14 | pages.forEach((page_name) => { 15 | let url = `html/${page_name}.html`; 16 | $(`#${page_name}_page`).click(() => { 17 | common.openNewUrl(url); 18 | }); 19 | }); 20 | 21 | $(".ext-url").click((e) => { 22 | common.openNewUrl($(e.currentTarget).attr("href")); 23 | }); 24 | 25 | chrome.storage.onChanged.addListener((changes) => { 26 | let data = changes[Const.CHROME_SYNC_KEY]; 27 | if (!$.isEmptyObject(data) && !$.isEmptyObject(data.newValue)) { 28 | data = data.newValue; 29 | updateViewData(data); 30 | } 31 | }); 32 | 33 | loadChatppEmoData(); 34 | }); 35 | 36 | function loadStatus(name, value) { 37 | if (value !== undefined && (value === false || value === "false")) { 38 | $(`#${name}-status`).removeClass().addClass("text-danger").html("DISABLED"); 39 | } else { 40 | $(`#${name}-status`).removeClass().addClass("text-primary").html("ENABLED"); 41 | } 42 | } 43 | 44 | function loadChatppEmoData() { 45 | let storage = new Storage; 46 | storage.get(Const.CHROME_SYNC_KEY, (data) => { 47 | if ($.isEmptyObject(data)) { 48 | common.openNewExtensionPageUrl(common.app_detail.options_page); 49 | } else { 50 | updateViewData(data); 51 | } 52 | }); 53 | } 54 | 55 | function updateViewData(data) { 56 | let features = ["emoticon", "mention", "shortcut"]; 57 | for (let i in features) { 58 | loadStatus(features[i], data[`${features[i]}_status`]); 59 | } 60 | } 61 | 62 | function setVersionType() { 63 | let chrome_storage_local = new ChromeStorageLocal(); 64 | chrome_storage_local.get((data) => { 65 | if ($.isEmptyObject(data)) { 66 | local_stored_data = {}; 67 | } else { 68 | local_stored_data = data; 69 | } 70 | if (local_stored_data[Const.CHROME_LOCAL_KEY] === undefined) { 71 | local_stored_data[Const.CHROME_LOCAL_KEY] = {}; 72 | } 73 | local_stored_data[Const.CHROME_LOCAL_KEY]["version"] = common.getAppVersion(); 74 | local_stored_data[Const.CHROME_LOCAL_KEY]["version_name"] = common.getAppVersionName(); 75 | chrome.browserAction.getBadgeText({}, (result) => { 76 | if (result === "new") { 77 | chrome.browserAction.setBadgeText({text: ""}); 78 | common.openNewUrl("html/change_logs.html"); 79 | } 80 | }); 81 | chrome_storage_local.setData(local_stored_data); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /src/js/externals/room.js: -------------------------------------------------------------------------------- 1 | let Const = require("../helpers/Const.js"); 2 | let common = require("../helpers/Common.js"); 3 | let Storage = require("../helpers/Storage.js"); 4 | 5 | let storage = new Storage(); 6 | 7 | let rooms = []; 8 | 9 | $(() => { 10 | if (!common.isPage("room")) { 11 | return; 12 | } 13 | common.setPageTitle(); 14 | 15 | storage.get(Const.CHROME_SYNC_ROOM_KEY, (data) => { 16 | if (!$.isEmptyObject(data)) { 17 | rooms = data; 18 | loadData(); 19 | } 20 | }); 21 | 22 | $("#save-btn").click(() => { 23 | $("input").each((index, element) => { 24 | let number = $(element).data("btn"); 25 | let value = $(element).val(); 26 | rooms[number] = common.parseRoomId(value); 27 | }); 28 | storage.set(Const.CHROME_SYNC_ROOM_KEY, rooms, common.reload); 29 | }); 30 | }); 31 | 32 | function loadData() { 33 | for (let i in rooms) { 34 | if (rooms[i]) { 35 | $(`[data-btn='${i}']`).val(rooms[i]); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/js/externals/setting.js: -------------------------------------------------------------------------------- 1 | let Const = require("../helpers/Const.js"); 2 | let common = require("../helpers/Common.js"); 3 | let Storage = require("../helpers/Storage.js"); 4 | 5 | let storage = new Storage(); 6 | let stored_data = {}; 7 | 8 | $(() => { 9 | if (!common.isPage("setting")) { 10 | return; 11 | } 12 | common.setPageTitle(); 13 | 14 | storage.get(Const.CHROME_SYNC_KEY, (data) => { 15 | stored_data[Const.CHROME_SYNC_KEY] = data; 16 | if ($.isEmptyObject(data)) { 17 | common.openNewExtensionPageUrl(common.app_detail.options_page) 18 | } else { 19 | updateViewData(data); 20 | $("[id$=-status-btn]").click((e) => { 21 | let status = true; 22 | let id = $(e.currentTarget).attr("id"); 23 | let id_parts = id.split("-"); 24 | let feature_name = id_parts[0]; 25 | if ($(e.currentTarget).html() === "Disable") { 26 | status = false; 27 | } 28 | stored_data[Const.CHROME_SYNC_KEY][`${feature_name}_status`] = status; 29 | storage.setData(stored_data, () => { 30 | loadStatus(feature_name, status); 31 | }); 32 | }) 33 | } 34 | }); 35 | }); 36 | 37 | function loadStatus(name, value) { 38 | if (value === false || (value === undefined && name === "legacy_theme")) { 39 | $(`#${name}-status`).removeClass().addClass("text-danger").html("DISABLED"); 40 | $(`#${name}-status-btn`).removeClass().addClass("btn btn-success btn-xs").html("Enable"); 41 | } else { 42 | $(`#${name}-status`).removeClass().addClass("text-success").html("ENABLED"); 43 | $(`#${name}-status-btn`).removeClass().addClass("btn btn-danger btn-xs").html("Disable"); 44 | } 45 | } 46 | 47 | function updateViewData(data) { 48 | let features = ["emoticon", "mention", "shortcut", "legacy_theme"]; 49 | for (let i in features) { 50 | loadStatus(features[i], data[`${features[i]}_status`]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/js/externals/shortcut.js: -------------------------------------------------------------------------------- 1 | let common = require("../helpers/Common.js"); 2 | 3 | $(() => { 4 | if (!common.isPage("shortcut")) { 5 | return; 6 | } 7 | common.setPageTitle(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/js/helpers/ChatworkFacade.js: -------------------------------------------------------------------------------- 1 | let common = require("../helpers/Common.js"); 2 | 3 | class ChatworkFacade { 4 | constructor() { 5 | 6 | } 7 | 8 | myId() { 9 | return AC.myid; 10 | } 11 | 12 | getUserName(user_id) { 13 | return AC.getDefaultNickName(user_id); 14 | } 15 | 16 | currentRoom() { 17 | return RM.id; 18 | } 19 | 20 | getRoomAdmins() { 21 | let members = this.getRoomMembers(); 22 | let admins = []; 23 | for (let id in members) { 24 | if (members[id] === "admin") { 25 | admins.push(id); 26 | } 27 | } 28 | return admins; 29 | } 30 | 31 | isAdmin(user) { 32 | let members = this.getRoomMembers(); 33 | user = user || this.myId(); 34 | return members[user] === "admin"; 35 | } 36 | 37 | getRoomMembers() { 38 | return RM.member_dat; 39 | } 40 | 41 | getRoomMembersCount() { 42 | return RM.sorted_member_list.length; 43 | } 44 | 45 | getRoomMembersArray() { 46 | let members = this.getRoomMembers(); 47 | return Object.keys(members); 48 | } 49 | 50 | getRandomMemberInRoom() { 51 | let members = this.getRoomMembersArray(); 52 | return common.random(members); 53 | } 54 | 55 | searchRoomsByPerson(user_id) { 56 | let rooms = RL.rooms; 57 | let same_rooms = []; 58 | for (let room_id in rooms) { 59 | let room = rooms[room_id]; 60 | if (room._name && room.member_dat && room.member_dat.hasOwnProperty(user_id)) { 61 | same_rooms.push(room); 62 | } 63 | } 64 | return same_rooms; 65 | } 66 | 67 | removeMemberFromRoom(user_id, room_id) { 68 | let room = RL.rooms[room_id]; 69 | if (room.type === "group" && room.member_dat.hasOwnProperty(user_id) && room.member_dat[this.myId()] === "admin") { 70 | if (!window.confirm(`Are you sure to delete this user from ${room.getName()} ?`)) { 71 | return false; 72 | } 73 | delete room.member_dat[user_id]; 74 | let params = { 75 | body_params: { 76 | cmd: "update_room", 77 | room_id, 78 | role: room.member_dat 79 | }, 80 | query_params: {} 81 | }; 82 | CW.post("gateway.php", params); 83 | return true; 84 | } 85 | 86 | return false; 87 | } 88 | 89 | addMembersFromChatTextToCurrentRoom() { 90 | let room_id = this.currentRoom(); 91 | let room = RL.rooms[room_id]; 92 | let member_dat = $.extend({}, room.member_dat); 93 | if (room.type === "group" && member_dat[this.myId()] === "admin") { 94 | if (!window.confirm("Are you sure to add all Users mentioned in Chatbox to this room?")) { 95 | return false; 96 | } 97 | let text = this.getChatText(); 98 | let users = common.parseUsersId(text); 99 | let update = false; 100 | for (u of users) { 101 | if (!member_dat.hasOwnProperty(u)) { 102 | member_dat[u] = "member"; 103 | update = true; 104 | } 105 | } 106 | if (update) { 107 | let params = { 108 | body_params: { 109 | cmd: "update_room", 110 | room_id, 111 | role: member_dat 112 | }, 113 | query_params: {} 114 | }; 115 | CW.post("gateway.php", params, (response) => { 116 | if (response.status && !response.status.success) { 117 | window.alert(response.status.message); 118 | } 119 | }); 120 | } else { 121 | window.alert("There are no new mentioned Members to add into this Room"); 122 | } 123 | 124 | return true; 125 | } 126 | } 127 | 128 | getChatText() { 129 | return $("#_chatText").val(); 130 | } 131 | 132 | clearChatText() { 133 | CS.view.setChatText(""); 134 | } 135 | 136 | checkNotifyAllCondition() { 137 | return common.checkDevVersionInternal() || (this.getRoomMembersCount() > 100 && this.isAdmin()); 138 | } 139 | 140 | getMessageObjectById(mid) { 141 | return RM.timeline.chat_id2chat_dat[mid]; 142 | } 143 | 144 | getMessagePanelByMessageId(mid) { 145 | let message = this.getMessageObjectById(mid); 146 | return TimeLineView.prototype.getMessagePanel(message); 147 | } 148 | 149 | getMyRoomId() { 150 | return AC.getRoomId(AC.myid); 151 | } 152 | 153 | getTempRoomId() { 154 | // Get the first room that is not the same as current room 155 | let current_room = RM.id; 156 | let my_room = this.getMyRoomId(); 157 | if (current_room != my_room) { 158 | return my_room; 159 | } 160 | let sorted_rooms_list = RL.getSortedRoomList(); 161 | for (i of sorted_rooms_list) { 162 | if (i !== current_room) { 163 | return i; 164 | } 165 | } 166 | } 167 | } 168 | 169 | let chatwork = new ChatworkFacade(); 170 | module.exports = chatwork; 171 | -------------------------------------------------------------------------------- /src/js/helpers/ChromeStorageLocal.js: -------------------------------------------------------------------------------- 1 | let Storage = require("./Storage.js"); 2 | let Const = require("./Const.js"); 3 | 4 | class ChromeStorageLocal { 5 | constructor() { 6 | this.storage = new Storage(true); 7 | this.key = Const.CHROME_LOCAL_KEY; 8 | } 9 | 10 | get(callback) { 11 | this.storage.get(this.key, callback); 12 | } 13 | 14 | set(data, callback) { 15 | this.set(this.key, data, callback); 16 | } 17 | 18 | setData(data, callback) { 19 | this.storage.setData(data, callback); 20 | } 21 | } 22 | 23 | module.exports = ChromeStorageLocal; 24 | -------------------------------------------------------------------------------- /src/js/helpers/Common.js: -------------------------------------------------------------------------------- 1 | let Const = require("./Const.js"); 2 | 3 | class Common { 4 | constructor() { 5 | this.version = Const.VERSION_CHROME; 6 | this.app_detail = this.getAppDetail(); 7 | this.official_emoticons_data = { 8 | Default: { 9 | name: "Default", 10 | link: "https://dl.dropboxusercontent.com/s/lmxis68cfh4v1ho/default.json?dl=1", 11 | description: "The default Emoticons data of Chat++" 12 | }, 13 | Vietnamese: { 14 | name: "Vietnamese", 15 | link: "https://dl.dropboxusercontent.com/s/2b085bilbno4ri1/vietnamese.json?dl=1", 16 | description: "Yet another data for people who want to use Vietnamese Emoticons" 17 | }, 18 | Japanese: { 19 | name: "Japanese", 20 | link: "https://dl.dropboxusercontent.com/s/fdq05pwwtsccrn6/japanese.json?dl=1", 21 | description: "Yet another data for people who want to use Japanese Emoticons" 22 | }, 23 | Skype: { 24 | name: "Skype", 25 | link: "https://dl.dropboxusercontent.com/s/8ew2mdh0v2vcad8/skype.json?dl=1", 26 | description: "Skype Original Emoticons" 27 | } 28 | }; 29 | } 30 | 31 | isChromeVersion() { 32 | return this.version === Const.VERSION_CHROME; 33 | } 34 | 35 | isFirefoxVersion() { 36 | return this.version === Const.VERSION_FIREFOX; 37 | } 38 | 39 | isDevVersion() { 40 | let app_name = this.app_detail.name; 41 | return app_name.indexOf(Const.VERSION_NAME_DEV, app_name.length - (Const.VERSION_NAME_DEV).length) !== -1; 42 | } 43 | 44 | checkDevVersionInternal() { 45 | return localStorage["chatpp_version_name"] === Const.VERSION_NAME_DEV; 46 | } 47 | 48 | getStorage(local) { 49 | if (!local && this.isChromeVersion()) { 50 | return chrome.storage.sync; 51 | } 52 | 53 | return chrome.storage.local; 54 | } 55 | 56 | sync(key, data, callback) { 57 | let sync = {}; 58 | sync[key] = data; 59 | let storage = this.getStorage(); 60 | storage.set(sync, () => { 61 | if (callback) { 62 | callback(); 63 | } 64 | }); 65 | } 66 | 67 | getEmoticonDataUrl(data_name, default_url) { 68 | if (data_name && this.official_emoticons_data[data_name]) { 69 | default_url = this.official_emoticons_data[data_name]["link"]; 70 | } 71 | 72 | return default_url ? default_url.replace("http://i.imgur.com/", "https://i.imgur.com/") : null; 73 | } 74 | 75 | getObjectLength(object) { 76 | return Object.keys(object).length; 77 | } 78 | 79 | htmlEncode(value){ 80 | return $("
    ").text(value).html(); 81 | } 82 | 83 | getEmoUrl(img) { 84 | let url = `${Const.DEFAULT_IMG_HOST}img/emoticons/${img}`; 85 | if (img.indexOf("https://") == 0 || img.indexOf("http://") == 0) { 86 | url = img; 87 | } 88 | return this.htmlEncode(url); 89 | } 90 | 91 | parseRoomId(text) { 92 | let room = text.match(/\d+/g); 93 | if (!room || room.length == 0) { 94 | return null; 95 | } 96 | room = room[0]; 97 | let regex = /^[0-9]{6,10}$/g; 98 | if (regex.test(room)) { 99 | return room; 100 | } 101 | return null; 102 | } 103 | 104 | parseUsersId(text) { 105 | let regex = /\[[a-zA-Z]+:([0-9]+)\]/g; 106 | let match; 107 | let users = []; 108 | while ((match = regex.exec(text)) != null) { 109 | users.push(match[1]); 110 | } 111 | 112 | return users; 113 | } 114 | 115 | reload() { 116 | location.reload(); 117 | } 118 | 119 | getAppDetail() { 120 | return chrome.app.getDetails(); 121 | } 122 | 123 | getAppVersion() { 124 | return this.app_detail.version; 125 | } 126 | 127 | getAppVersionName() { 128 | if (this.isDevVersion()) { 129 | return Const.VERSION_NAME_DEV; 130 | } 131 | 132 | return Const.VERSION_NAME_RELEASE 133 | } 134 | 135 | getAppFullName() { 136 | let version_name = this.getAppVersionName(); 137 | 138 | return `${this.app_detail.short_name} ${this.app_detail.version} ${version_name}`; 139 | } 140 | 141 | openNewUrl(url) { 142 | chrome.tabs.create({url}); 143 | } 144 | 145 | 146 | getExtensionPageUrl(url) { 147 | return chrome.extension.getURL(url); 148 | } 149 | 150 | openNewExtensionPageUrl(url) { 151 | this.openNewUrl(this.getExtensionPageUrl(url)); 152 | } 153 | 154 | validateUrl(url) { 155 | let regexp = /(https):\/\/(\w+:?\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; 156 | return regexp.test(url); 157 | } 158 | 159 | validateDropboxUrl(url) { 160 | if (!this.validateUrl(url)) { 161 | return false; 162 | } 163 | let supported_urls = ["https://dl.dropboxusercontent.com/", "https://www.dropbox.com/"]; 164 | for (let supported_url of supported_urls) { 165 | if (url.startsWith(supported_url)) { 166 | return true; 167 | } 168 | } 169 | 170 | return false; 171 | } 172 | 173 | isPage(page_name) { 174 | return $("#page-name").data("page-name") === page_name; 175 | } 176 | 177 | setPageTitle() { 178 | $("#chatpp_name").html(this.getAppFullName()); 179 | } 180 | 181 | setStatus(key, value) { 182 | if (key.indexOf("_status") === -1) { 183 | key = `${key}_status`; 184 | } 185 | localStorage[key] = !!value; 186 | } 187 | 188 | getStatus(key) { 189 | if (key.indexOf("_status") === -1) { 190 | key = `${key}_status`; 191 | } 192 | return localStorage[key] === "true" || localStorage[key] === true; 193 | } 194 | 195 | regexEscape(string) { 196 | return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); 197 | } 198 | 199 | generateEmoticonRegex(text, regex) { 200 | regex = regex || this.htmlEncode(this.regexEscape(text)); 201 | return new RegExp(regex, "g"); 202 | } 203 | 204 | random(items) { 205 | if (!items.length) { 206 | return null; 207 | } 208 | 209 | return items[Math.floor(Math.random() * items.length)]; 210 | } 211 | 212 | randomString(n) { 213 | let text = ""; 214 | let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 215 | 216 | for( let i = 0; i < n; i++ ) 217 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 218 | 219 | return text; 220 | } 221 | } 222 | 223 | let common = new Common(); 224 | module.exports = common; 225 | -------------------------------------------------------------------------------- /src/js/helpers/Const.js: -------------------------------------------------------------------------------- 1 | let Const = { 2 | LOCAL_STORAGE_DATA_KEY: "YACEP_EMO_DATA", 3 | LOCAL_STORAGE_INFO_KEY: "YACEP_EMO_INFO", 4 | LOCAL_STORAGE_GROUP_MENTION: "CHATPP_GROUP_MENTION", 5 | LOCAL_STORAGE_ROOM_SHORTCUT: "CHATPP_ROOM_SHORTCUT", 6 | LOCAL_STORAGE_DISABLE_NOTIFY_ROOM: "CHATPP_DISABLE_NOTIFY_ROOM", 7 | CHROME_LOCAL_KEY: "CHATPP_CHROME_LOCAL_DATA", 8 | CHROME_SYNC_KEY: "CHATPP_CHROME_SYNC_DATA", 9 | CHROME_SYNC_GROUP_KEY: "CHATPP_CHROME_SYNC_GROUP", 10 | CHROME_SYNC_ROOM_KEY: "CHATPP_CHROME_SYNC_ROOM", 11 | CHROME_SYNC_DISABLE_NOTIFY_ROOM_KEY: "CHATPP_CHROME_SYNC_DISABLE_NOTIFY_ROOM", 12 | DEFAULT_DATA_URL: "https://dl.dropboxusercontent.com/s/lmxis68cfh4v1ho/default.json?dl=1", 13 | ADVERTISEMENT_URL: "https://dl.dropboxusercontent.com/s/jsmceot0pqi8lpk/chatppad.json?dl=1", 14 | VERSION_CHROME: "VERSION_CHROME", 15 | VERSION_FIREFOX: "VERSION_FIREFOX", 16 | VERSION_NAME_DEV: "dev", 17 | VERSION_NAME_RELEASE: "final", 18 | DEFAULT_IMG_HOST: "https://chatpp.thangtd.com/", 19 | DELAY_TIME: 6000, 20 | FORCE_TURN_OFF_THUMBNAIL: 1, 21 | ADVERTISEMENT_LOAD_TIMEOUT: 1000 * 60 * 30, 22 | TO_ALL_MARK: "TO ALL >>>" 23 | }; 24 | 25 | module.exports = Const; 26 | -------------------------------------------------------------------------------- /src/js/helpers/EmoStorage.js: -------------------------------------------------------------------------------- 1 | let common = require("./Common.js"); 2 | let Storage = require("./Storage.js"); 3 | let Const = require("./Const.js"); 4 | 5 | class EmoStorage extends Storage { 6 | constructor() { 7 | super(); 8 | this.data = {}; 9 | this.data_count = 0; 10 | } 11 | 12 | getDataCount() { 13 | return common.getObjectLength(this.data); 14 | } 15 | 16 | setFeatureStatus(emo_info) { 17 | let features = ["mention", "shortcut", "thumbnail", "emoticon", "legacy_theme"]; 18 | for (let i in features) { 19 | let feature_name = `${features[i]}_status`; 20 | this.data[feature_name] = emo_info[feature_name] === undefined ? true : emo_info[feature_name]; 21 | } 22 | this.data.force_update_version = emo_info.force_update_version; 23 | } 24 | 25 | pushData(inputted_data, inputted_priority) { 26 | let priority = (inputted_priority !== undefined) ? inputted_priority : inputted_data.priority; 27 | if (this.data[inputted_data.data_name] === undefined) { 28 | this.data_count++; 29 | } 30 | this.data[inputted_data.data_name] = { 31 | priority, 32 | data_name: inputted_data.data_name, 33 | data_url: inputted_data.data_url, 34 | data_changelog: inputted_data.data_changelog, 35 | data_version: inputted_data.data_version, 36 | date_sync: (new Date()).toLocaleString() 37 | }; 38 | } 39 | 40 | removeData(data_name) { 41 | if (this.data[data_name] !== undefined) { 42 | delete this.data[data_name]; 43 | } 44 | } 45 | 46 | syncData(callback) { 47 | this.set(Const.CHROME_SYNC_KEY, this.data, callback); 48 | } 49 | } 50 | 51 | module.exports = EmoStorage; 52 | -------------------------------------------------------------------------------- /src/js/helpers/Storage.js: -------------------------------------------------------------------------------- 1 | let common = require("./Common.js"); 2 | 3 | class Storage { 4 | constructor(local) { 5 | this.storage = common.getStorage(local); 6 | } 7 | 8 | get(key, callback) { 9 | this.storage.get(key, (info) => { 10 | info = info ? info[key] : undefined; 11 | callback(info); 12 | }); 13 | } 14 | 15 | set(key, data, callback) { 16 | let sync = {}; 17 | sync[key] = data; 18 | this.storage.set(sync, () => { 19 | if (callback) { 20 | callback(); 21 | } 22 | }); 23 | } 24 | 25 | setData(data, callback) { 26 | this.storage.set(data, () => { 27 | if (callback) { 28 | callback(); 29 | } 30 | }); 31 | } 32 | } 33 | 34 | module.exports = Storage; 35 | -------------------------------------------------------------------------------- /src/js/internals/Advertisement.js: -------------------------------------------------------------------------------- 1 | let ADVERTISEMENT_CHANGE_TIME = 1000 * 30; 2 | 3 | class Advertisement { 4 | setUp() { 5 | if ($("#_chatppSponsored").length > 0) { 6 | return; 7 | } 8 | $(".chatInput ul").first().append( 9 | $("
  • ", { id: "_chatppSponsored", class: "_showDescription", css: { 10 | "display": "inline-block" 11 | }, attr:{ 12 | "role": "button", 13 | "aria-label": "Chat Plus Plus Information" 14 | } }).append( 15 | $("", { id: "chatppPreLoad", class: "icoSizeSmall" }) 16 | ).append(this.getAdvertisementText()) 17 | ); 18 | setInterval(() => { 19 | this.changeRandomAdvertisement(); 20 | }, ADVERTISEMENT_CHANGE_TIME); 21 | } 22 | 23 | changeRandomAdvertisement() { 24 | let text = this.getAdvertisementText(); 25 | $("#chatppAdvertisement").html(text); 26 | } 27 | 28 | getAdvertisementText() { 29 | if (localStorage["chatpp_advertisement"] !== undefined && localStorage["chatpp_advertisement"]) { 30 | let ads = JSON.parse(localStorage["chatpp_advertisement"]); 31 | if (ads.length > 0) { 32 | return ads[Math.floor(Math.random() * ads.length)]; 33 | } 34 | } 35 | return "Advertisement Here!"; 36 | } 37 | } 38 | 39 | let advertisement = new Advertisement(); 40 | module.exports = advertisement; 41 | -------------------------------------------------------------------------------- /src/js/internals/Chatpp.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "..\\..\\.." 5 | }, 6 | { 7 | "path": "..\\..\\..\\..\\..\\Downloads\\Compressed\\chatpp_emo_fix" 8 | } 9 | ], 10 | "settings": {} 11 | } -------------------------------------------------------------------------------- /src/js/internals/NotificationDisabler.js: -------------------------------------------------------------------------------- 1 | class NotificationDisabler { 2 | static setUp() { 3 | let disabledNotifyRooms = null; 4 | 5 | if (localStorage["CHATPP_DISABLE_NOTIFY_ROOM"] !== "" 6 | && localStorage["CHATPP_DISABLE_NOTIFY_ROOM"] !== "undefined" 7 | && localStorage["CHATPP_DISABLE_NOTIFY_ROOM"]) { 8 | disabledNotifyRooms = JSON.parse(localStorage["CHATPP_DISABLE_NOTIFY_ROOM"]); 9 | } 10 | 11 | if (disabledNotifyRooms) { 12 | CW.popupOld = CW.popup; 13 | /* eslint-disable no-unused-vars */ 14 | let b = null, 15 | d = null, 16 | e = window.navigator.userAgent.toLowerCase().indexOf("chrome") != -1; 17 | /* eslint-enable */ 18 | CW.popup = function wrapper(icon, title, body, room_id) { 19 | if (disabledNotifyRooms.indexOf(room_id.toString()) == -1) { 20 | CW.popupOld(icon, title, body, room_id); 21 | } 22 | } 23 | } 24 | } 25 | } 26 | 27 | module.exports = NotificationDisabler; 28 | -------------------------------------------------------------------------------- /src/js/internals/NotifyAll.js: -------------------------------------------------------------------------------- 1 | let Const = require("../helpers/Const.js"); 2 | 3 | class NotifyAll { 4 | setUp() { 5 | this.registerRegex(); 6 | } 7 | 8 | registerRegex() { 9 | CW.reg_cmp.push({ 10 | key: /TO ALL >>>/g, 11 | rep: "TO ALL", 12 | reptxt: "TO ALL", 13 | special: true 14 | }); 15 | 16 | window.FindReact = (dom) => { 17 | let key = Object.keys(dom).find((key) => key.startsWith("__reactInternalInstance$")); 18 | let internalInstance = dom[key]; 19 | if (internalInstance == null) return null; 20 | 21 | if (internalInstance.return) { // react 16+ 22 | return internalInstance._debugOwner 23 | ? internalInstance._debugOwner.stateNode 24 | : internalInstance.return.stateNode; 25 | } else { // react <16 26 | return internalInstance._currentElement._owner._instance; 27 | } 28 | } 29 | 30 | let dom = document.getElementsByClassName("_message messageHasBorder"); 31 | if (!dom.length) { 32 | return; 33 | } 34 | let node = window.FindReact(dom[dom.length-1]); 35 | if (!node) { 36 | return; 37 | } 38 | node.__proto__.renderOld = node.__proto__.render; 39 | node.__proto__.render = function() { 40 | if (this.props.message.body.indexOf(Const.TO_ALL_MARK) === 0) { 41 | this.props.message.mentioned = true; 42 | } 43 | 44 | return this.renderOld(); 45 | }; 46 | } 47 | } 48 | 49 | let notify_all = new NotifyAll(); 50 | module.exports = notify_all; 51 | -------------------------------------------------------------------------------- /src/js/internals/RoomInformation.js: -------------------------------------------------------------------------------- 1 | let common = require("../helpers/Common.js"); 2 | 3 | class RoomInformation { 4 | setUp() { 5 | if ($("#_roomInfo").length > 0) { 6 | return; 7 | } 8 | $(".chatInput ul").first().append( 9 | $("
  • ", { 10 | id: "_roomInfo", 11 | class: "_showDescription chatInput__element", 12 | css: { 13 | "display": "inline-block" 14 | }, 15 | attr: { 16 | "role": "button", 17 | "aria-label": "View Room Information" 18 | } 19 | }).append( 20 | $("", { class: "icoFontAdminInfoMenu icoSizeLarge" }) 21 | ) 22 | ); 23 | $("body").append( 24 | $("
    ", { 25 | id: "_roomInfoList", 26 | class: "roomInfo emoticonTooltip toolTip tooltip--white mainContetTooltip", 27 | attr: { 28 | "role": "tooltip" 29 | } 30 | }).append( 31 | $("
    ", { 32 | class: "_cwTTTriangle toolTipTriangle toolTipTriangleWhiteBottom" 33 | }), 34 | $("", { 35 | id: "_roomInfoText" 36 | }).append( 37 | $("
    ", { 38 | id: "_roomInfoTextTotalMembers", 39 | class: "tooltipFooter" 40 | 41 | }), 42 | $("
    ", { 43 | id: "_roomInfoTextTotalMessages", 44 | class: "tooltipFooter" 45 | 46 | }), 47 | $("
    ", { 48 | id: "_roomInfoTextTotalFiles", 49 | class: "tooltipFooter" 50 | 51 | }), 52 | $("
    ", { 53 | id: "_roomInfoTextTotalTasks", 54 | class: "tooltipFooter" 55 | 56 | }), 57 | $("
    ", { 58 | id: "_roomInfoTextMyTasks", 59 | class: "tooltipFooter" 60 | 61 | }), 62 | ) 63 | ) 64 | ); 65 | $("#_roomInfo").click((e) => { 66 | this.prepareRoomInfo(); 67 | let room_name = `${RM.getIcon()} ${common.htmlEncode(RM.getName())}`; 68 | let tip = $("#_roomInfoList").cwListTip({ 69 | selectOptionArea: `${room_name} Information`, 70 | fixHeight: !1, 71 | search: !1 72 | }); 73 | tip.open($(e.currentTarget)); 74 | }); 75 | } 76 | 77 | prepareRoomInfo() { 78 | let total_members = `Total Members: ${RM.getSortedMemberList().length}`; 79 | $("#_roomInfoTextTotalMembers").html(total_members); 80 | let total_messages = `Total Messages: ${RM.chat_num}`; 81 | $("#_roomInfoTextTotalMessages").html(total_messages); 82 | let total_tasks = `Total Tasks: ${RM.task_num}`; 83 | $("#_roomInfoTextTotalTasks").html(total_tasks); 84 | let my_tasks = `My Tasks: ${RM.mytask_num}`; 85 | $("#_roomInfoTextMyTasks").html(my_tasks); 86 | let total_files = `Total Files: ${RM.file_num}`; 87 | $("#_roomInfoTextTotalFiles").html(total_files); 88 | } 89 | 90 | } 91 | 92 | let room_information = new RoomInformation(); 93 | module.exports = room_information; 94 | -------------------------------------------------------------------------------- /src/js/internals/Shortcut.js: -------------------------------------------------------------------------------- 1 | let Const = require("../helpers/Const.js"); 2 | let common = require("../helpers/Common.js"); 3 | let emoticon = require("./Emoticon.js"); 4 | 5 | let DOM_VK_SPACE = 32, 6 | DOM_VK_0 = 48, 7 | DOM_VK_A = 65, 8 | DOM_VK_B = 66, 9 | DOM_VK_E = 69, 10 | DOM_VK_J = 74, 11 | DOM_VK_K = 75, 12 | DOM_VK_L = 76, 13 | DOM_VK_M = 77, 14 | DOM_VK_N = 78, 15 | DOM_VK_Q = 81, 16 | DOM_VK_R = 82, 17 | DOM_VK_S = 83, 18 | DOM_VK_T = 84, 19 | DOM_VK_V = 86, 20 | DOM_VK_X = 88, 21 | DOM_VK_Z = 90; 22 | 23 | class Shortcut { 24 | constructor() { 25 | this.shortcuts_default = { 26 | reply: DOM_VK_R, 27 | quote: DOM_VK_Q, 28 | link: DOM_VK_L, 29 | edit: DOM_VK_E, 30 | task: DOM_VK_T, 31 | my_chat: DOM_VK_A, 32 | scroll: DOM_VK_S, 33 | previous_mention: DOM_VK_K, 34 | next_mention: DOM_VK_J, 35 | next_mention_room: DOM_VK_M, 36 | next_new_message_room: DOM_VK_N, 37 | down_room: DOM_VK_V, 38 | up_room: DOM_VK_B, 39 | first_room: DOM_VK_Z, 40 | first_nonstick_room: DOM_VK_X, 41 | focus_chatbox: DOM_VK_SPACE, 42 | edit_image_upload: DOM_VK_E 43 | }; 44 | this.room_shortcuts = {}; 45 | if (localStorage[Const.LOCAL_STORAGE_ROOM_SHORTCUT]) { 46 | this.room_shortcuts = JSON.parse(localStorage[Const.LOCAL_STORAGE_ROOM_SHORTCUT]); 47 | } 48 | this.status = common.getStatus("shortcut"); 49 | this.actions = { 50 | quote: "quote", 51 | link: "link", 52 | edit: "edit", 53 | task: "task", 54 | }; 55 | } 56 | 57 | setUp() { 58 | if (this.status) { 59 | this.registerShortcut(); 60 | } 61 | 62 | if (window.language_module) { 63 | for (i in this.actions) { 64 | this.actions[i] = window.language_module.a.getLang(`%%%chat_action_${i}%%%`); 65 | } 66 | } 67 | } 68 | 69 | registerShortcut() { 70 | let shortcuts_default = this.shortcuts_default; 71 | CW.view.registerKeyboardShortcut(shortcuts_default.reply, !1, !1, !1, !1, () => { 72 | let message_id = this.getHoverMessageId(); 73 | this.replyMessage(message_id); 74 | }); 75 | 76 | CW.view.registerKeyboardShortcut(shortcuts_default.quote, !1, !1, !1, !1, () => { 77 | let message_id = this.getHoverMessageId(); 78 | this.quoteMessage(message_id, false); 79 | // this.triggerDefaultAction("quote"); 80 | }); 81 | 82 | if (emoticon.status) { 83 | $("#_chatContent").on("click", "li.actionNav__item", (e) => { 84 | let target = e.currentTarget; 85 | e.preventDefault(); 86 | let label = $(target).find(".actionNav__itemLabel"); 87 | if (label && label.text() === this.actions.quote) { 88 | let message_id = this.getHoverMessageId(); 89 | this.quoteMessage(message_id, true); 90 | } 91 | }); 92 | } 93 | 94 | CW.view.registerKeyboardShortcut(shortcuts_default.link, !1, !1, !1, !1, () => { 95 | this.triggerDefaultAction("link"); 96 | }); 97 | 98 | CW.view.registerKeyboardShortcut(shortcuts_default.edit, !1, !1, !1, !1, () => { 99 | this.triggerDefaultAction("edit"); 100 | }); 101 | 102 | CW.view.registerKeyboardShortcut(shortcuts_default.task, !1, !1, !1, !1, () => { 103 | this.triggerDefaultAction("task"); 104 | }); 105 | 106 | CW.view.registerKeyboardShortcut(shortcuts_default.my_chat, !1, !1, !1, !1, () => { 107 | RL.selectRoom(AC.getRoomId(AC.myid)); 108 | }); 109 | 110 | CW.view.registerKeyboardShortcut(shortcuts_default.scroll, !1, !1, !1, !1, () => { 111 | this.goToBottom(); 112 | }); 113 | 114 | CW.view.registerKeyboardShortcut(shortcuts_default.previous_mention, !1, !1, !1, !1, () => { 115 | this.goToPreviousMention(); 116 | }); 117 | 118 | CW.view.registerKeyboardShortcut(shortcuts_default.next_mention, !1, !1, !1, !1, () => { 119 | this.goToNexMention(); 120 | }); 121 | 122 | CW.view.registerKeyboardShortcut(shortcuts_default.next_mention_room, !1, !1, !1, !1, () => { 123 | this.nextUnreadRoom(true); 124 | }); 125 | 126 | CW.view.registerKeyboardShortcut(shortcuts_default.next_new_message_room, !1, !1, !1, !1, () => { 127 | this.nextUnreadRoom(); 128 | }); 129 | 130 | CW.view.registerKeyboardShortcut(shortcuts_default.up_room, !1, !1, !1, !1, () => { 131 | this.nextRoom(true) 132 | }); 133 | 134 | CW.view.registerKeyboardShortcut(shortcuts_default.down_room, !1, !1, !1, !1, () => { 135 | this.nextRoom(); 136 | }); 137 | 138 | CW.view.registerKeyboardShortcut(shortcuts_default.first_room, !1, !1, !1, !1, () => { 139 | this.firstRoom(); 140 | }); 141 | 142 | CW.view.registerKeyboardShortcut(shortcuts_default.first_nonstick_room, !1, !1, !1, !1, () => { 143 | this.firstRoom(true); 144 | }); 145 | 146 | CW.view.registerKeyboardShortcut(shortcuts_default.focus_chatbox, !1, !1, !1, !1, () => { 147 | $("#_chatText").focus(); 148 | }); 149 | 150 | CW.view.registerKeyboardShortcut(shortcuts_default.edit_image_upload, !1, !0, !1, !1, () => { 151 | this.triggerDefaultAction("edit"); 152 | let chat_text = $("#_chatText"); 153 | let text = chat_text.val(); 154 | let img = text.match(/(\[preview id=[0-9]* ht=[0-9]*\])/); 155 | if (img && img[0]) { 156 | text = text.replace(/\[info\].*\[\/info\]/, img[0]); 157 | chat_text.val(text); 158 | } 159 | }); 160 | 161 | for (let i in this.room_shortcuts) { 162 | if (this.room_shortcuts[i] && this.room_shortcuts.hasOwnProperty(i)) { 163 | let room = this.room_shortcuts[i]; 164 | CW.view.registerKeyboardShortcut(DOM_VK_0 + parseInt(i), !1, !1, !1, !1, () => { 165 | RL.selectRoom(room); 166 | }); 167 | } 168 | } 169 | } 170 | 171 | isScrollable() { 172 | return this.get(0).scrollHeight > this.height(); 173 | } 174 | 175 | triggerDefaultAction(action) { 176 | $("._message:hover .actionNav__item").each((index, element) => { 177 | let label = $(element).find(".actionNav__itemLabel"); 178 | if (label) { 179 | if (label.text() === this.actions[action]) { 180 | $(element).trigger("click"); 181 | } 182 | } 183 | }); 184 | } 185 | 186 | triggerMoreAction() { 187 | let more_action = $("._message:hover").find("._cwABMoreTip"); 188 | if (this.isDomExists(more_action)) { 189 | more_action.trigger("click"); 190 | let delete_button = $("._cwABMoreListBox").find("[data-cwui-ab-type=\"action\"]"); 191 | if (this.isDomExists(delete_button)) { 192 | delete_button.trigger("click"); 193 | } 194 | } 195 | } 196 | 197 | selectRoom(room) { 198 | RL.selectRoom(room); 199 | } 200 | 201 | isDomExists(dom) { 202 | return dom.length > 0; 203 | } 204 | 205 | getHoverMessageId() { 206 | return $("._message:hover").data("mid"); 207 | } 208 | 209 | getPivotMessage() { 210 | return this.getHoverMessageId() || $("#_timeLine ._messageSelected").data("mid"); 211 | } 212 | 213 | getMessagePosition(id) { 214 | let messages = RM.timeline.chat_list; 215 | for (let i = messages.length - 1; i >= 0; i--) { 216 | if (messages[i].id == id) { 217 | return i; 218 | } 219 | } 220 | 221 | return -1; 222 | } 223 | 224 | goToBottom() { 225 | this.goToMessageInRoom(RM.timeline.getLastChatId()); 226 | } 227 | 228 | goToPreviousMention() { 229 | let current = this.getPivotMessage(); 230 | let position = this.getMessagePosition(current); 231 | let messages = RM.timeline.chat_list; 232 | for (let i = position - 1; i >= 0; i--) { 233 | if (this.isMentionMessage(messages[i])) { 234 | this.goToMessageInRoom(messages[i].id); 235 | return true; 236 | } 237 | } 238 | 239 | if (!RM.timeline.has_old && messages.length == 0) { 240 | return false; 241 | } 242 | 243 | RM.timeline.loadOld(); 244 | } 245 | 246 | goToNexMention() { 247 | let current = this.getPivotMessage(); 248 | let position = this.getMessagePosition(current); 249 | let messages = RM.timeline.chat_list; 250 | for (let i = position + 1; i > 0 && i < messages.length; i++) { 251 | if (this.isMentionMessage(messages[i])) { 252 | this.goToMessageInRoom(messages[i].id); 253 | return true; 254 | } 255 | } 256 | 257 | return false; 258 | } 259 | 260 | goToMessageInRoom(message_id) { 261 | RL.selectRoom(RM.id, message_id, { 262 | smoothScroll: true 263 | }) 264 | } 265 | 266 | isMentionMessage(message) { 267 | let regex_reply = new RegExp(`\\[.* aid=${AC.myid} .*\\]`); 268 | let regex_to = new RegExp(`\\[To:${AC.myid}\\]`); 269 | let regex_to_all = new RegExp("\\[toall\\]"); 270 | 271 | return [regex_reply, regex_to, regex_to_all].some((r) => r.test(message.msg)); 272 | } 273 | 274 | replyMessage(message) { 275 | let data = RM.timeline.chat_id2chat_dat[message]; 276 | if (data) { 277 | $("#_chatText").focus(); 278 | let name = ST.data.private_nickname && !RM.isInternal() ? AC.getDefaultNickName(data.aid) : AC.getNickName(data.aid); 279 | /* eslint-disable no-useless-concat */ 280 | CS.view.setChatText(`[${L.chatsend_reply} aid=${data.aid} to=${RM.id}-${message}] ${name}` + "\n", !0); 281 | /* eslint-enable */ 282 | } 283 | } 284 | 285 | quoteMessage(message, skipable) { 286 | let data = RM.timeline.chat_id2chat_dat[message]; 287 | if (data) { 288 | // Apply Chatpp's own inserting logic when quoting a message which has Chatpp's emoticons 289 | if (skipable && $(`#_messageId${message}`).find('img.ui_emoticon[data-cwtag^="chatpp-"]').length == 0) { 290 | return; 291 | } 292 | $("#_chatText").focus(); 293 | /* eslint-disable no-useless-concat */ 294 | CS.view.setChatText(`[${L.chatsend_quote} aid=${data.aid} time=${data.tm}]${data.msg}[/${L.chatsend_quote}]` + "\n", !0); 295 | /* eslint-enable */ 296 | } 297 | } 298 | 299 | nextUnreadRoom(check_mention) { 300 | let current_room = RM.id; 301 | let sortedRooms = RL.getSortedRoomList(); 302 | let rooms = RL.rooms; 303 | for (let i = 0; i < sortedRooms.length; i++) { 304 | if (sortedRooms[i] && sortedRooms[i] !== current_room) { 305 | let room = rooms[sortedRooms[i]]; 306 | let check = check_mention ? room.getMentionNum() : room.getUnreadNum(); 307 | if (check) { 308 | return RL.selectRoom(room.id); 309 | } 310 | } 311 | } 312 | } 313 | 314 | nextRoom(back) { 315 | let previous; 316 | let current_room = RM.id; 317 | let sortedRooms = RL.getSortedRoomList(); 318 | for (let i = 0; i < sortedRooms.length; i++) { 319 | if (sortedRooms[i] === current_room) { 320 | if (back) { 321 | if (previous) { 322 | return RL.selectRoom(previous); 323 | } 324 | } else { 325 | if (sortedRooms[i+1]) { 326 | return RL.selectRoom(sortedRooms[i+1]); 327 | } 328 | } 329 | } 330 | previous = sortedRooms[i]; 331 | } 332 | } 333 | 334 | firstRoom(nonstick) { 335 | let sortedRooms = RL.getSortedRoomList(); 336 | let room_index = nonstick ? RL.getStickyRoomNum() : 0; 337 | return RL.selectRoom(sortedRooms[room_index]); 338 | } 339 | } 340 | 341 | let shortcut = new Shortcut(); 342 | module.exports = shortcut; 343 | -------------------------------------------------------------------------------- /src/js/internals/ViewEnhancer.js: -------------------------------------------------------------------------------- 1 | let chatwork = require("../helpers/ChatworkFacade.js"); 2 | let Const = require("../helpers/Const.js"); 3 | 4 | class ViewEnhancer { 5 | constructor() { 6 | this.to_all_status = true; 7 | } 8 | 9 | isActive() { 10 | return this.to_all_status; 11 | } 12 | 13 | updateGetContactPanelView() { 14 | AC.view.getContactPanelOld = AC.view.getContactPanel; 15 | AC.view.getContactPanel = function(b, d) { 16 | let panel = AC.view.getContactPanelOld(b, d); 17 | if (b == chatwork.myId()) { 18 | return panel; 19 | } 20 | let temp = $("
    "); 21 | let label = LANGUAGE == "ja" ? "同じグループチャットを探す" : "Search for the same Group Chat"; 22 | $(temp).html(panel); 23 | $(".contactPanel__footerButtonContainer", temp).first().append(`
    `); 24 | return $(temp).html(); 25 | }; 26 | $(document).on("click", ".searchSameRooms", (e) => { 27 | let uid = $(e.currentTarget).data("uid"); 28 | let username = chatwork.getUserName(uid); 29 | let same_rooms = chatwork.searchRoomsByPerson(uid); 30 | let result = ""; 31 | same_rooms.forEach((room) => { 32 | result += `
    ${room.getIcon()} ${room.getName()}
    `; 33 | }); 34 | let delete_button = ""; 35 | if (result) { 36 | delete_button = '
    ' + 37 | `Remove ${username} from the Rooms where you are an Administrator!
    Please be careful!
    ` + 38 | `
    Delete
    ` + 39 | "
    "; 40 | } 41 | result = '
    ' + 42 | `
    ${same_rooms.length} room(s) found!
    ` + 43 | `${result}${delete_button}` + 44 | "
    "; 45 | CW.view.alert(result, null, true); 46 | }); 47 | $(document).on("click", "#_removeSameRoomsBtn", (e) => { 48 | let uid = $(e.currentTarget).data("uid"); 49 | let username = chatwork.getUserName(uid); 50 | CW.confirm(`Are you sure to delete ${username} from the rooms that you are an Administrator?`, () => { 51 | let same_rooms = chatwork.searchRoomsByPerson(uid); 52 | let result = ""; 53 | same_rooms.forEach((room) => { 54 | if (chatwork.removeMemberFromRoom(uid, room.id)) { 55 | $(`.sameRoomInfo[data-rid="${room.id}"]`).hide(); 56 | let sameRoomNumberElement = $("#_sameRoomsNumber"); 57 | sameRoomNumberElement.html(sameRoomNumberElement.html() - 1); 58 | result += `
    ${room.getIcon()} ${room.getName()}
    `; 59 | } 60 | }); 61 | if (result) { 62 | result = '
    ' + 63 | `
    ${username} has been removed from the following room(s)!
    ` + 64 | `${result}` + 65 | "
    "; 66 | CW.view.alert(result, null, true); 67 | } 68 | }); 69 | }); 70 | } 71 | 72 | updateChatSendView() { 73 | CS.view.chatTextKeyUpOld = CS.view.chatTextKeyUp; 74 | CS.view.chatTextKeyUp = function(b) { 75 | let up_key = b.keyCode; 76 | let d = $("#_chatText"); 77 | (function() { 78 | /* eslint-disable no-undef */ 79 | if (!(up_key !== 13 || press_key !== 13)) { 80 | /* eslint-enable */ 81 | let a = d.val(), 82 | b = d.prop("selectionStart"), 83 | e = d.prop("selectionEnd"); 84 | b === e && ( 85 | e = a.substr(0, b), e = $.support.isWindowsFirefox ? e.replace(/(^|\n)``` *\r?\n([\s\S]+?)\r?\n```$/, "$1[code]\n$2\n[/code]") : e.replace(/(^|\n)``` *\r?\n([\s\S]+?)\r?\n```\n$/, "$1[code]\n$2\n[/code]\n"), 86 | e = $.support.isWindowsFirefox ? e.replace(/(^|\n)``t *\r?\n([\s\S]+?)\r?\n```$/, "$1[title]$2[/title]") : e.replace(/(^|\n)``t *\r?\n([\s\S]+?)\r?\n```\n$/, "$1[title]$2[/title]"), 87 | e = $.support.isWindowsFirefox ? e.replace(/(^|\n)``i *\r?\n([\s\S]+?)\r?\n```$/, "$1[info]$2[/info]") : e.replace(/(^|\n)``i *\r?\n([\s\S]+?)\r?\n```\n$/, "$1[info]$2[/info]\n"), 88 | a = a.substr(b), d.val(e + a), d.prop("selectionStart", e.length), d.prop("selectionEnd", e.length) 89 | ) 90 | } 91 | })(); 92 | return CS.view.chatTextKeyUpOld(b); 93 | }; 94 | } 95 | } 96 | 97 | let view_enhancer = new ViewEnhancer(); 98 | module.exports = view_enhancer; 99 | -------------------------------------------------------------------------------- /src/js/internals/main.js: -------------------------------------------------------------------------------- 1 | let emoticon = require("./Emoticon.js"); 2 | let shortcut = require("./Shortcut.js"); 3 | let view_enhancer = require("./ViewEnhancer.js"); 4 | let NotificationDisabler = require("./NotificationDisabler.js"); 5 | let notify_all = require("./NotifyAll.js"); 6 | let mention = require("./Mention.js"); 7 | let room_information = require("./RoomInformation.js"); 8 | const common = require("../helpers/Common.js"); 9 | 10 | let cw_timer; 11 | 12 | $(() => { 13 | cw_timer = setInterval(() => { 14 | if (typeof CW !== "undefined" && typeof RM !== "undefined") { 15 | window.clearInterval(cw_timer); 16 | $("#_chatppPreLoad").remove(); 17 | addStyle(); 18 | exposeModules(); 19 | if (emoticon.status) { 20 | emoticon.setUp(); 21 | } 22 | shortcut.setUp(); 23 | NotificationDisabler.setUp(); 24 | notify_all.setUp(); 25 | 26 | view_enhancer.updateChatSendView(); 27 | view_enhancer.updateGetContactPanelView(); 28 | 29 | RoomView.prototype.buildOld = RoomView.prototype.build; 30 | RoomView.prototype.build = function(a) { 31 | this.buildOld(a); 32 | if (window.chatpp_id != RM.id) { 33 | window.chatpp_id = RM.id; 34 | setTimeout(() => { 35 | emoticon.addExternalEmoList(false); 36 | room_information.setUp(); 37 | mention.setUp(); 38 | }, 100); 39 | } 40 | } 41 | 42 | RL.rooms[RM.id].build(); 43 | } 44 | }, 100); 45 | }); 46 | 47 | function exposeModules() { 48 | /* eslint-disable no-console */ 49 | /* for debugging new feature */ 50 | if (window.esmodules.length < 10) { 51 | console.log("Exposing esmodules failed! Chat++ Emoticons will not work! Try to reload browser by Ctrl + Shift + R"); 52 | } 53 | for (let i in window.esmodules) { 54 | let m = window.esmodules[i]; 55 | if (m.a && m.a.langMap) { 56 | console.log("Find Language module"); 57 | window.language_module = m; 58 | break; 59 | } 60 | } 61 | /* eslint-enable */ 62 | } 63 | 64 | function addStyle() { 65 | $("").appendTo("head"); 66 | $("").appendTo("head"); 67 | $("").appendTo("head"); 68 | $("").appendTo("head"); 69 | $("").appendTo("head"); 70 | $("").appendTo("head"); 71 | $("").appendTo("head"); 72 | $("").appendTo("head"); 73 | } 74 | -------------------------------------------------------------------------------- /src/js/libraries/bootbox.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootbox.js v4.3.0 3 | * 4 | * http://bootboxjs.com/license.txt 5 | */ 6 | !function(a,b){"use strict";"function"==typeof define&&define.amd?define(["jquery"],b):"object"==typeof exports?module.exports=b(require("jquery")):a.bootbox=b(a.jQuery)}(this,function a(b,c){"use strict";function d(a){var b=q[o.locale];return b?b[a]:q.en[a]}function e(a,c,d){a.stopPropagation(),a.preventDefault();var e=b.isFunction(d)&&d(a)===!1;e||c.modal("hide")}function f(a){var b,c=0;for(b in a)c++;return c}function g(a,c){var d=0;b.each(a,function(a,b){c(a,b,d++)})}function h(a){var c,d;if("object"!=typeof a)throw new Error("Please supply an object of options");if(!a.message)throw new Error("Please specify a message");return a=b.extend({},o,a),a.buttons||(a.buttons={}),a.backdrop=a.backdrop?"static":!1,c=a.buttons,d=f(c),g(c,function(a,e,f){if(b.isFunction(e)&&(e=c[a]={callback:e}),"object"!==b.type(e))throw new Error("button with key "+a+" must be an object");e.label||(e.label=a),e.className||(e.className=2>=d&&f===d-1?"btn-primary":"btn-default")}),a}function i(a,b){var c=a.length,d={};if(1>c||c>2)throw new Error("Invalid argument length");return 2===c||"string"==typeof a[0]?(d[b[0]]=a[0],d[b[1]]=a[1]):d=a[0],d}function j(a,c,d){return b.extend(!0,{},a,i(c,d))}function k(a,b,c,d){var e={className:"bootbox-"+a,buttons:l.apply(null,b)};return m(j(e,d,c),b)}function l(){for(var a={},b=0,c=arguments.length;c>b;b++){var e=arguments[b],f=e.toLowerCase(),g=e.toUpperCase();a[f]={label:d(g)}}return a}function m(a,b){var d={};return g(b,function(a,b){d[b]=!0}),g(a.buttons,function(a){if(d[a]===c)throw new Error("button key "+a+" is not allowed (options are "+b.join("\n")+")")}),a}var n={dialog:"",header:"",footer:"",closeButton:"",form:"
    ",inputs:{text:"",textarea:"",email:"",select:"",checkbox:"
    ",date:"",time:"",number:"",password:""}},o={locale:"en",backdrop:!0,animate:!0,className:null,closeButton:!0,show:!0,container:"body"},p={};p.alert=function(){var a;if(a=k("alert",["ok"],["message","callback"],arguments),a.callback&&!b.isFunction(a.callback))throw new Error("alert requires callback property to be a function when provided");return a.buttons.ok.callback=a.onEscape=function(){return b.isFunction(a.callback)?a.callback():!0},p.dialog(a)},p.confirm=function(){var a;if(a=k("confirm",["cancel","confirm"],["message","callback"],arguments),a.buttons.cancel.callback=a.onEscape=function(){return a.callback(!1)},a.buttons.confirm.callback=function(){return a.callback(!0)},!b.isFunction(a.callback))throw new Error("confirm requires a callback");return p.dialog(a)},p.prompt=function(){var a,d,e,f,h,i,k;if(f=b(n.form),d={className:"bootbox-prompt",buttons:l("cancel","confirm"),value:"",inputType:"text"},a=m(j(d,arguments,["title","callback"]),["cancel","confirm"]),i=a.show===c?!0:a.show,a.message=f,a.buttons.cancel.callback=a.onEscape=function(){return a.callback(null)},a.buttons.confirm.callback=function(){var c;switch(a.inputType){case"text":case"textarea":case"email":case"select":case"date":case"time":case"number":case"password":c=h.val();break;case"checkbox":var d=h.find("input:checked");c=[],g(d,function(a,d){c.push(b(d).val())})}return a.callback(c)},a.show=!1,!a.title)throw new Error("prompt requires a title");if(!b.isFunction(a.callback))throw new Error("prompt requires a callback");if(!n.inputs[a.inputType])throw new Error("invalid prompt type");switch(h=b(n.inputs[a.inputType]),a.inputType){case"text":case"textarea":case"email":case"date":case"time":case"number":case"password":h.val(a.value);break;case"select":var o={};if(k=a.inputOptions||[],!k.length)throw new Error("prompt with select requires options");g(k,function(a,d){var e=h;if(d.value===c||d.text===c)throw new Error("given options in wrong format");d.group&&(o[d.group]||(o[d.group]=b("").attr("label",d.group)),e=o[d.group]),e.append("")}),g(o,function(a,b){h.append(b)}),h.val(a.value);break;case"checkbox":var q=b.isArray(a.value)?a.value:[a.value];if(k=a.inputOptions||[],!k.length)throw new Error("prompt with checkbox requires options");if(!k[0].value||!k[0].text)throw new Error("given options in wrong format");h=b("
    "),g(k,function(c,d){var e=b(n.inputs[a.inputType]);e.find("input").attr("value",d.value),e.find("label").append(d.text),g(q,function(a,b){b===d.value&&e.find("input").prop("checked",!0)}),h.append(e)})}return a.placeholder&&h.attr("placeholder",a.placeholder),a.pattern&&h.attr("pattern",a.pattern),f.append(h),f.on("submit",function(a){a.preventDefault(),a.stopPropagation(),e.find(".btn-primary").click()}),e=p.dialog(a),e.off("shown.bs.modal"),e.on("shown.bs.modal",function(){h.focus()}),i===!0&&e.modal("show"),e},p.dialog=function(a){a=h(a);var c=b(n.dialog),d=c.find(".modal-dialog"),f=c.find(".modal-body"),i=a.buttons,j="",k={onEscape:a.onEscape};if(g(i,function(a,b){j+="",k[a]=b.callback}),f.find(".bootbox-body").html(a.message),a.animate===!0&&c.addClass("fade"),a.className&&c.addClass(a.className),"large"===a.size&&d.addClass("modal-lg"),"small"===a.size&&d.addClass("modal-sm"),a.title&&f.before(n.header),a.closeButton){var l=b(n.closeButton);a.title?c.find(".modal-header").prepend(l):l.css("margin-top","-10px").prependTo(f)}return a.title&&c.find(".modal-title").html(a.title),j.length&&(f.after(n.footer),c.find(".modal-footer").html(j)),c.on("hidden.bs.modal",function(a){a.target===this&&c.remove()}),c.on("shown.bs.modal",function(){c.find(".btn-primary:first").focus()}),c.on("escape.close.bb",function(a){k.onEscape&&e(a,c,k.onEscape)}),c.on("click",".modal-footer button",function(a){var d=b(this).data("bb-handler");e(a,c,k[d])}),c.on("click",".bootbox-close-button",function(a){e(a,c,k.onEscape)}),c.on("keyup",function(a){27===a.which&&c.trigger("escape.close.bb")}),b(a.container).append(c),c.modal({backdrop:a.backdrop,keyboard:!1,show:!1}),a.show&&c.modal("show"),c},p.setDefaults=function(){var a={};2===arguments.length?a[arguments[0]]=arguments[1]:a=arguments[0],b.extend(o,a)},p.hideAll=function(){return b(".bootbox").modal("hide"),p};var q={br:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Sim"},cs:{OK:"OK",CANCEL:"Zrušit",CONFIRM:"Potvrdit"},da:{OK:"OK",CANCEL:"Annuller",CONFIRM:"Accepter"},de:{OK:"OK",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},el:{OK:"Εντάξει",CANCEL:"Ακύρωση",CONFIRM:"Επιβεβαίωση"},en:{OK:"OK",CANCEL:"Cancel",CONFIRM:"OK"},es:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Aceptar"},et:{OK:"OK",CANCEL:"Katkesta",CONFIRM:"OK"},fi:{OK:"OK",CANCEL:"Peruuta",CONFIRM:"OK"},fr:{OK:"OK",CANCEL:"Annuler",CONFIRM:"D'accord"},he:{OK:"אישור",CANCEL:"ביטול",CONFIRM:"אישור"},id:{OK:"OK",CANCEL:"Batal",CONFIRM:"OK"},it:{OK:"OK",CANCEL:"Annulla",CONFIRM:"Conferma"},ja:{OK:"OK",CANCEL:"キャンセル",CONFIRM:"確認"},lt:{OK:"Gerai",CANCEL:"Atšaukti",CONFIRM:"Patvirtinti"},lv:{OK:"Labi",CANCEL:"Atcelt",CONFIRM:"Apstiprināt"},nl:{OK:"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},no:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},pl:{OK:"OK",CANCEL:"Anuluj",CONFIRM:"Potwierdź"},pt:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Confirmar"},ru:{OK:"OK",CANCEL:"Отмена",CONFIRM:"Применить"},sv:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},tr:{OK:"Tamam",CANCEL:"İptal",CONFIRM:"Onayla"},zh_CN:{OK:"OK",CANCEL:"取消",CONFIRM:"确认"},zh_TW:{OK:"OK",CANCEL:"取消",CONFIRM:"確認"}};return p.init=function(c){return a(c||b)},p}); -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | var mix = require('laravel-mix'); 2 | 3 | mix.combine([ 4 | 'src/js/libraries/bootstrap.min.js', 5 | 'src/js/libraries/bootbox.min.js' 6 | ], 'build/js/externals/libs.js') 7 | .js('src/js/externals/popup.js', 'build/js/externals/popup.js') 8 | .js([ 9 | 'src/js/externals/emoticon.js', 10 | 'src/js/externals/shortcut.js', 11 | 'src/js/externals/notification.js', 12 | 'src/js/externals/room.js', 13 | 'src/js/externals/setting.js', 14 | 'src/js/externals/group.js', 15 | ], 'build/js/externals/pages.js') 16 | .js('src/js/extensions/contentscript.js', 'build/js/extensions/contentscript.js') 17 | .js('src/js/extensions/background.js', 'build/js/extensions/background.js') 18 | .js('src/js/internals/main.js', 'build/js/internals/all.js') 19 | .copy("src/js/extensions/injectPreloadHook.js", "build/js/extensions/injectPreloadHook.js"); --------------------------------------------------------------------------------