Bladeren bron

Notification: add position

Leopoldthecoder 8 jaren geleden
bovenliggende
commit
c340ed13e4

+ 149 - 8
examples/docs/en-US/notification.md

@@ -49,13 +49,52 @@
       },
 
       open7() {
-        this.$notify.success({
+        this.$notify({
+          title: 'Custom Position',
+          message: 'I\'m at the top right corner'
+        });
+      },
+
+      open8() {
+        this.$notify({
+          title: 'Custom Position',
+          message: 'I\'m at the bottom right corner',
+          position: 'bottom-right'
+        });
+      },
+
+      open9() {
+        this.$notify({
+          title: 'Custom Position',
+          message: 'I\'m at the bottom left corner',
+          position: 'bottom-left'
+        });
+      },
+
+      open10() {
+        this.$notify({
+          title: 'Custom Position',
+          message: 'I\'m at the top left corner',
+          position: 'top-left'
+        });
+      },
+
+      open11() {
+        this.$notify({
           title: 'Success',
           message: 'This is a success message',
           offset: 100
         });
       },
 
+      open12() {
+        this.$notify({
+          title: 'HTML String',
+          dangerouslyUseHTMLString: true,
+          message: '<strong>This is <i>HTML</i> string</strong>'
+        });
+      },
+
       onClose() {
         console.log('Notification is closed');
       }
@@ -65,7 +104,7 @@
 
 ## Notification
 
-Displays a global notification message at the upper right corner of the page.
+Displays a global notification message at a corner of the page.
 
 ### Basic usage
 
@@ -177,16 +216,84 @@ We provide four types: success, warning, info and error.
 ```
 :::
 
+### Custom Position
+
+Notification can emerge from any corner you like.
+
+::: demo The `position` attribute defines which corner Notification slides in. It can be `top-right`, `top-left`, `bottom-right` or `bottom-left`. Defaults to `top-right`.
+```html
+<template>
+  <el-button
+    plain
+    @click="open7">
+    Top Right
+  </el-button>
+  <el-button
+    plain
+    @click="open8">
+    Bottom Right
+  </el-button>
+  <el-button
+    plain
+    @click="open9">
+    Bottom Left
+  </el-button>
+  <el-button
+    plain
+    @click="open10">
+    Top Left
+  </el-button>
+</template>
+
+<script>
+  export default {
+    methods: {
+      open7() {
+        this.$notify({
+          title: 'Custom Position',
+          message: 'I\'m at the top right corner'
+        });
+      },
+
+      open8() {
+        this.$notify({
+          title: 'Custom Position',
+          message: 'I\'m at the bottom right corner',
+          position: 'bottom-right'
+        });
+      },
+
+      open9() {
+        this.$notify({
+          title: 'Custom Position',
+          message: 'I\'m at the bottom left corner',
+          position: 'bottom-left'
+        });
+      },
+
+      open10() {
+        this.$notify({
+          title: 'Custom Position',
+          message: 'I\'m at the top left corner',
+          position: 'top-left'
+        });
+      }
+    }
+  }
+</script>
+```
+:::
+
 ### With offset
 
-Customize Notification's offset from the top edge of the screen
+Customize Notification's offset from the edge of the screen.
 
-::: demo Set the `offset` attribute to customize Notification's offset from the top edge of the screen. Note that every Notification instance of the same moment should have the same offset.
+::: demo Set the `offset` attribute to customize Notification's offset from the edge of the screen. Note that every Notification instance of the same moment should have the same offset.
 ```html
 <template>
   <el-button
-      plain
-      @click="open7">
+    plain
+    @click="open11">
     Notification with offset
   </el-button>
 </template>
@@ -194,8 +301,8 @@ Customize Notification's offset from the top edge of the screen
 <script>
   export default {
     methods: {
-      open7() {
-        this.$notify.success({
+      open11() {
+        this.$notify({
           title: 'Success',
           message: 'This is a success message',
           offset: 100
@@ -207,6 +314,39 @@ Customize Notification's offset from the top edge of the screen
 ```
 :::
 
+### Use HTML String
+`message` supports HTML string.
+
+::: demo Set `dangerouslyUseHTMLString` to true and `message` will be treated as an HTML string.
+```html
+<template>
+  <el-button
+    plain
+    @click="open12">
+    Use HTML String
+  </el-button>
+</template>
+
+<script>
+  export default {
+    methods: {
+      open12() {
+        this.$notify({
+          title: 'HTML String',
+          dangerouslyUseHTMLString: true,
+          message: '<strong>This is <i>HTML</i> string</strong>'
+        });
+      }
+    }
+  }
+</script>
+```
+:::
+
+:::warning
+Although `message` property supports HTML strings, dynamically rendering arbitrary HTML on your website can be very dangerous because it can easily lead to [XSS attacks](https://en.wikipedia.org/wiki/Cross-site_scripting). So when `dangerouslyUseHTMLString` is on, please make sure the content of `message` is trusted, and **never** assign `message` to user-provided content.
+:::
+
 ### Global method
 
 Element has added a global method `$notify` for Vue.prototype. So in a vue instance you can call `Notification` like what we did in this page.
@@ -226,6 +366,7 @@ In this case you should call `Notification(options)`. We have also registered me
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | title | title | string | — | — |
 | message | description text | string/Vue.VNode | — | — |
+| dangerouslyUseHTMLString | whether `message` is treated as HTML string | boolean | — | false |
 | type | notification type | string | success/warning/info/error | — |
 | iconClass | custom icon's class. It will be overridden by `type` | string | — | — |
 | customClass | custom class name for Notification | string | — | — |

+ 151 - 10
examples/docs/zh-CN/notification.md

@@ -49,13 +49,52 @@
       },
 
       open7() {
-        this.$notify.success({
-          title: '成功',
-          message: '这是一条成功的提示消息',
+        this.$notify({
+          title: '自定义位置',
+          message: '右上角弹出的消息'
+        });
+      },
+
+      open8() {
+        this.$notify({
+          title: '自定义位置',
+          message: '右下角弹出的消息',
+          position: 'bottom-right'
+        });
+      },
+
+      open9() {
+        this.$notify({
+          title: '自定义位置',
+          message: '左下角弹出的消息',
+          position: 'bottom-left'
+        });
+      },
+
+      open10() {
+        this.$notify({
+          title: '自定义位置',
+          message: '左上角弹出的消息',
+          position: 'top-left'
+        });
+      },
+
+      open11() {
+        this.$notify({
+          title: '偏移',
+          message: '这是一条带有偏移的提示消息',
           offset: 100
         });
       },
 
+      open12() {
+        this.$notify({
+          title: 'HTML 片段',
+          dangerouslyUseHTMLString: true,
+          message: '<strong>这是 <i>HTML</i> 片段</strong>'
+        });
+      },
+
       onClose() {
         console.log('Notification 已关闭');
       }
@@ -65,7 +104,7 @@
 
 ## Notification 通知
 
-悬浮出现在页面右上角,显示全局的通知提醒消息。
+悬浮出现在页面角,显示全局的通知提醒消息。
 
 ### 基本用法
 
@@ -178,16 +217,84 @@
 ```
 :::
 
+### 自定义弹出位置
+
+可以让 Notification 从屏幕四角中的任意一角弹出
+
+::: demo 使用`position`属性定义 Notification 的弹出位置,支持四个选项:`top-right`、`top-left`、`bottom-right`、`bottom-left`,默认为`top-right`。
+```html
+<template>
+  <el-button
+    plain
+    @click="open7">
+    右上角
+  </el-button>
+  <el-button
+    plain
+    @click="open8">
+    右下角
+  </el-button>
+  <el-button
+    plain
+    @click="open9">
+    左下角
+  </el-button>
+  <el-button
+    plain
+    @click="open10">
+    左上角
+  </el-button>
+</template>
+
+<script>
+  export default {
+    methods: {
+      open7() {
+        this.$notify({
+          title: '自定义位置',
+          message: '右上角弹出的消息'
+        });
+      },
+
+      open8() {
+        this.$notify({
+          title: '自定义位置',
+          message: '右下角弹出的消息',
+          position: 'bottom-right'
+        });
+      },
+
+      open9() {
+        this.$notify({
+          title: '自定义位置',
+          message: '左下角弹出的消息',
+          position: 'bottom-left'
+        });
+      },
+
+      open10() {
+        this.$notify({
+          title: '自定义位置',
+          message: '左上角弹出的消息',
+          position: 'top-left'
+        });
+      }
+    }
+  }
+</script>
+```
+:::
+
 ### 带有偏移
 
 让 Notification 偏移一些位置
 
-::: demo Notification 提供设置偏移量的功能,通过设置 `offset` 字段,可以使弹出的消息距屏幕顶部偏移一段距离。注意在同一时刻,所有的 Notification 实例应当具有一个相同的偏移量。
+::: demo Notification 提供设置偏移量的功能,通过设置 `offset` 字段,可以使弹出的消息距屏幕边缘偏移一段距离。注意在同一时刻,所有的 Notification 实例应当具有一个相同的偏移量。
 ```html
 <template>
   <el-button
     plain
-    @click="open7">
+    @click="open11">
     偏移的消息
   </el-button>
 </template>
@@ -195,10 +302,10 @@
 <script>
   export default {
     methods: {
-      open7() {
-        this.$notify.success({
-          title: '成功',
-          message: '这是一条成功的提示消息',
+      open11() {
+        this.$notify({
+          title: '偏移',
+          message: '这是一条带有偏移的提示消息',
           offset: 100
         });
       }
@@ -208,6 +315,39 @@
 ```
 :::
 
+### 使用 HTML 片段
+`message` 属性支持传入 HTML 片段
+
+::: demo 将`dangerouslyUseHTMLString`属性设置为 true,`message` 就会被当作 HTML 片段处理。
+```html
+<template>
+  <el-button
+    plain
+    @click="open12">
+    使用 HTML 片段
+  </el-button>
+</template>
+
+<script>
+  export default {
+    methods: {
+      open12() {
+        this.$notify({
+          title: 'HTML 片段',
+          dangerouslyUseHTMLString: true,
+          message: '<strong>这是 <i>HTML</i> 片段</strong>'
+        });
+      }
+    }
+  }
+</script>
+```
+:::
+
+:::warning
+`message` 属性虽然支持传入 HTML 片段,但是在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 [XSS 攻击](https://en.wikipedia.org/wiki/Cross-site_scripting)。因此在 `dangerouslyUseHTMLString` 打开的情况下,请确保 `message` 的内容是可信的,**永远不要**将用户提交的内容赋值给 `message` 属性。
+:::
+
 ### 全局方法
 
 Element 为 `Vue.prototype` 添加了全局方法 `$notify`。因此在 vue instance 中可以采用本页面中的方式调用 Notification。
@@ -227,6 +367,7 @@ import { Notification } from 'element-ui';
 |---------- |-------------- |---------- |--------------------------------  |-------- |
 | title | 标题 | string | — | — |
 | message | 说明文字 | string/Vue.VNode | — | — |
+| dangerouslyUseHTMLString | 是否将 message 属性作为 HTML 片段处理 | boolean | — | false |
 | type | 主题样式,如果不在可选值内将被忽略 | string | success/warning/info/error | — |
 | iconClass | 自定义图标的类名。若设置了 `type`,则 `iconClass` 会被覆盖 | string | — | — |
 | customClass | 自定义类名 | string | — | — |

+ 30 - 24
packages/notification/src/main.js

@@ -1,17 +1,18 @@
 import Vue from 'vue';
 import { PopupManager } from 'element-ui/src/utils/popup';
 import { isVNode } from 'element-ui/src/utils/vdom';
-let NotificationConstructor = Vue.extend(require('./main.vue'));
+const NotificationConstructor = Vue.extend(require('./main.vue'));
 
 let instance;
 let instances = [];
 let seed = 1;
 
-var Notification = function(options) {
+const Notification = function(options) {
   if (Vue.prototype.$isServer) return;
   options = options || {};
-  let userOnClose = options.onClose;
-  let id = 'notification_' + seed++;
+  const userOnClose = options.onClose;
+  const id = 'notification_' + seed++;
+  const position = options.position || 'top-right';
 
   options.onClose = function() {
     Notification.close(id, userOnClose);
@@ -32,13 +33,12 @@ var Notification = function(options) {
   instance.dom = instance.vm.$el;
   instance.dom.style.zIndex = PopupManager.nextZIndex();
 
-  const offset = options.offset || 0;
-  let topDist = offset;
-  for (let i = 0, len = instances.length; i < len; i++) {
-    topDist += instances[i].$el.offsetHeight + 16;
-  }
-  topDist += 16;
-  instance.top = topDist;
+  let verticalOffset = options.offset || 0;
+  instances.filter(item => item.position === position).forEach(item => {
+    verticalOffset += item.$el.offsetHeight + 16;
+  });
+  verticalOffset += 16;
+  instance.verticalOffset = verticalOffset;
   instances.push(instance);
   return instance.vm;
 };
@@ -56,23 +56,29 @@ var Notification = function(options) {
 });
 
 Notification.close = function(id, userOnClose) {
-  let index;
-  let removedHeight;
-  for (var i = 0, len = instances.length; i < len; i++) {
-    if (id === instances[i].id) {
-      if (typeof userOnClose === 'function') {
-        userOnClose(instances[i]);
-      }
+  let index = -1;
+  const len = instances.length;
+  const instance = instances.filter((instance, i) => {
+    if (instance.id === id) {
       index = i;
-      removedHeight = instances[i].dom.offsetHeight;
-      instances.splice(i, 1);
-      break;
+      return true;
     }
+    return false;
+  })[0];
+  if (!instance) return;
+
+  if (typeof userOnClose === 'function') {
+    userOnClose(instance);
   }
+  instances.splice(index, 1);
 
-  if (len > 1) {
-    for (i = index; i < len - 1 ; i++) {
-      instances[i].dom.style.top = parseInt(instances[i].dom.style.top, 10) - removedHeight - 16 + 'px';
+  if (len <= 1) return;
+  const position = instance.position;
+  const removedHeight = instance.dom.offsetHeight;
+  for (let i = index; i < len - 1 ; i++) {
+    if (instances[i].position === position) {
+      instances[i].dom.style[instance.verticalProperty] =
+        parseInt(instances[i].dom.style[instance.verticalProperty], 10) - removedHeight - 16 + 'px';
     }
   }
 };

+ 26 - 6
packages/notification/src/main.vue

@@ -1,10 +1,9 @@
 <template>
   <transition name="el-notification-fade">
     <div
-      class="el-notification"
-      :class="customClass"
+      :class="['el-notification', customClass, horizontalClass]"
       v-show="visible"
-      :style="{ top: top ? top + 'px' : 'auto' }"
+      :style="positionStyle"
       @mouseenter="clearTimer()"
       @mouseleave="startTimer()"
       @click="click">
@@ -15,7 +14,12 @@
       </i>
       <div class="el-notification__group" :class="{ 'is-with-icon': typeClass || iconClass }">
         <h2 class="el-notification__title" v-text="title"></h2>
-        <div class="el-notification__content"><slot>{{ message }}</slot></div>
+        <div class="el-notification__content">
+          <slot>
+            <p v-if="!dangerouslyUseHTMLString">{{ message }}</p>
+            <p v-else v-html="message"></p>
+          </slot>
+        </div>
         <div class="el-notification__closeBtn el-icon-close" @click.stop="close"></div>
       </div>
     </div>
@@ -43,14 +47,30 @@
         onClose: null,
         onClick: null,
         closed: false,
-        top: null,
-        timer: null
+        verticalOffset: 0,
+        timer: null,
+        dangerouslyUseHTMLString: false,
+        position: 'top-right'
       };
     },
 
     computed: {
       typeClass() {
         return this.type && typeMap[this.type] ? `el-icon-${ typeMap[this.type] }` : '';
+      },
+
+      horizontalClass() {
+        return this.position.indexOf('right') > -1 ? 'right' : 'left';
+      },
+
+      verticalProperty() {
+        return /^top-/.test(this.position) ? 'top' : 'bottom';
+      },
+
+      positionStyle() {
+        return {
+          [this.verticalProperty]: `${ this.verticalOffset }px`
+        };
       }
     },
 

+ 22 - 4
packages/theme-default/src/notification.css

@@ -9,12 +9,19 @@
     box-sizing: border-box;
     border-radius: var(--border-radius-small);
     position: fixed;
-    right: 16px;
     background-color: var(--color-white);
     box-shadow: var(--notification-shadow);
-    transition: opacity 0.3s, transform .3s, right .3s, top 0.4s;
+    transition: opacity .3s, transform .3s, left .3s, right .3s, top 0.4s, bottom .3s;
     overflow: hidden;
 
+    &.right {
+      right: 16px;
+    }
+
+    &.left {
+      left: 16px;
+    }
+
     @e group {
       margin-left: 0;
       @when with-icon {
@@ -35,6 +42,10 @@
       margin: 10px 0 0 0;
       color: var(--notification-color);
       text-align: justify;
+
+      p {
+        margin: 0;
+      }
     }
 
     @e icon {
@@ -74,8 +85,15 @@
   }
 
   .el-notification-fade-enter {
-    transform: translateX(100%);
-    right: 0;
+    &.right {
+      right: 0;
+      transform: translateX(100%);
+    }
+
+    &.left {
+      left: 0;
+      transform: translateX(-100%);
+    }
   }
 
   .el-notification-fade-leave-active {

+ 12 - 1
test/unit/specs/notification.spec.js

@@ -47,12 +47,23 @@ describe('Notification', () => {
     });
     const group = document.querySelector('.el-notification__group');
     const title = group.querySelector('.el-notification__title');
-    const message = group.querySelector('.el-notification__content');
+    const message = group.querySelector('.el-notification__content p');
     expect(document.querySelector('.el-notification')).to.exist;
     expect(title.textContent).to.equal('狮子');
     expect(message.textContent).to.equal('狮鹫');
   });
 
+  it('html string as message', () => {
+    Notification({
+      title: '狮子',
+      message: '<strong>狮鹫</strong>',
+      dangerouslyUseHTMLString: true,
+      duration: 0
+    });
+    const message = document.querySelector('.el-notification__content strong');
+    expect(message.textContent).to.equal('狮鹫');
+  });
+
   it('create by vnode', () => {
     const fakeVM = new Vue();
     const h = fakeVM.$createElement;