├── .editorconfig ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .stylelintrc ├── .travis.yml ├── .vscode └── launch.json ├── LICENSE ├── README-CN.md ├── README.md ├── angular.json ├── build.sh ├── karma.conf.js ├── lib ├── components │ ├── interfaces │ │ ├── holder.options.ts │ │ ├── icons.ts │ │ ├── notify-event.type.ts │ │ ├── notify.type.ts │ │ ├── options.type.ts │ │ └── push.type.ts │ ├── notify-holder.component.html │ ├── notify-holder.component.ts │ ├── notify.component.html │ ├── notify.component.ts │ ├── notify.less │ ├── notify.service.config.ts │ ├── notify.service.ts │ ├── notify.types.ts │ └── push.service.ts ├── index.ts ├── ngx-notify.module.ts ├── public_api.ts ├── spec │ └── notify.directive.spec.ts ├── test.ts ├── tsconfig.json └── tsconfig.spec.json ├── package.json ├── protractor.conf.js ├── rollup.config.js ├── scripts └── inline-template.js ├── src ├── app │ ├── app.component.ts │ ├── app.module.ts │ └── components │ │ ├── demo.component.html │ │ ├── demo.component.scss │ │ └── demo.component.ts ├── assets │ ├── forkme.png │ ├── logo.png │ └── system-fault.mp3 ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── index.html ├── main.ts ├── polyfills.ts ├── tsconfig.json └── typings.d.ts ├── tsconfig-build.json ├── tsconfig.json ├── tslint.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /__gen_lib 8 | /publish-es5 9 | /publish-es2015 10 | /publish 11 | 12 | # dependencies 13 | /node_modules 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | 31 | # misc 32 | /.sass-cache 33 | /connect.lock 34 | /coverage 35 | /libpeerconnection.log 36 | npm-debug.log 37 | yarn-error.log 38 | testem.log 39 | /typings 40 | 41 | # System Files 42 | .DS_Store 43 | Thumbs.db 44 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | .tsdrc 30 | 31 | #IntelliJ configuration files 32 | .idea 33 | 34 | dist 35 | dev 36 | docs 37 | lib 38 | test 39 | tmp 40 | 41 | Thumbs.db 42 | .DS_Store 43 | 44 | *.ts 45 | !*.d.ts 46 | 47 | src/app/example* 48 | src/public 49 | typings 50 | *_spec.* 51 | CONTRIBUTING.md 52 | gulpfile.ts 53 | favicon.ico 54 | karma-shim.js 55 | karma.conf.js 56 | make.js 57 | protractor.conf.js 58 | test-main.js 59 | tsconfig.json 60 | tslint.json 61 | typedoc.json 62 | typings.json 63 | webpack.config.js 64 | *.yml 65 | .jshintrc 66 | .editorconfig 67 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipchk/ngx-notify/4bf6ea60ef4f7125a0c55d36f58b7febec34ca77/.prettierignore -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": { 3 | "stylelint-config-standard", 4 | "./node_modules/prettier-stylelint/config.js" 5 | }, 6 | "rules": { 7 | "comment-empty-line-before": null, 8 | "declaration-empty-line-before": null, 9 | "function-comma-newline-after": null, 10 | "function-name-case": null, 11 | "function-parentheses-newline-inside": null, 12 | "function-max-empty-lines": null, 13 | "function-whitespace-after": null, 14 | "indentation": null, 15 | "number-leading-zero": null, 16 | "number-no-trailing-zeros": null, 17 | "rule-empty-line-before": null, 18 | "selector-combinator-space-after": null, 19 | "selector-list-comma-newline-after": null, 20 | "selector-pseudo-element-colon-notation": null, 21 | "unit-no-unknown": null, 22 | "value-list-max-empty-lines": null, 23 | "selector-type-no-unknown": null, 24 | "selector-pseudo-element-no-unknown": [ 25 | true, 26 | { 27 | "ignorePseudoElements": [ 28 | "ng-deep" 29 | ] 30 | } 31 | ], 32 | "no-descending-specificity": null 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | node_js: 4 | - "8.11.0" 5 | 6 | env: 7 | - TASK=test 8 | - TASK=lint 9 | - TASK=build 10 | - TASK=site:build 11 | 12 | addons: 13 | apt: 14 | sources: 15 | - google-chrome 16 | packages: 17 | - google-chrome-stable 18 | - google-chrome-beta 19 | 20 | git: 21 | depth: 1 22 | 23 | before_install: 24 | - export CHROME_BIN=chromium-browser 25 | - export DISPLAY=:99.0 26 | - sh -e /etc/init.d/xvfb start 27 | 28 | script: 29 | - npm run $TASK 30 | - | 31 | if [ "$TASK" = "test" ]; then 32 | cat ./coverage/lcov.info | ./node_modules/.bin/codecov 33 | fi 34 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}/scripts/inline-template.js" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present 卡色 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # ngx-notify 2 | 一个无须依赖HTML模板、极简Angular通知组件。 3 | 4 | [![NPM version](https://img.shields.io/npm/v/ngx-notify.svg)](https://www.npmjs.com/package/ngx-notify) 5 | [![Build Status](https://travis-ci.org/cipchk/ngx-notify.svg?branch=master)](https://travis-ci.org/cipchk/ngx-notify) 6 | 7 | 通知组件优秀的非常多,但多数都是需要依赖组件模板,这一点让我很蛋疼,特别是你想做一些(例如:通用处理HTTP操作时)通过操作时,这时就懵逼不&……%¥,于是就有了这个轮子! 8 | 9 | ## 示例 10 | 11 | - [在线示例](https://cipchk.github.io/ngx-notify/) 12 | - [Stackblitz](https://stackblitz.com/edit/ngx-notify) 13 | 14 | ## 安装 15 | 16 | ``` 17 | npm install ngx-notify --save 18 | ``` 19 | 20 | 添加 `NotifyModule` 模块到项目中: 21 | 22 | ``` 23 | import { NotifyModule } from 'ngx-notify'; 24 | 25 | @NgModule({ 26 | imports: [ 27 | BrowserModule, 28 | NotifyModule.forRoot({ 29 | options: { }, 30 | notify: { 31 | progress: true 32 | } 33 | }) 34 | ], 35 | declarations: [AppComponent], 36 | bootstrap: [AppComponent] 37 | }) 38 | export class AppModule { } 39 | ``` 40 | 41 | **全局选项** 42 | 43 | `NotifyModule.forRoot` 有三个参数来指定通用全局配置。 44 | 45 | | 名称 | 类型 | 默认值 | 描述 | 46 | | ------- |:-------------:| -----:| -----:| 47 | | container | HTMLElement | `document.body` | 通知组件容器 | 48 | | options | HolderOptions | | 通知容器组件配置项,见[HolderOptions](#holder-options) | 49 | | notify | Options | | 通知组件配置项,见[Options](#options) | 50 | 51 | ## 使用方法 52 | 53 | 通知的创建都是以 `NotifyService` 来驱动的,所以需要先将期注入到组件类中。 54 | 55 | ``` 56 | constructor(private notifyService: NotifyService) {} 57 | ``` 58 | 59 | 创建一个类型为 `success` 的通知: 60 | 61 | ``` 62 | this.notifyService.success('title', 'content'); 63 | ``` 64 | 65 | 当然,你可以随时为某个通知覆盖默认[Options](#options)配置项。 66 | 67 | ``` 68 | // 10秒后关闭 69 | this.notifyService.success('title', 'content', { timeout: 1000 * 10 }); 70 | ``` 71 | 72 | ## HolderOptions 73 | 74 | | 名称 | 类型 | 默认值 | 描述 | 75 | | ------- |:-------------:| -----:| -----:| 76 | | position | Array | `[ 'right', 'bottom' ]` | 容器位置,包括值:`[ 'right' | 'left', 'top' | 'bottom']` | 77 | | offset | Array | `[ 20, 20 ]` | 容器偏移值 | 78 | | zIndex | number | 1031 | CSS `z-index` 值 | 79 | | minWidth | number | 300 | CSS `min-width` 值 | 80 | | maxWidth | number | 300 | CSS `max-width` 值 | 81 | | maxStack | number | 8 | 最多显示数量,当超过时强制移除最旧的通知 | 82 | | lastOnBottom | boolean | true | 是否从底部开始显示 | 83 | | className | string | | 自定义类名 | 84 | | onCreate | Function | | 创建通知的回调 | 85 | | onDestroy | Function | | 销毁通知的回调 | 86 | 87 | 88 | ## Options 89 | 90 | | 名称 | 类型 | 默认值 | 描述 | 91 | | ------- |:-------------:| -----:| -----:| 92 | | type | string | success | 组件类型,取值范围:`'success' | 'error' | 'info' | 'alert' | 'bare' | 'html'` | 93 | | icon | string | success | 图标样式,未指定默认会根据type自动切换,HTML格式,例如:``、``、`` 等。 | 94 | | title | string | | 标题 | 95 | | content | string | | 内容 | 96 | | theme | string | default | 主题,取值范围:`'default' | 'bootstrap'`。 | 97 | | html | string | | 自定义模板代码,支持 \{type\}、\{icon\}、\{title\}、\{content\} 字符串格式化。 | 98 | | animate_in | string | | 进入动画(可以使用animate.css),默认系统以淡入 | 99 | | animate_out | string | | 离开动画(可以使用animate.css),默认系统以淡出 | 100 | | progress | boolean | false | 是否显示进度条 | 101 | | timeout | boolean | 3000 | 显示时长(单位:毫秒) | 102 | | pauseOnHover | boolean | true | 悬停时暂停 | 103 | | clickToClose | boolean | true | 点击时关闭 | 104 | | rtl | boolean | false | 文本方向从右到左 | 105 | | className | string | | 自定义类名 | 106 | | onCreate | Function | | 创建通知的回调 | 107 | | onDestroy | Function | | 销毁通知的回调 | 108 | 109 | ## Theme 主题 110 | 111 | 如果你需要使用 `bootstrap` (支持V3、V4)的话,需要引入 `bootstrap.css`,例如: 112 | 113 | ``` 114 | this.notifyService.success('title', 'content', { 115 | theme: 'bootstrap' 116 | }); 117 | ``` 118 | 119 | 或者,也可以自定义模板。 120 | 121 | ``` 122 | this.notifyService.success('title', 'content', { 123 | className: 'my-notify', 124 | html: `
{title}
125 |
{content}
126 |
127 | ` 128 | }); 129 | ``` 130 | 131 | 自定义模板可以使用[Options](#options)中所有属性值,以 `{title}` 中括号来表示标题值。 132 | 133 | **建议** 自定义模板时和 `className` 一起使用! 134 | 135 | ## Animate 动画效果 136 | 137 | 默认淡入淡出效果是由CSS实现,如果需要获得更多动画效果,可以引入`Animate.css`,例如: 138 | 139 | ``` 140 | this.notifyService.success('title', 'content', { 141 | animate_in: 'fadeInLeft', 142 | animate_out: 'fadeInRight' 143 | }); 144 | ``` 145 | 146 | # Troubleshooting 147 | 148 | Please follow this guidelines when reporting bugs and feature requests: 149 | 150 | 1. Use [GitHub Issues](https://github.com/cipchk/ngx-notify/issues) board to report bugs and feature requests (not our email address) 151 | 2. Please **always** write steps to reproduce the error. That way we can focus on fixing the bug, not scratching our heads trying to reproduce it. 152 | 153 | Thanks for understanding! 154 | 155 | ### License 156 | 157 | The MIT License (see the [LICENSE](https://github.com/cipchk/ngx-notify/blob/master/LICENSE) file for the full text) 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 注意 2 | 3 | [ngx-toastr](https://www.npmjs.com/package/ngx-toastr) 比 `ngx-notify` 更优秀,如果可以,建议优先使用;我依然对 `ngx-notify` 保持更新。 4 | 5 | # ngx-notify 6 | 7 | 一个无须依赖HTML模板、极简Angular通知组件。 8 | 9 | [![NPM version](https://img.shields.io/npm/v/ngx-notify.svg)](https://www.npmjs.com/package/ngx-notify) 10 | [![Build Status](https://travis-ci.org/cipchk/ngx-notify.svg?branch=master)](https://travis-ci.org/cipchk/ngx-notify) 11 | 12 | 通知组件优秀的非常多,但多数都是需要依赖组件模板,这一点让我很蛋疼,特别是你想做一些(例如:通用处理HTTP操作时)通过操作时,这时就懵逼不&……%¥,于是就有了这个轮子! 13 | 14 | ## Installation instructions 15 | 16 | Install `ngx-notify` from `npm` 17 | 18 | ``` 19 | npm install ngx-notify --save 20 | ``` 21 | 22 | Import the `NotifyModule` in to your root `AppModule`. 23 | 24 | ``` 25 | import { NotifyModule } from 'ngx-notify'; 26 | 27 | @NgModule({ 28 | imports: [ 29 | BrowserModule, 30 | NotifyModule.forRoot({ 31 | options: { }, 32 | notify: { 33 | progress: true 34 | } 35 | }) 36 | ], 37 | declarations: [AppComponent], 38 | bootstrap: [AppComponent] 39 | }) 40 | export class AppModule { } 41 | ``` 42 | 43 | ## Usage & Demo 44 | 45 | - [Live Demo](https://cipchk.github.io/ngx-notify/) 46 | - [Stackblitz](https://stackblitz.com/edit/ngx-notify) 47 | 48 | # Troubleshooting 49 | 50 | Please follow this guidelines when reporting bugs and feature requests: 51 | 52 | 1. Use [GitHub Issues](https://github.com/cipchk/ngx-notify/issues) board to report bugs and feature requests (not our email address) 53 | 2. Please **always** write steps to reproduce the error. That way we can focus on fixing the bug, not scratching our heads trying to reproduce it. 54 | 55 | Thanks for understanding! 56 | 57 | ## More 58 | 59 | 更多说明见:[中文版](https://github.com/cipchk/ngx-notify/blob/master/README-CN.md) 60 | 61 | ### License 62 | 63 | The MIT License (see the [LICENSE](https://github.com/cipchk/ngx-notify/blob/master/LICENSE) file for the full text) 64 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-notify": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "architect": { 11 | "build": { 12 | "builder": "@angular-devkit/build-angular:browser", 13 | "options": { 14 | "outputPath": "dist", 15 | "index": "src/index.html", 16 | "main": "src/main.ts", 17 | "tsConfig": "src/tsconfig.json", 18 | "polyfills": "src/polyfills.ts", 19 | "assets": ["src/assets"], 20 | "styles": [], 21 | "scripts": [] 22 | }, 23 | "configurations": { 24 | "production": { 25 | "optimization": true, 26 | "outputHashing": "all", 27 | "sourceMap": false, 28 | "extractCss": true, 29 | "namedChunks": false, 30 | "aot": true, 31 | "extractLicenses": true, 32 | "vendorChunk": false, 33 | "buildOptimizer": true, 34 | "fileReplacements": [ 35 | { 36 | "replace": "src/environments/environment.ts", 37 | "with": "src/environments/environment.prod.ts" 38 | } 39 | ] 40 | } 41 | } 42 | }, 43 | "serve": { 44 | "builder": "@angular-devkit/build-angular:dev-server", 45 | "options": { 46 | "browserTarget": "ngx-notify:build" 47 | }, 48 | "configurations": { 49 | "production": { 50 | "browserTarget": "ngx-notify:build:production" 51 | } 52 | } 53 | }, 54 | "extract-i18n": { 55 | "builder": "@angular-devkit/build-angular:extract-i18n", 56 | "options": { 57 | "browserTarget": "ngx-notify:build" 58 | } 59 | }, 60 | "test": { 61 | "builder": "@angular-devkit/build-angular:karma", 62 | "options": { 63 | "main": "lib/test.ts", 64 | "tsConfig": "lib/tsconfig.spec.json", 65 | "karmaConfig": "karma.conf.js", 66 | "polyfills": "src/polyfills.ts", 67 | "scripts": [], 68 | "styles": [], 69 | "assets": ["src/assets"] 70 | } 71 | }, 72 | "lint": { 73 | "builder": "@angular-devkit/build-angular:tslint", 74 | "options": { 75 | "tsConfig": [], 76 | "exclude": [] 77 | } 78 | } 79 | } 80 | }, 81 | "ngx-notify-e2e": { 82 | "root": "", 83 | "sourceRoot": "", 84 | "projectType": "application", 85 | "architect": { 86 | "e2e": { 87 | "builder": "@angular-devkit/build-angular:protractor", 88 | "options": { 89 | "protractorConfig": "protractor.conf.js", 90 | "devServerTarget": "ngx-notify:serve" 91 | } 92 | }, 93 | "lint": { 94 | "builder": "@angular-devkit/build-angular:tslint", 95 | "options": { 96 | "tsConfig": [], 97 | "exclude": [] 98 | } 99 | } 100 | } 101 | } 102 | }, 103 | "defaultProject": "ngx-notify", 104 | "schematics": { 105 | "@schematics/angular:component": { 106 | "prefix": "", 107 | "styleext": "css" 108 | }, 109 | "@schematics/angular:directive": { 110 | "prefix": "" 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | readonly currentDir=$(cd $(dirname $0); pwd) 4 | cd ${currentDir} 5 | rm -rf publish 6 | rm -rf __gen_lib 7 | rm -rf publish-es2015 8 | cp -r lib __gen_lib 9 | node ./scripts/inline-template.js 10 | 11 | echo 'Compiling to es2015 via Angular compiler' 12 | $(npm bin)/ngc -p tsconfig-build.json -t es2015 --outDir publish-es2015/src 13 | 14 | echo 'Bundling to es module of es2015' 15 | export ROLLUP_TARGET=esm 16 | $(npm bin)/rollup -c rollup.config.js -f es -i publish-es2015/src/index.js -o publish-es2015/esm2015/notify.js 17 | 18 | echo 'Compiling to es5 via Angular compiler' 19 | $(npm bin)/ngc -p tsconfig-build.json -t es5 --outDir publish-es5/src 20 | 21 | echo 'Bundling to es module of es5' 22 | export ROLLUP_TARGET=esm 23 | $(npm bin)/rollup -c rollup.config.js -f es -i publish-es5/src/index.js -o publish-es5/esm5/notify.js 24 | 25 | echo 'Bundling to umd module of es5' 26 | export ROLLUP_TARGET=umd 27 | $(npm bin)/rollup -c rollup.config.js -f umd -i publish-es5/esm5/notify.js -o publish-es5/bundles/notify.umd.js 28 | 29 | echo 'Bundling to minified umd module of es5' 30 | export ROLLUP_TARGET=mumd 31 | $(npm bin)/rollup -c rollup.config.js -f umd -i publish-es5/esm5/notify.js -o publish-es5/bundles/notify.umd.min.js 32 | 33 | echo 'Unifying publish folder' 34 | mv publish-es5 publish 35 | mv publish-es2015/esm2015 publish/esm2015 36 | rm -rf publish-es2015 37 | 38 | echo 'Cleaning up temporary files' 39 | rm -rf __gen_lib 40 | rm -rf publish/src/*.js 41 | rm -rf publish/src/**/*.js 42 | 43 | echo 'Normalizing entry files' 44 | sed -e "s/from '.\//from '.\/src\//g" publish/src/index.d.ts > publish/notify.d.ts 45 | sed -e "s/\":\".\//\":\".\/src\//g" publish/src/index.metadata.json > publish/notify.metadata.json 46 | rm publish/src/index.d.ts publish/src/index.metadata.json 47 | 48 | echo 'Copying package.json' 49 | cp package.json publish/package.json 50 | cp README.md publish/README.md 51 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/components/interfaces/holder.options.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from '@angular/core'; 2 | 3 | export interface HolderOptions { 4 | /** 5 | * 位置,默认:`[ 'right', 'bottom' ]` 6 | */ 7 | position?: ['right' | 'left', 'top' | 'bottom']; 8 | 9 | /** 10 | * 偏移值,默认:`[20, 20]` 11 | */ 12 | offset?: [number, number]; 13 | 14 | /** 15 | * z-index 值,默认:`1031` 16 | */ 17 | zIndex?: number; 18 | 19 | /** 20 | * 最多显示数量,当超过时强制移除最旧的通知,默认:`8` 21 | */ 22 | maxStack?: number; 23 | 24 | /** 25 | * 最小宽度,默认:`300` 26 | */ 27 | minWidth?: number; 28 | 29 | /** 30 | * 最大宽度,默认:`300` 31 | */ 32 | maxWidth?: number; 33 | 34 | /** 35 | * 是否从底部开始显示,默认:`true` 36 | */ 37 | lastOnBottom?: boolean; 38 | 39 | /** 40 | * 自定义类名 41 | */ 42 | className?: string; 43 | 44 | /** 45 | * 创建通知的回调 46 | */ 47 | onCreate?: Function; 48 | 49 | /** 50 | * 销毁通知的回调 51 | */ 52 | onDestroy?: Function; 53 | } 54 | -------------------------------------------------------------------------------- /lib/components/interfaces/icons.ts: -------------------------------------------------------------------------------- 1 | export interface Icons { 2 | alert: string; 3 | error: string; 4 | info: string; 5 | success: string; 6 | } 7 | 8 | export const defaultIcons: Icons = { 9 | alert: ` 10 | 11 | 12 | 13 | `, 14 | 15 | error: ` 16 | 17 | `, 18 | info: ` 19 | 20 | 21 | 22 | 23 | `, 24 | success: ` 25 | 26 | 27 | 28 | 29 | ` 30 | }; 31 | -------------------------------------------------------------------------------- /lib/components/interfaces/notify-event.type.ts: -------------------------------------------------------------------------------- 1 | import { HolderOptions } from './holder.options'; 2 | import { Notify } from './notify.type'; 3 | 4 | export interface NotifyEvent { 5 | command: string; 6 | id?: string; 7 | notify?: Notify; 8 | add?: boolean; 9 | holderOptions?: HolderOptions; 10 | } 11 | -------------------------------------------------------------------------------- /lib/components/interfaces/notify.type.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from '@angular/core'; 2 | import { Options } from './options.type'; 3 | 4 | export interface Notify extends Options { 5 | id?: string; 6 | 7 | createdOn?: Date; 8 | 9 | destroyedOn?: Date; 10 | 11 | click?: EventEmitter<{}>; 12 | 13 | [index: string]: any; 14 | } 15 | -------------------------------------------------------------------------------- /lib/components/interfaces/options.type.ts: -------------------------------------------------------------------------------- 1 | export interface Options { 2 | /** 3 | * 通知类型,默认:`success` 4 | */ 5 | type?: 'success' | 'error' | 'info' | 'alert' | 'bare' | 'html'; 6 | 7 | /** 8 | * ICON 9 | */ 10 | icon?: string; 11 | 12 | /** 13 | * 标题 14 | */ 15 | title?: string; 16 | 17 | /** 18 | * 消息内容 19 | */ 20 | content?: string; 21 | 22 | /** 23 | * 自定义模板代码,支持 \{type\}、\{icon\}、\{title\}、\{content\} 字符串格式化。 24 | */ 25 | html?: string; 26 | 27 | /** 28 | * 进入动画(可以使用animate.css),默认系统以淡入 29 | */ 30 | animate_in?: string; 31 | 32 | /** 33 | * 离开动画(可以使用animate.css),默认系统以淡出 34 | */ 35 | animate_out?: string; 36 | 37 | /** 38 | * 进度条,默认:`false` 39 | */ 40 | progress?: boolean; 41 | 42 | /** 43 | * 显示时长(单位:毫秒),默认:`3000` 44 | */ 45 | timeout?: number; 46 | 47 | /** 48 | * 悬停时暂停,默认:`true` 49 | */ 50 | pauseOnHover?: boolean; 51 | 52 | /** 53 | * 点击时关闭,默认:`true` 54 | */ 55 | clickToClose?: boolean; 56 | 57 | /** 58 | * 主题类型,默认:`default` 59 | */ 60 | theme?: 'default' | 'bootstrap'; 61 | 62 | /** 63 | * 文本方向从右到左,默认:`false` 64 | */ 65 | rtl?: boolean; 66 | 67 | /** 68 | * 自定义类名 69 | */ 70 | className?: string; 71 | 72 | /** 73 | * 创建通知的回调 74 | */ 75 | onCreate?: Function; 76 | 77 | /** 78 | * 销毁通知的回调 79 | */ 80 | onDestroy?: Function; 81 | } 82 | -------------------------------------------------------------------------------- /lib/components/interfaces/push.type.ts: -------------------------------------------------------------------------------- 1 | export type Permission = 'denied' | 'granted' | 'default'; 2 | 3 | export interface PushNotify { 4 | /** 5 | * 提醒内容 6 | */ 7 | body?: string; 8 | /** 9 | * 图标地址 10 | */ 11 | icon?: string; 12 | /** 13 | * 赋予通知一个ID,以便在必要的时候对通知进行刷新、替换或移除 14 | */ 15 | tag?: string; 16 | /** 17 | * 任意类型和通知相关联的数据。 18 | */ 19 | data?: any; 20 | /** 21 | * 叠高楼,如果指定为 `true` 且 `tag` 有值时;新通知会覆盖旧通知。 22 | */ 23 | renotify?: boolean; 24 | /** 25 | * 是否要有声音 26 | */ 27 | silent?: boolean; 28 | /** 29 | * 声音地址 30 | */ 31 | sound?: string; 32 | /** 33 | * 是否不再屏幕上显示通知信息 34 | */ 35 | noscreen?: boolean; 36 | /** 37 | * 是否通知具有粘性 38 | */ 39 | sticky?: boolean; 40 | /** 41 | * 文字方向,默认:`auto` 42 | */ 43 | dir?: 'auto' | 'ltr' | 'rtl'; 44 | /** 45 | * 语言,符串必须在 [!BCP 47 language tag](https://tools.ietf.org/html/bcp47) 文档中是有效的。 46 | */ 47 | lang?: string; 48 | /** 49 | * 振动模式 50 | * - 例如 `[200, 100, 200]` 表示设备振动200毫秒,然后停止100毫秒,再振动200毫秒。 51 | */ 52 | vibrate?: number[]; 53 | } 54 | -------------------------------------------------------------------------------- /lib/components/notify-holder.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /lib/components/notify-holder.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | OnInit, 4 | ViewEncapsulation, 5 | OnDestroy, 6 | EventEmitter, 7 | Injector, 8 | } from '@angular/core'; 9 | import { Subscription } from 'rxjs'; 10 | 11 | import { defaultIcons } from './interfaces/icons'; 12 | import { NotifyEvent } from './interfaces/notify-event.type'; 13 | import { HolderOptions } from './interfaces/holder.options'; 14 | import { Notify } from './interfaces/notify.type'; 15 | import { NotifyComponent } from './notify.component'; 16 | import { NotifyOptions } from './notify.types'; 17 | import { NotifyService } from './notify.service'; 18 | 19 | @Component({ 20 | selector: 'notify-holder', 21 | templateUrl: './notify-holder.component.html', 22 | styleUrls: ['./notify.less'], 23 | encapsulation: ViewEncapsulation.None, 24 | preserveWhitespaces: false 25 | }) 26 | export class NotifyHolderComponent implements OnInit, OnDestroy { 27 | notifies: Notify[] = []; 28 | options: HolderOptions; 29 | styles: any = {}; 30 | 31 | private listener: Subscription; 32 | 33 | constructor(injector: Injector) { 34 | this.listener = injector 35 | .get(NotifyService) 36 | .getChangeEmitter() 37 | .subscribe(item => { 38 | switch (item.command) { 39 | case 'clear': 40 | this.notifies = []; 41 | break; 42 | case 'remove': 43 | this.remove(item.id!); 44 | break; 45 | case 'set': 46 | if (item.add) this.add(item.notify!); 47 | else this.defaultBehavior(item); 48 | break; 49 | case 'options': 50 | this.options = Object.assign( 51 | { 52 | lastOnBottom: true, 53 | minWidth: 300, 54 | maxWidth: 300, 55 | maxStack: 8, 56 | position: ['right', 'bottom'], 57 | offset: [20, 20], 58 | zIndex: 1031, 59 | }, 60 | item.holderOptions!, 61 | ); 62 | this.styles = { 63 | 'z-index': this.options.zIndex, 64 | 'min-width.px': this.options.minWidth, 65 | 'max-width.px': this.options.maxWidth, 66 | }; 67 | this.styles[ 68 | this.options.position[0] + '.px' 69 | ] = this.options.offset[0]; 70 | this.styles[ 71 | this.options.position[1] + '.px' 72 | ] = this.options.offset[1]; 73 | break; 74 | default: 75 | this.defaultBehavior(item); 76 | break; 77 | } 78 | }); 79 | } 80 | 81 | ngOnInit() {} 82 | 83 | private defaultBehavior(value: NotifyEvent) { 84 | this.notifies.splice(this.notifies.indexOf(value.notify), 1); 85 | 86 | if (value.notify.onDestroy || this.options.onDestroy) { 87 | const emitItem = this.getEmit(value.notify, false); 88 | if (value.notify.onDestroy) value.notify.onDestroy(emitItem); 89 | 90 | if (this.options.onDestroy) this.options.onDestroy(emitItem); 91 | } 92 | } 93 | 94 | private getEmit(notify: Notify, to: boolean) { 95 | const res: Notify = { 96 | createdOn: notify.createdOn, 97 | id: notify.id, 98 | type: notify.type, 99 | icon: notify.icon, 100 | title: notify.title, 101 | content: notify.content, 102 | html: notify.html, 103 | }; 104 | if (!to) res.destroyedOn = new Date(); 105 | return res; 106 | } 107 | 108 | private remove(id: string) { 109 | let index = 0, 110 | item: Notify = null, 111 | allow = false; 112 | 113 | this.notifies.forEach((notify, idx) => { 114 | if (notify.id === id) { 115 | index = idx; 116 | allow = true; 117 | item = notify; 118 | } 119 | }); 120 | 121 | if (allow) { 122 | this.notifies.splice(index, 1); 123 | } 124 | } 125 | 126 | private add(item: Notify) { 127 | item = Object.assign( 128 | { 129 | type: 'success', 130 | timeout: 1000 * 3, 131 | pauseOnHover: true, 132 | progress: false, 133 | clickToClose: true, 134 | theme: 'default', 135 | animate_in: 'notify-fade-in', 136 | animate_out: 'notify-fade-out', 137 | createdOn: new Date(), 138 | }, 139 | item, 140 | ); 141 | 142 | if (!item.html) { 143 | switch (item.theme) { 144 | case 'bootstrap': 145 | item.html = ``; 146 | break; 147 | default: 148 | item.html = `
{title}
{content}
{icon}
`; 149 | break; 150 | } 151 | } 152 | 153 | if (this.options.lastOnBottom) { 154 | if (this.notifies.length >= this.options.maxStack) 155 | this.notifies.splice(0, 1); 156 | this.notifies.push(item); 157 | } else { 158 | if (this.notifies.length >= this.options.maxStack) 159 | this.notifies.splice(this.notifies.length - 1, 1); 160 | 161 | this.notifies.splice(0, 0, item); 162 | } 163 | 164 | if (item.onCreate || this.options.onCreate) { 165 | const emitItem = this.getEmit(item, true); 166 | if (item.onCreate) item.onCreate(emitItem); 167 | 168 | if (this.options.onCreate) this.options.onCreate(emitItem); 169 | } 170 | } 171 | 172 | ngOnDestroy(): void { 173 | if (this.listener) this.listener.unsubscribe(); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /lib/components/notify.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /lib/components/notify.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, Input, OnInit, NgZone } from '@angular/core'; 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 3 | 4 | import { Notify } from './interfaces/notify.type'; 5 | import { NotifyService } from './notify.service'; 6 | 7 | @Component({ 8 | selector: 'notify', 9 | templateUrl: './notify.component.html', 10 | preserveWhitespaces: false 11 | }) 12 | export class NotifyComponent implements OnInit, OnDestroy { 13 | 14 | @Input() item: Notify; 15 | html: SafeHtml; 16 | animate: string; 17 | classes: any = {}; 18 | 19 | constructor(private sanitizer: DomSanitizer, private zone: NgZone, private notifyService: NotifyService) { } 20 | 21 | ngOnInit(): void { 22 | 23 | switch (this.item.theme) { 24 | case 'bootstrap': 25 | switch (this.item.type) { 26 | case 'alert': 27 | this.item.bstype = 'warning'; 28 | break; 29 | case 'error': 30 | this.item.bstype = 'danger'; 31 | break; 32 | default: 33 | this.item.bstype = this.item.type; 34 | break; 35 | } 36 | break; 37 | } 38 | 39 | this.html = this.sanitizer.bypassSecurityTrustHtml(this.item.html.replace(/\{([a-z]+)\}/g, (full, key) => { 40 | return this.item[key] || ''; 41 | })); 42 | 43 | this.classes['notify-theme-' + this.item.theme] = true; 44 | this.classes['notify-' + this.item.type] = true; 45 | 46 | if (this.item.rtl === true) 47 | this.classes['notify-rtl'] = true; 48 | 49 | if (this.item.animate_in) { 50 | this.classes['animated'] = true; 51 | this.classes[this.item.animate_in] = true; 52 | } 53 | 54 | if (this.item.className) { 55 | this.classes[this.item.className] = true; 56 | } 57 | 58 | if (this.item.timeout > 0) 59 | this.startTimeout(); 60 | } 61 | 62 | private stopTime = false; 63 | private timer: any; 64 | private steps: number; 65 | private speed: number; 66 | private start: any; 67 | private count = 0; 68 | private diff: any; 69 | private startTimeout() { 70 | this.steps = this.item.timeout / 10; 71 | this.speed = this.item.timeout / this.steps; 72 | this.start = new Date().getTime(); 73 | this.zone.runOutsideAngular(() => this.timer = setTimeout(this.instance, this.speed)); 74 | } 75 | 76 | progressWidth = 0; 77 | private instance = () => { 78 | this.zone.runOutsideAngular(() => { 79 | this.zone.run(() => this.diff = (new Date().getTime() - this.start) - (this.count * this.speed)); 80 | 81 | if (this.count++ >= this.steps) { 82 | this.zone.run(() => { 83 | this.remove(); 84 | }); 85 | } else if (!this.stopTime) { 86 | if (this.item.progress) 87 | this.zone.run(() => this.progressWidth += 100 / this.steps); 88 | 89 | this.timer = setTimeout(this.instance, (this.speed - this.diff)); 90 | } 91 | }); 92 | } 93 | 94 | onEnter(): void { 95 | if (this.item.pauseOnHover) { 96 | this.stopTime = true; 97 | } 98 | } 99 | 100 | onLeave(): void { 101 | if (this.item.pauseOnHover) { 102 | this.stopTime = false; 103 | setTimeout(this.instance, (this.speed - this.diff)); 104 | } 105 | } 106 | 107 | onClick($e: MouseEvent): void { 108 | this.item.click!.emit($e); 109 | 110 | if (this.item.clickToClose) { 111 | this.remove(); 112 | } 113 | } 114 | 115 | private remove() { 116 | if (this.item.animate_out) { 117 | this.classes[this.item.animate_in] = false; 118 | this.classes[this.item.animate_out] = true; 119 | this.zone.runOutsideAngular(() => { 120 | setTimeout(() => { 121 | this.zone.run(() => this.notifyService.set(this.item, false)); 122 | }, 500); 123 | }); 124 | } else { 125 | this.notifyService.set(this.item, false); 126 | } 127 | } 128 | 129 | ngOnDestroy(): void { 130 | clearTimeout(this.timer); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /lib/components/notify.less: -------------------------------------------------------------------------------- 1 | @content-padding: 50px; 2 | @icon-width: 70px; 3 | 4 | .notifies { 5 | position: fixed; 6 | } 7 | 8 | .notify { 9 | width: 100%; 10 | box-sizing: border-box; 11 | position: relative; 12 | margin-bottom: 10px; 13 | color: #fff; 14 | cursor: pointer; 15 | transition: all 0.5s; 16 | } 17 | 18 | .notify-theme-default { 19 | padding: 10px 20px; 20 | &.notify-alert { 21 | background: #ffdb5b; 22 | .notify-progress span { 23 | background: #edc242; 24 | } 25 | } 26 | &.notify-success { 27 | background: #8bc34a; 28 | .notify-progress span { 29 | background: #689f38; 30 | } 31 | } 32 | &.notify-error { 33 | background: #f44336; 34 | .notify-progress span { 35 | background: #d32f2f; 36 | } 37 | } 38 | &.notify-info { 39 | background: #03a9f4; 40 | .notify-progress span { 41 | background: #0288d1; 42 | } 43 | } 44 | } 45 | 46 | .notify-theme-bootstrap { 47 | &.notify-alert { 48 | .notify-progress span { 49 | background: #edc242; 50 | } 51 | } 52 | &.notify-success { 53 | .notify-progress span { 54 | background: #689f38; 55 | } 56 | } 57 | &.notify-error { 58 | .notify-progress span { 59 | background: #d32f2f; 60 | } 61 | } 62 | &.notify-info { 63 | .notify-progress span { 64 | background: #0288d1; 65 | } 66 | } 67 | } 68 | 69 | .notify-title { 70 | margin: 0; 71 | padding: 0; 72 | line-height: 30px; 73 | font-size: 16px; 74 | font-weight: bold; 75 | } 76 | 77 | .notify-content { 78 | margin: 0; 79 | font-size: 14px; 80 | padding: 0 @content-padding 0 0; 81 | line-height: 20px; 82 | } 83 | 84 | .notify-icon { 85 | position: absolute; 86 | box-sizing: border-box; 87 | top: 0; 88 | right: 0; 89 | height: 100%; 90 | max-width: @icon-width; 91 | width: 100%; 92 | svg { 93 | width: 100%; 94 | height: 100%; 95 | padding: 10px; 96 | fill: #fff; 97 | } 98 | > * { 99 | max-width: @icon-width; 100 | } 101 | } 102 | 103 | .notify-progress { 104 | position: absolute; 105 | top: 0; 106 | left: 0; 107 | width: 100%; 108 | height: 5px; 109 | span { 110 | float: left; 111 | height: 100%; 112 | } 113 | } 114 | 115 | .notify-rtl { 116 | direction: rtl; 117 | .notify-title { 118 | padding-left: @content-padding; 119 | } 120 | .notify-content { 121 | padding: 0 0 0 @content-padding; 122 | } 123 | .notify-icon { 124 | left: 0; 125 | right: auto; 126 | } 127 | } 128 | 129 | .notify-fade-in { 130 | animation-duration: 1s; 131 | animation-fill-mode: both; 132 | -webkit-animation-name: fadeIn; 133 | animation-name: fadeIn; 134 | } 135 | 136 | .notify-fade-out { 137 | animation-duration: 1s; 138 | animation-fill-mode: both; 139 | -webkit-animation-name: fadeOut; 140 | animation-name: fadeOut; 141 | } 142 | 143 | @-webkit-keyframes fadeIn { 144 | 0% { 145 | opacity: 0; 146 | } 147 | 100% { 148 | opacity: 1; 149 | } 150 | } 151 | 152 | @keyframes fadeIn { 153 | 0% { 154 | opacity: 0; 155 | } 156 | 100% { 157 | opacity: 1; 158 | } 159 | } 160 | 161 | @-webkit-keyframes fadeOut { 162 | 0% { 163 | opacity: 1; 164 | } 165 | 100% { 166 | opacity: 0; 167 | } 168 | } 169 | 170 | @keyframes fadeOut { 171 | 0% { 172 | opacity: 1; 173 | } 174 | 100% { 175 | opacity: 0; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /lib/components/notify.service.config.ts: -------------------------------------------------------------------------------- 1 | import { Options } from './interfaces/options.type'; 2 | import { HolderOptions } from './interfaces/holder.options'; 3 | 4 | export class NotifyServiceConfig { 5 | /** 6 | * 容器,默认:`document.body` 7 | */ 8 | container?: HTMLElement = null; 9 | 10 | options?: HolderOptions; 11 | 12 | notify?: Options; 13 | } 14 | -------------------------------------------------------------------------------- /lib/components/notify.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | Optional, 4 | ComponentFactoryResolver, 5 | ApplicationRef, 6 | Injector, 7 | EmbeddedViewRef, 8 | EventEmitter, 9 | } from '@angular/core'; 10 | import { Subject } from 'rxjs'; 11 | 12 | import { HolderOptions } from './interfaces/holder.options'; 13 | import { Notify } from './interfaces/notify.type'; 14 | import { Options } from './interfaces/options.type'; 15 | import { NotifyEvent } from './interfaces/notify-event.type'; 16 | import { Icons, defaultIcons } from './interfaces/icons'; 17 | import { NotifyServiceConfig } from './notify.service.config'; 18 | import { NotifyComponent } from './notify.component'; 19 | import { NotifyHolderComponent } from './notify-holder.component'; 20 | 21 | @Injectable() 22 | export class NotifyService { 23 | private emitter: Subject = new Subject(); 24 | private icons: Icons = defaultIcons; 25 | 26 | private notifyHolderComponent: NotifyHolderComponent; 27 | 28 | private container: HTMLElement; 29 | 30 | constructor( 31 | private resolver: ComponentFactoryResolver, 32 | private applicationRef: ApplicationRef, 33 | private injector: Injector, 34 | @Optional() private config: NotifyServiceConfig, 35 | ) { 36 | if (config) { 37 | this.container = config.container; 38 | } 39 | } 40 | 41 | set(notify: Notify, to: boolean = true) { 42 | if (!this.notifyHolderComponent) { 43 | this.notifyHolderComponent = this.createNotifyHolder(); 44 | this.updateSetting({}); 45 | } 46 | 47 | if (to) { 48 | notify = Object.assign({}, this.config.notify, notify); 49 | notify.id = notify.id 50 | ? notify.id 51 | : Math.random() 52 | .toString(36) 53 | .substring(3); 54 | notify.click = new EventEmitter<{}>(); 55 | } 56 | 57 | this.emitter.next({ command: 'set', notify: notify, add: to }); 58 | return notify; 59 | } 60 | 61 | updateSetting(options: HolderOptions) { 62 | this.emitter.next({ 63 | command: 'options', 64 | holderOptions: Object.assign({}, this.config.options, options), 65 | }); 66 | } 67 | 68 | private createNotifyHolder(): NotifyHolderComponent { 69 | const factory = this.resolver.resolveComponentFactory( 70 | NotifyHolderComponent, 71 | ), 72 | ref = factory.create(this.injector), 73 | rootNode = (ref.hostView as EmbeddedViewRef) 74 | .rootNodes[0] as HTMLElement; 75 | 76 | if (!this.container) { 77 | this.container = document.body; 78 | } 79 | 80 | this.applicationRef.attachView(ref.hostView); 81 | ref.onDestroy(() => { 82 | this.applicationRef.detachView(ref.hostView); 83 | }); 84 | 85 | this.container.appendChild(rootNode); 86 | return ref.instance; 87 | } 88 | 89 | getChangeEmitter() { 90 | return this.emitter; 91 | } 92 | 93 | success(title: string, content?: string, override?: Options) { 94 | return this.set( 95 | Object.assign({}, override, { 96 | title: title, 97 | content: content || '', 98 | type: 'success', 99 | icon: this.icons.success, 100 | }), 101 | ); 102 | } 103 | 104 | error(title: string, content?: string, override?: Options) { 105 | return this.set( 106 | Object.assign({}, override, { 107 | title: title, 108 | content: content || '', 109 | type: 'error', 110 | icon: this.icons.error, 111 | }), 112 | ); 113 | } 114 | 115 | alert(title: string, content?: string, override?: Options) { 116 | return this.set( 117 | Object.assign({}, override, { 118 | title: title, 119 | content: content || '', 120 | type: 'alert', 121 | icon: this.icons.alert, 122 | }), 123 | ); 124 | } 125 | 126 | info(title: string, content?: string, override?: Options) { 127 | return this.set( 128 | Object.assign({}, override, { 129 | title: title, 130 | content: content || '', 131 | type: 'info', 132 | icon: this.icons.info, 133 | }), 134 | ); 135 | } 136 | 137 | bare(title: string, content: string, type: string, override?: Options) { 138 | return this.set( 139 | Object.assign({}, override, { 140 | title: title, 141 | content: content || '', 142 | type: type, 143 | }), 144 | ); 145 | } 146 | 147 | html( 148 | title: string, 149 | content: string, 150 | html: string, 151 | type?: string, 152 | override?: Options, 153 | ) { 154 | return this.set( 155 | Object.assign({}, override, { 156 | title: title, 157 | content: content || '', 158 | html: html, 159 | type: type, 160 | }), 161 | ); 162 | } 163 | 164 | remove(id: string) { 165 | this.emitter.next({ command: 'remove', id: id }); 166 | } 167 | 168 | clear() { 169 | this.emitter.next({ command: 'clear' }); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/components/notify.types.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from '@angular/core'; 2 | 3 | /** 4 | * 配置项 5 | */ 6 | export interface NotifyOptions { 7 | /** 8 | * 类型,默认:`success` 9 | */ 10 | type?: string; 11 | 12 | /** 13 | * 指定icon值 14 | */ 15 | icon?: string; 16 | /** 17 | * 标题 18 | */ 19 | title?: string; 20 | /** 21 | * 通知内容 22 | */ 23 | content?: string; 24 | 25 | /** 26 | * 位置,默认:`top-right` 27 | */ 28 | position?: string; 29 | 30 | /** 31 | * 偏移值,默认:`{ x: 20, y: 20 }` 32 | */ 33 | offset?: { x: number; y: number }; 34 | 35 | /** 36 | * z-index 值,默认:`1031` 37 | */ 38 | z_index?: number; 39 | 40 | /** 41 | * 进度条,默认:`false` 42 | */ 43 | progress?: boolean; 44 | 45 | /** 46 | * 链接地址,当存在时,点击进行跳转 47 | */ 48 | url?: string; 49 | 50 | /** 51 | * 链接目标 52 | */ 53 | target?: string; 54 | 55 | /** 56 | * 自定义模板代码,支持类似 C# String.format 字符串格式化。 57 | * - `{0}` 类型 58 | * - `{1}` 标题 59 | * - `{2}` 内容 60 | */ 61 | template?: string; 62 | 63 | /** 64 | * 显示时回调 65 | */ 66 | onShow?: Function; 67 | 68 | /** 69 | * 关闭时回调 70 | */ 71 | onClosed?: Function; 72 | 73 | click?: EventEmitter<{}>; 74 | } 75 | -------------------------------------------------------------------------------- /lib/components/push.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | Optional, 4 | ComponentFactoryResolver, 5 | ApplicationRef, 6 | Injector, 7 | EmbeddedViewRef, 8 | EventEmitter, 9 | } from '@angular/core'; 10 | import { Observable } from 'rxjs'; 11 | import { Permission, PushNotify } from './interfaces/push.type'; 12 | 13 | declare const Notification: any; 14 | 15 | @Injectable() 16 | export class NotifyPushService { 17 | permission: Permission; 18 | 19 | constructor() { 20 | this.permission = this.isSupported() ? Notification.permission : 'denied'; 21 | } 22 | 23 | requestPermission() { 24 | if (this.isSupported()) 25 | Notification.requestPermission( 26 | (status: any) => (this.permission = status), 27 | ); 28 | } 29 | 30 | isSupported() { 31 | return 'Notification' in window; 32 | } 33 | 34 | create(title: string, options?: PushNotify): any { 35 | return new Observable((obs: any) => { 36 | if (!this.isSupported()) { 37 | obs.error('不支持'); 38 | obs.complete(); 39 | } 40 | 41 | if (this.permission !== 'granted') { 42 | obs.error(`用户未授权`); 43 | obs.complete(); 44 | } 45 | 46 | const n = new Notification(title, options); 47 | 48 | n.onshow = (e: any) => obs.next({ notification: n, event: e }); 49 | n.onclick = (e: any) => obs.next({ notification: n, event: e }); 50 | n.onerror = (e: any) => obs.error({ notification: n, event: e }); 51 | n.onclose = () => obs.complete(); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './public_api'; 2 | -------------------------------------------------------------------------------- /lib/ngx-notify.module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NgModule, 3 | ModuleWithProviders, 4 | ComponentFactoryResolver, 5 | ApplicationRef, 6 | Injector, 7 | } from '@angular/core'; 8 | import { CommonModule } from '@angular/common'; 9 | 10 | import { NotifyPushService } from './components/push.service'; 11 | import { NotifyComponent } from './components/notify.component'; 12 | import { NotifyHolderComponent } from './components/notify-holder.component'; 13 | import { NotifyService } from './components/notify.service'; 14 | import { NotifyOptions } from './components/notify.types'; 15 | import { NotifyServiceConfig } from './components/notify.service.config'; 16 | 17 | @NgModule({ 18 | imports: [CommonModule], 19 | declarations: [NotifyHolderComponent, NotifyComponent], 20 | providers: [NotifyService, NotifyPushService], 21 | entryComponents: [NotifyHolderComponent, NotifyComponent], 22 | }) 23 | export class NotifyModule { 24 | static forRoot(config?: NotifyServiceConfig): ModuleWithProviders { 25 | return { 26 | ngModule: NotifyModule, 27 | providers: [{ provide: NotifyServiceConfig, useValue: config }], 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './components/interfaces/icons'; 2 | export * from './components/interfaces/notify-event.type'; 3 | export * from './components/interfaces/notify.type'; 4 | export * from './components/interfaces/options.type'; 5 | export * from './components/interfaces/holder.options'; 6 | export * from './components/interfaces/push.type'; 7 | export * from './components/notify.types'; 8 | export * from './components/notify-holder.component'; 9 | export * from './components/notify.component'; 10 | export * from './components/notify.service.config'; 11 | export * from './components/notify.service'; 12 | export * from './components/push.service'; 13 | export * from './ngx-notify.module'; 14 | -------------------------------------------------------------------------------- /lib/spec/notify.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | import { NotifyModule } from '../ngx-notify.module'; 5 | 6 | const html = ``; 7 | 8 | describe('Component: ngx-notify', () => { 9 | let fixture: ComponentFixture; 10 | let context: TestNGDragulaComponent; 11 | let element: any; 12 | let clean: any; 13 | 14 | beforeEach(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [TestNGDragulaComponent], 17 | imports: [NotifyModule], 18 | }); 19 | TestBed.overrideComponent(TestNGDragulaComponent, { 20 | set: { template: html }, 21 | }); 22 | fixture = TestBed.createComponent(TestNGDragulaComponent); 23 | context = fixture.componentInstance; 24 | element = fixture.nativeElement.querySelector('#c1'); 25 | clean = fixture.nativeElement.querySelector('#c2'); 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('fixture should not be null', () => { 30 | expect(fixture).not.toBeNull(); 31 | }); 32 | }); 33 | 34 | @Component({ 35 | selector: 'ngx-notify-test', 36 | template: '', 37 | }) 38 | class TestNGDragulaComponent {} 39 | -------------------------------------------------------------------------------- /lib/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting, 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting(), 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "es2015", 6 | "sourceMap": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "declaration": true, 11 | "outDir": "../release", 12 | "lib": [ 13 | "es2015", 14 | "dom" 15 | ] 16 | }, 17 | "files": [ 18 | "./index.ts" 19 | ], 20 | "angularCompilerOptions": { 21 | "skipTemplateCodegen": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts", 15 | "../src/polyfills.ts" 16 | ], 17 | "include": [ 18 | "**/*.spec.ts", 19 | "**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-notify", 3 | "version": "2.0.2", 4 | "main": "./bundles/notify.umd.js", 5 | "module": "./esm5/notify.js", 6 | "es2015": "./esm2015/notify.js", 7 | "typings": "./notify.d.ts", 8 | "description": "一个无须依赖HTML模板、极简Angular通知组件。", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/cipchk/ngx-notify.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "angular2", 16 | "ng2", 17 | "ngx-notify", 18 | "ng2-notify", 19 | "notify" 20 | ], 21 | "author": "cipchk ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/cipchk/ngx-notify/issues" 25 | }, 26 | "homepage": "https://github.com/cipchk/ngx-notify#readme", 27 | "scripts": { 28 | "analyze": "ng build --prod --build-optimizer --stats-json", 29 | "lint": "tslint -p tsconfig.json -c tslint.json 'lib/*/*.ts'", 30 | "test": "ng test --code-coverage", 31 | "site:build": "ng build --prod --build-optimizer", 32 | "site:gh": "ng build --prod --build-optimizer --base-href /ngx-notify/ && gh-pages -d ./dist", 33 | "build": "bash ./build.sh", 34 | "release:next": "bash ./build.sh && cd publish && npm publish --access public --tag next", 35 | "release": "bash ./build.sh && cd publish && npm publish --access public" 36 | }, 37 | "dependencies": { 38 | "@angular/animations": "^6.0.0", 39 | "@angular/common": "^6.0.0", 40 | "@angular/compiler": "^6.0.0", 41 | "@angular/core": "^6.0.0", 42 | "@angular/forms": "^6.0.0", 43 | "@angular/http": "^6.0.0", 44 | "@angular/platform-browser": "^6.0.0", 45 | "@angular/platform-browser-dynamic": "^6.0.0", 46 | "@angular/router": "^6.0.0", 47 | "core-js": "^2.5.4", 48 | "rxjs": "^6.0.0", 49 | "zone.js": "^0.8.26" 50 | }, 51 | "peerDependencies": {}, 52 | "devDependencies": { 53 | "@angular-devkit/build-angular": "~0.6.0", 54 | "@angular/cli": "~6.0.0", 55 | "@angular/compiler-cli": "^6.0.0", 56 | "@angular/language-service": "^6.0.0", 57 | "@types/jasmine": "~2.8.6", 58 | "@types/jasminewd2": "~2.0.3", 59 | "@types/node": "~8.9.4", 60 | "codecov": "^3.0.0", 61 | "codelyzer": "~4.2.1", 62 | "jasmine-core": "~2.99.1", 63 | "jasmine-spec-reporter": "~4.2.1", 64 | "karma": "~1.7.1", 65 | "karma-chrome-launcher": "~2.2.0", 66 | "karma-coverage-istanbul-reporter": "~1.4.2", 67 | "karma-jasmine": "~1.1.1", 68 | "karma-jasmine-html-reporter": "^0.2.2", 69 | "protractor": "~5.3.0", 70 | "rollup": "^0.49.2", 71 | "rollup-plugin-node-resolve": "^3.0.0", 72 | "rollup-plugin-replace": "^2.0.0", 73 | "rollup-plugin-sourcemaps": "^0.4.2", 74 | "rollup-plugin-uglify": "^2.0.1", 75 | "ts-node": "~5.0.1", 76 | "tslint": "~5.9.1", 77 | "typescript": "~2.7.2" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | beforeLaunch: function() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | }, 27 | onPrepare() { 28 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "rollup-plugin-node-resolve"; 2 | import uglify from "rollup-plugin-uglify"; 3 | import sourcemaps from "rollup-plugin-sourcemaps"; 4 | 5 | const target = process.env.ROLLUP_TARGET || "esm"; 6 | 7 | let globals = { 8 | "@angular/animations": "ng.animations", 9 | "@angular/core": "ng.core", 10 | "@angular/common": "ng.common", 11 | "@angular/forms": "ng.forms", 12 | "@angular/common/http": "ng.common.http", 13 | "@angular/router": "ng.router", 14 | "@angular/platform-browser": "ng.platformBrowser", 15 | "@angular/platform-server": "ng.platformServer", 16 | "@angular/platform-browser-dynamic": "ng.platformBrowserDynamic", 17 | "@angular/platform-browser/animations": "ng.platformBrowser.animations", 18 | "@angular/core/testing": "ng.core.testing", 19 | "@angular/common/testing": "ng.common.testing", 20 | "@angular/common/http/testing": "ng.common.http.testing", 21 | 22 | "rxjs": "rxjs", 23 | }; 24 | 25 | let plugins = [sourcemaps(), resolve()]; 26 | 27 | switch (target) { 28 | case "esm": 29 | Object.assign(globals, { 30 | tslib: "tslib" 31 | }); 32 | break; 33 | case "mumd": 34 | // @ts-ignore 35 | plugins.push(uglify()); 36 | break; 37 | } 38 | 39 | export default { 40 | exports: "named", 41 | name: "ngxNotify", 42 | plugins, 43 | external: Object.keys(globals), 44 | globals, 45 | output: { 46 | sourcemap: true 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /scripts/inline-template.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const glob = require('glob').sync 4 | const less = require('less'); 5 | 6 | function inlineResourcesForDirectory(folderPath) { 7 | return Promise.all(glob(path.join(folderPath, '**/*.ts')).map(filePath => inlineResources(filePath))) 8 | } 9 | 10 | function inlineResources(filePath) { 11 | return new Promise(resolve => { 12 | let fileContent = fs.readFileSync(filePath, 'utf-8') 13 | 14 | fileContent = inlineTemplate(fileContent, filePath) 15 | 16 | inlineLess(fileContent, filePath).then(res => { 17 | fs.writeFileSync(filePath, res, 'utf-8'); 18 | resolve(); 19 | }).catch(() => resolve()); 20 | }); 21 | } 22 | 23 | function inlineLess(fileContent, filePath) { 24 | return new Promise(resolve => { 25 | const re = /styleUrls\s*:\s*\[\s*'([^']+?\.less)'\s*\]/g; 26 | const matches = re.exec(fileContent); 27 | if (matches == null || matches.length <= 0) { 28 | resolve(fileContent); 29 | return ; 30 | } 31 | const templatePath = path.join(path.dirname(filePath), matches[1]); 32 | const templateContent = loadResourceFile(templatePath); 33 | less.render(templateContent, {}).then(lessResult => { 34 | const newContent = fileContent.replace(re, (_match, templateUrl) => `styles: [\`${lessResult.css}\`]`); 35 | resolve(newContent); 36 | }).catch(() => resolve(fileContent)); 37 | }); 38 | } 39 | 40 | function inlineTemplate(fileContent, filePath) { 41 | return fileContent.replace(/templateUrl\s*:\s*'([^']+?\.html)'/g, (_match, templateUrl) => { 42 | const templatePath = path.join(path.dirname(filePath), templateUrl) 43 | const templateContent = loadResourceFile(templatePath) 44 | return `template: \`${templateContent}\`` 45 | }); 46 | } 47 | 48 | function loadResourceFile(filePath) { 49 | return fs.readFileSync(filePath, 'utf-8').replace(/([\n\r]\s*)+/gm, ' ') 50 | } 51 | 52 | inlineResourcesForDirectory('./__gen_lib').then(); 53 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | template: ` 6 |

ngx-notify

7 |

一个无须依赖HTML模板、极简Angular通知组件。

8 | 9 | `, 10 | encapsulation: ViewEncapsulation.None 11 | }) 12 | export class AppComponent { 13 | } 14 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@angular/http'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | import { CommonModule } from '@angular/common'; 6 | 7 | import { NotifyModule } from 'ngx-notify'; 8 | 9 | import { AppComponent } from './app.component'; 10 | import { DemoComponent } from './components/demo.component'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | BrowserModule, 15 | FormsModule, 16 | HttpModule, 17 | CommonModule, 18 | 19 | NotifyModule.forRoot({ 20 | options: { 21 | }, 22 | notify: { 23 | progress: true 24 | } 25 | }) 26 | ], 27 | declarations: [ 28 | AppComponent, 29 | DemoComponent 30 | ], 31 | providers: [ ], 32 | bootstrap: [AppComponent] 33 | }) 34 | 35 | export class AppDemoModule { 36 | } 37 | -------------------------------------------------------------------------------- /src/app/components/demo.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Toast

4 |
5 |
6 |

Create

7 |
8 | 9 | 17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 | 35 | 36 |
37 |
38 |

Option

39 |
40 |
41 | 42 | 46 |
47 |
48 | 49 | 50 |
51 |
52 | 53 | 173 |
174 |
175 | 176 | 296 |
297 |
298 | 299 |
300 | 304 |
305 |
306 |
307 | 308 |
309 | 313 |
314 |
315 |
316 | 317 |
318 | 322 |
323 |
324 |
325 | 326 |
327 | 331 |
332 |
333 |
334 |
335 |
336 |

Notify Setting

337 |
338 |
339 | 340 | 344 |
345 |
346 | 347 |
348 | 349 |
350 | px 351 |
352 |
353 |
354 |
355 | 356 | 360 |
361 |
362 | 363 |
364 | 365 |
366 | px 367 |
368 |
369 |
370 |
371 | 372 |
373 | 374 |
375 | px 376 |
377 |
378 |
379 |
380 | 381 |
382 | 383 |
384 | px 385 |
386 |
387 |
388 |
389 | 390 | 391 |
392 |
393 | 394 |
395 | 399 |
400 |
401 |
402 | 403 |
404 |
405 |
406 |
407 |

Logs

408 |

你可以使用 `onCreate` 或 `onDestroy` 来跟踪所有创建与销毁的通知。

409 |
410 |
411 | {{log.title}} 412 | {{log.content}} 413 | CreatedOn: {{log.createdOn | date:'mediumTime'}} 414 | DestroyedOn: {{log.destroyedOn | date:'mediumTime'}} 415 |
416 |
417 |
418 |
419 |
420 |
421 |

Browser Push

422 |
423 |
424 |

Request Permission

425 |

使用前需要用户授权!

426 |

427 | this.notifyPushService.requestPermission() 428 |

429 | 430 |
431 |
432 |

Create

433 |

一旦用户授权,就可以使用下面代码发起浏览器通知。

434 |

435 | this.notifyPushService.create('未处理消息 10 条').subscribe(); 436 |

437 |
438 |
439 | 440 | 441 |
442 |
443 | 444 | 445 |
446 |
447 | 448 | 449 |
450 |
451 | 452 |
453 | 457 |
458 |
459 |
460 | 461 |
462 | 466 |
467 |
468 |
469 | 470 |
471 |
472 |
473 |
474 | -------------------------------------------------------------------------------- /src/app/components/demo.component.scss: -------------------------------------------------------------------------------- 1 | .logs { 2 | width: 100%; 3 | float: left; 4 | background: #f1f1f1; 5 | border: 1px solid #e1e1e1; 6 | border-radius: 4px; 7 | max-height: 400px; 8 | overflow-y: scroll; 9 | } 10 | 11 | .log { 12 | box-sizing: border-box; 13 | float: left; 14 | width: 100%; 15 | padding: 10px; 16 | color: #8bc34a; 17 | border-bottom: 1px solid #e1e1e1; 18 | } 19 | 20 | .log.destroyed { 21 | color: #f44336; 22 | } 23 | 24 | .notifies .notify.my-notify { 25 | border-left: 4px solid #689f38; 26 | .notify-title, 27 | .notify-content { 28 | font-size: 12px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/components/demo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import { NotifyService, NotifyPushService } from 'ngx-notify'; 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | @Component({ 7 | selector: 'demo', 8 | templateUrl: './demo.component.html', 9 | styleUrls: ['./demo.component.scss'], 10 | encapsulation: ViewEncapsulation.None, 11 | }) 12 | export class DemoComponent implements OnInit { 13 | item: any = { 14 | type: 'success', 15 | title: '这是标题', 16 | content: '这是内容', 17 | html: `
{title}
18 |
{content}
19 |
20 | `, 21 | }; 22 | logs: any[] = []; 23 | 24 | options: any = { 25 | progress: true, 26 | timeout: 1000 * 3, 27 | pauseOnHover: true, 28 | clickToClose: true, 29 | theme: 'default', 30 | rtl: false, 31 | className: 'my-notify', 32 | animate_in: 'notify-fade-in', 33 | animate_out: 'notify-fade-out', 34 | onCreate: item => { 35 | this.logs.push(item); 36 | }, 37 | onDestroy: item => { 38 | this.logs.push(item); 39 | }, 40 | }; 41 | 42 | setting: any = { 43 | position: ['right', 'bottom'], 44 | offset: [20, 20], 45 | lastOnBottom: true, 46 | zIndex: 1031, 47 | minWidth: 300, 48 | maxWidth: 300, 49 | }; 50 | 51 | constructor(private _ns: NotifyService, private _nps: NotifyPushService) {} 52 | 53 | onCreate() { 54 | const opt = Object.assign({}, this.options); 55 | if (this.item.type === 'html') { 56 | this._ns.html( 57 | this.item.title, 58 | this.item.content, 59 | this.item.html, 60 | 'success', 61 | opt, 62 | ); 63 | return; 64 | } 65 | opt.className = ''; 66 | this._ns[this.item.type](this.item.title, this.item.content, opt); 67 | } 68 | 69 | onSetting() { 70 | this._ns.success('操作', '保存成功', { timeout: 2000 }); 71 | this._ns.updateSetting(this.setting); 72 | } 73 | 74 | onClearAll() { 75 | this._ns.clear(); 76 | } 77 | 78 | public push: any = { 79 | title: '这是标题', 80 | body: '这是内容', 81 | icon: 'assets/logo.png', 82 | dir: 'auto', 83 | renotify: false, 84 | silent: true, 85 | sound: 'assets/system-fault.mp3', 86 | }; 87 | 88 | onRequestPermission() { 89 | this._nps.requestPermission(); 90 | } 91 | 92 | onPushCreate() { 93 | const opt = Object.assign({}, this.push); 94 | if (opt.renotify === true) opt.tag = '1'; 95 | 96 | this._nps.create(opt.title, opt).subscribe(); 97 | } 98 | 99 | ngOnInit() {} 100 | } 101 | -------------------------------------------------------------------------------- /src/assets/forkme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipchk/ngx-notify/4bf6ea60ef4f7125a0c55d36f58b7febec34ca77/src/assets/forkme.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipchk/ngx-notify/4bf6ea60ef4f7125a0c55d36f58b7febec34ca77/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/system-fault.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cipchk/ngx-notify/4bf6ea60ef4f7125a0c55d36f58b7febec34ca77/src/assets/system-fault.mp3 -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ngx-notify | 一个无须依赖HTML模板、极简Angular通知组件。 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 |
17 | Loading... 18 |
19 | 20 | 21 | Fork me on GitHub 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { environment } from './environments/environment'; 6 | import { AppDemoModule } from './app/app.module'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppDemoModule); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Required to support Web Animations `@angular/platform-browser/animations`. 51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by Angular itself. 59 | */ 60 | import 'zone.js/dist/zone'; // Included with Angular CLI. 61 | 62 | 63 | 64 | /*************************************************************************************************** 65 | * APPLICATION IMPORTS 66 | */ 67 | 68 | /** 69 | * Date, currency, decimal and percent pipes. 70 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 71 | */ 72 | // import 'intl'; // Run `npm install --save intl`. 73 | /** 74 | * Need to import at least one locale-data with intl. 75 | */ 76 | // import 'intl/locale-data/jsonp/en'; 77 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../out-tsc/app", 6 | "baseUrl": "./", 7 | "module": "es2015", 8 | "types": [], 9 | "paths": { 10 | "ngx-notify": ["../lib/index"], 11 | "ngx-notify/*": ["../lib/*"] 12 | } 13 | }, 14 | "exclude": ["test.ts", "**/*.spec.ts"] 15 | } 16 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | 7 | declare namespace jasmine { 8 | interface Matchers { 9 | toHaveCssClass(expected: any): boolean; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./publish/src", 5 | "baseUrl": "./", 6 | "declaration": true, 7 | "importHelpers": true, 8 | "module": "es2015", 9 | "sourceMap": true, 10 | "target": "es2015", 11 | "types": [ 12 | "node" 13 | ] 14 | }, 15 | "files": [ 16 | "./__gen_lib/public_api.ts" 17 | ], 18 | "angularCompilerOptions": { 19 | "annotateForClosureCompiler": true, 20 | "strictMetadataEmit": true, 21 | "skipTemplateCodegen": true, 22 | "flatModuleOutFile": "index.js", 23 | "flatModuleId": "ngx-notify" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es5", 11 | "typeRoots": ["node_modules/@types"], 12 | "lib": ["es2017", "dom"], 13 | "baseUrl": "./", 14 | "paths": { 15 | "ngx-notify": ["./lib/ngx-notify.module"], 16 | "ngx-notify/*": ["./lib/*"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["node_modules/codelyzer"], 3 | "rules": { 4 | "callable-types": true, 5 | "class-name": true, 6 | "comment-format": [true, "check-space"], 7 | "curly": false, 8 | "eofline": true, 9 | "forin": true, 10 | "import-blacklist": [true], 11 | "import-spacing": true, 12 | "indent": [true, "spaces"], 13 | "interface-over-type-literal": true, 14 | "label-position": true, 15 | "max-line-length": [false, 140], 16 | "member-access": false, 17 | "member-ordering": [false], 18 | "no-arg": true, 19 | "no-bitwise": true, 20 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 21 | "no-construct": true, 22 | "no-debugger": true, 23 | "no-duplicate-variable": true, 24 | "no-empty": false, 25 | "no-empty-interface": true, 26 | "no-eval": true, 27 | "no-inferrable-types": [true, "ignore-params"], 28 | "no-shadowed-variable": true, 29 | "no-string-literal": false, 30 | "no-string-throw": true, 31 | "no-switch-case-fall-through": true, 32 | "no-trailing-whitespace": true, 33 | "no-unused-expression": true, 34 | "no-use-before-declare": true, 35 | "no-var-keyword": true, 36 | "object-literal-sort-keys": false, 37 | "one-line": [ 38 | false, 39 | "check-open-brace", 40 | "check-catch", 41 | "check-else", 42 | "check-whitespace" 43 | ], 44 | "prefer-const": true, 45 | "quotemark": [true, "single"], 46 | "radix": true, 47 | "semicolon": [true, "always"], 48 | "triple-equals": [true, "allow-null-check"], 49 | "typedef-whitespace": [ 50 | true, 51 | { 52 | "call-signature": "nospace", 53 | "index-signature": "nospace", 54 | "parameter": "nospace", 55 | "property-declaration": "nospace", 56 | "variable-declaration": "nospace" 57 | } 58 | ], 59 | "typeof-compare": true, 60 | "unified-signatures": true, 61 | "variable-name": false, 62 | "whitespace": [ 63 | true, 64 | "check-branch", 65 | "check-decl", 66 | "check-operator", 67 | "check-separator", 68 | "check-type" 69 | ], 70 | 71 | "directive-selector": [false, "attribute", "app", "camelCase"], 72 | "component-selector": [false, "element", "app", "kebab-case"], 73 | "use-input-property-decorator": true, 74 | "use-output-property-decorator": true, 75 | "use-host-property-decorator": true, 76 | "no-input-rename": true, 77 | "no-output-rename": true, 78 | "use-life-cycle-interface": true, 79 | "use-pipe-transform-interface": true, 80 | "component-class-suffix": true, 81 | "directive-class-suffix": true, 82 | "no-access-missing-member": true, 83 | "templates-use-public": true, 84 | "invoke-injectable": true 85 | }, 86 | "linterOptions": { 87 | "exclude": [ 88 | "lib/**/*.spec.ts" 89 | ] 90 | } 91 | } 92 | --------------------------------------------------------------------------------