├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── rollup.config.js
├── src
├── App.vue
├── components
│ └── Clamp.js
├── main.js
└── vue-lang.js
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // http://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | parserOptions: {
6 | parser: 'babel-eslint',
7 | sourceType: 'module'
8 | },
9 | extends: [
10 | // https://github.com/vuejs/eslint-plugin-vue#bulb-rules
11 | 'plugin:vue/essential',
12 | 'plugin:vue/recommended',
13 | 'plugin:vue/strongly-recommended',
14 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md
15 | 'standard',
16 | 'prettier/standard',
17 | 'prettier/vue'
18 | ],
19 | // required to lint *.vue files
20 | plugins: ['vue'],
21 | // add your custom rules here
22 | rules: {
23 | // allow paren-less arrow functions
24 | 'arrow-parens': 0,
25 | // allow async-await
26 | 'generator-star-spacing': 0,
27 | // allow debugger during development
28 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
29 | 'no-multi-spaces': ['error', { ignoreEOLComments: true }],
30 | 'no-template-curly-in-string': 0,
31 | // to many false positives
32 | 'vue/no-side-effects-in-computed-properties': 0,
33 | // fix unused var error for JSX custom tags
34 | 'vue/jsx-uses-vars': 2,
35 | 'vue/require-default-prop': 0,
36 | 'vue/name-property-casing': ['error', 'kebab-case'],
37 | 'vue/component-name-in-template-casing': ['error', 'kebab-case'],
38 | 'vue/html-indent': [
39 | 'error',
40 | 2,
41 | {
42 | attribute: 1,
43 | baseIndent: 0,
44 | closeBracket: 0,
45 | alignAttributesVertically: true
46 | }
47 | ],
48 | 'vue/html-self-closing': [
49 | 'error',
50 | {
51 | html: {
52 | void: 'never',
53 | normal: 'always',
54 | component: 'always'
55 | },
56 | svg: 'always',
57 | math: 'always'
58 | }
59 | ],
60 | 'vue/html-closing-bracket-spacing': [
61 | 'error',
62 | {
63 | startTag: 'never',
64 | endTag: 'never',
65 | selfClosingTag: 'never'
66 | }
67 | ],
68 | 'vue/no-v-html': 0
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 | /demo
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw*
23 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .*
2 | *.config.js
3 | /demo
4 | /public
5 | /src
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.4.1
2 |
3 | * Fix clamped text for `location="start"`.
4 | * Text content is now always be trimmed.
5 |
6 | ## 0.4.0
7 |
8 | * Add `location` prop. (#66)
9 | * Add ESM output and no longer require users to transpile.
10 | * Externalize `resize-detector` to reduce bundle size.
11 |
12 | ## 0.3.2
13 |
14 | * Fix the problem caused by array spread. (#47)
15 |
16 | ## 0.3.1
17 |
18 | * Fix SSR support.
19 | * Fix RTL support.
20 |
21 | ## 0.3.0
22 |
23 | * Add `clampchange` event.
24 |
25 | ## 0.2.2
26 |
27 | * Preserve at lease a single line of content when even a single line would exceeds `max-height`.
28 |
29 | ## 0.2.1
30 |
31 | * Update layout when clamp status has been changed.
32 |
33 | ## 0.2.0
34 |
35 | * Add `clamped: boolean` and `expanded: boolean` to scoped slot `before`/`after`.
36 | * Fix content extraction.
37 |
38 | ## 0.1.0
39 |
40 | * First release.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 GU Yiling
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.md:
--------------------------------------------------------------------------------
1 | # <vue-clamp>
2 |
3 | Clamping multiline text with ease.
4 |
5 | See more in our [docs & demo](https://vue-clamp.vercel.app).
6 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-clamp",
3 | "version": "0.4.1",
4 | "description": "Clamping multiline text with ease.",
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "NODE_ENV=production rollup -c",
8 | "lint": "vue-cli-service lint",
9 | "build:demo": "vue-cli-service build",
10 | "prepublishOnly": "npm run build"
11 | },
12 | "main": "dist/vue-clamp.js",
13 | "module": "dist/vue-clamp.esm.js",
14 | "dependencies": {
15 | "resize-detector": "^0.3.0"
16 | },
17 | "devDependencies": {
18 | "@rollup/plugin-buble": "^0.21.3",
19 | "@rollup/plugin-node-resolve": "^7.1.3",
20 | "@vue/cli-plugin-babel": "^4.5.13",
21 | "@vue/cli-plugin-eslint": "^4.5.13",
22 | "@vue/cli-service": "^4.4.6",
23 | "@vue/eslint-config-standard": "^4.0.0",
24 | "babel-eslint": "^10.1.0",
25 | "core-js": "^3.6.5",
26 | "eslint": "^6.8.0",
27 | "eslint-config-prettier": "^6.11.0",
28 | "eslint-config-standard": "^14.1.1",
29 | "eslint-plugin-vue": "^6.2.2",
30 | "highlight.js": "^9.18.3",
31 | "prettier": "^2.0.5",
32 | "prettier-eslint": "^9.0.2",
33 | "qs": "^6.9.4",
34 | "rollup": "^2.23.0",
35 | "rollup-plugin-terser": "^5.3.0",
36 | "rollup-plugin-vue": "^5.1.9",
37 | "spectre.css": "^0.5.9",
38 | "stylus": "^0.54.8",
39 | "stylus-loader": "^3.0.2",
40 | "vue": "^2.6.11",
41 | "vue-template-compiler": "^2.6.11"
42 | },
43 | "peerDependencies": {
44 | "vue": "^2.5.17"
45 | },
46 | "bugs": {
47 | "url": "https://github.com/Justineo/vue-clamp/issues"
48 | },
49 | "homepage": "https://justineo.github.io/vue-clamp/demo/",
50 | "license": "MIT",
51 | "repository": {
52 | "type": "git",
53 | "url": "git+https://github.com/Justineo/vue-clamp.git"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Justineo/vue-clamp/cc04a308e060cd5f230b23b7569cc07707112e4b/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-clamp
9 |
10 |
11 |
12 | We're sorry but vue-clamp doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import vue from 'rollup-plugin-vue'
2 | import buble from '@rollup/plugin-buble'
3 | import { terser } from 'rollup-plugin-terser'
4 | import resolve from '@rollup/plugin-node-resolve'
5 |
6 | export default [{
7 | input: 'src/components/Clamp.js',
8 | output: {
9 | file: 'dist/vue-clamp.js',
10 | name: 'VueClamp',
11 | format: 'umd',
12 | globals: {
13 | vue: 'Vue',
14 | 'resize-detector': 'resizeDetector'
15 | }
16 | },
17 | external: [
18 | 'vue',
19 | 'resize-detector'
20 | ],
21 | plugins: [
22 | resolve(),
23 | vue(),
24 | buble(),
25 | terser()
26 | ]
27 | }, {
28 | input: 'src/components/Clamp.js',
29 | output: {
30 | file: 'dist/vue-clamp.esm.js',
31 | format: 'es'
32 | },
33 | external: [
34 | 'vue',
35 | 'resize-detector'
36 | ],
37 | plugins: [
38 | resolve(),
39 | vue(),
40 | buble(),
41 | terser()
42 | ]
43 | }]
44 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | <{{ pascal ? 'VueClamp' : 'vue-clamp' }}>
4 |
5 | English
6 | 中文
7 |
8 | {{ zh ? '轻松实现多行文本截断。' : 'Clamping multiline text with ease.' }}
9 |
10 | GitHub →
15 |
16 |
17 | #
18 | {{ zh ? '功能' : 'Features' }}
19 |
20 |
21 | {{ zh ? '可以选择限制行数与/或最大高度,无需指定行高。' : 'Clamps text with max lines and/or max height. No need to specify line height.' }}
22 | {{ zh ? '支持在布局变化时自动更新。' : 'Automatically updates upon layout change.' }}
23 | {{ zh ? '支持展开/收起被截断部分内容。' : 'The clamped text can be expanded/collapsed.' }}
24 | {{ zh ? '支持自定义截断文本前后内容,并且进行响应式更新。' : 'Customizable and responsive content before/after clamped text.' }}
25 | {{ zh ? '支持在文本末尾、中间或开始位置进行截断。' : 'Place elllipsis at the end, middle, or the start of the clamped text.' }}
26 |
27 |
28 | # Demo
29 |
30 |
31 |
32 |
73 |
85 | {{ zh ? textZh : text }}
86 |
87 | {{ zh ? '切换' : 'Toggle' }}
92 |
93 |
94 |
95 |
96 |
97 |
142 |
155 | {{ zh ? textZh : text }}
156 |
157 | {{ zh ? '推荐' : 'Featured' }}
158 |
159 |
160 |
161 |
162 |
163 |
204 | {{ zh ? textZh : text }}
217 | {{ zh ? '截断状态:' + (clamped3 ? '已截断' : '未截断') : 'Clamped: ' + (clamped3 ? 'Yes' : 'No')}}
220 |
221 |
222 |
223 |
299 |
313 | {{ zh ? textZh : text }}
314 |
315 | {{ zh ? '切换' : 'Toggle' }}
320 |
321 |
322 |
323 | #
324 | {{ zh ? '使用方法' : 'Usage' }}
325 |
326 |
327 | $ npm i --save vue-clamp
328 |
329 | <template>
330 | <v-clamp autoresize :max-lines="3">{{ text }}</v-clamp>
331 | </template>
332 |
333 | <script>
334 | import VClamp from 'vue-clamp'
335 |
336 | export default {
337 | components: {
338 | VClamp
339 | },
340 | data () {
341 | return {
342 | text: 'Some very very long text content.'
343 | }
344 | }
345 | }
346 | </script>
347 |
348 |
349 | # API
350 |
351 |
352 |
353 |
354 |
355 |
356 | tag: string
357 |
358 | {{ zh ? '生成的根元素的标签名。' : 'The tag name of the generated root element.' }}
359 |
360 | {{ defaultText }}
361 | div
362 |
363 |
364 |
365 |
366 | autoresize: boolean
367 |
368 | {{ zh ? '是否要自动适配根元素的尺寸变化。' : 'Whether to observe the root element\'s size.' }}
369 |
370 | {{ defaultText }}
371 | false
372 |
373 |
374 |
375 |
376 | max-lines: number
377 |
378 | {{ zh ? '可以显示的最大行数' : 'The max number of lines that can be displayed.' }}
379 |
380 |
381 |
382 | max-height: number | string
383 |
384 |
385 | 根元素的最大高度。数字值将被转换为
386 | px
单位;字符串值将直接作为 CSS 属性
387 | max-height
输出。
388 |
389 |
390 | The max height of the root element. Number values are converted to
391 | px
units. String values are used directly as the
392 | max-height
CSS property.
393 |
394 |
395 |
396 |
397 | ellipsis: string
398 |
399 | {{ zh ? '当文字被截断时需要显示的省略号字符串。' : 'The ellipsis characters displayed when the text is clamped.' }}
400 |
401 | {{ defaultText }}
402 | '…'
403 |
404 |
405 |
406 |
407 | location: 'start' | 'middle' | 'end'
408 |
409 | {{ zh ? '截断后显式省略符号的位置。' : 'The location of the ellipsis.' }}
410 |
411 | {{ defaultText }}
412 | 'end'
413 |
414 |
415 |
416 |
417 | expanded: boolean
418 |
419 |
420 | .sync
424 |
425 | {{ zh ? '是否展开显式被截断的文本。' : 'Whether the clamped area is expanded.' }}
426 |
427 | {{ defaultText }}
428 | false
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 | default
439 |
440 | {{ zh ? '需要截断的文本。只能包含纯文本内容。' : 'The text to clamp. Can only contain pure text.' }}
441 |
442 |
443 |
444 | before
445 |
446 |
447 | Slot {{ zh ? '作用域:' : 'scope:' }}
448 | { expand, collapse, toggle, clamped, expanded }
449 |
450 |
451 |
452 | expand: function(): void
453 | - {{ zh ? '展开被截断的文本。' : 'Expand the clamped text.' }}
454 |
455 |
456 | collapse: function(): void
457 | - {{ zh ? '收起展开后的文本。' : 'Collapse the expanded text.' }}
458 |
459 |
460 | toggle: function(): void
461 | - {{ zh ? '切换被截断文本的展开状态。' : 'Toggle the expand state of clamped text.' }}
462 |
463 |
464 | clamped: Boolean
465 | - {{ zh ? '内容是否处于截断状态。' : 'Whether text content is being clamped.' }}
466 |
467 |
468 | expanded: Boolean
469 | - {{ zh ? '内容是否处于展开状态。' : 'Whether text content is being expanded.' }}
470 |
471 |
472 | {{ zh ? '在被截断的文本前显式的内容,可以包含任意类型内容。' : 'Content displayed before the clamped text. Can contain anything.' }}
473 |
474 |
475 |
476 | after
477 |
478 |
479 | Slot 作用域:与
480 | before
相同。
481 |
482 |
483 | Slot scope: Same as
484 | before
.
485 |
486 | {{ zh ? '在被截断的文本后显式的内容,可以包含任意类型内容。' : 'Content displayed after the clamped text. Can contain anything.' }}
487 |
488 |
489 |
490 |
491 |
505 |
528 |
529 |
530 |
531 |
616 |
617 |
618 |
619 |
620 |
621 |
722 |
--------------------------------------------------------------------------------
/src/components/Clamp.js:
--------------------------------------------------------------------------------
1 | import { addListener, removeListener } from 'resize-detector'
2 |
3 | export default {
4 | name: 'vue-clamp',
5 | props: {
6 | tag: {
7 | type: String,
8 | default: 'div'
9 | },
10 | autoresize: {
11 | type: Boolean,
12 | default: false
13 | },
14 | maxLines: Number,
15 | maxHeight: [String, Number],
16 | ellipsis: {
17 | type: String,
18 | default: '…'
19 | },
20 | location: {
21 | type: String,
22 | default: 'end',
23 | validator (value) {
24 | return ['start', 'middle', 'end'].indexOf(value) !== -1
25 | }
26 | },
27 | expanded: Boolean
28 | },
29 | data () {
30 | return {
31 | offset: null,
32 | text: this.getText(),
33 | localExpanded: !!this.expanded
34 | }
35 | },
36 | computed: {
37 | clampedText () {
38 | if (this.location === 'start') {
39 | return this.ellipsis + (this.text.slice(-this.offset) || '').trim()
40 | } else if (this.location === 'middle') {
41 | const split = Math.floor(this.offset / 2)
42 | return (this.text.slice(0, split) || '').trim() + this.ellipsis + (this.text.slice(-split) || '').trim()
43 | }
44 |
45 | return (this.text.slice(0, this.offset) || '').trim() + this.ellipsis
46 | },
47 | isClamped () {
48 | if (!this.text) {
49 | return false
50 | }
51 | return this.offset !== this.text.length
52 | },
53 | realText () {
54 | return this.isClamped ? this.clampedText : this.text
55 | },
56 | realMaxHeight () {
57 | if (this.localExpanded) {
58 | return null
59 | }
60 | const { maxHeight } = this
61 | if (!maxHeight) {
62 | return null
63 | }
64 | return typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight
65 | }
66 | },
67 | watch: {
68 | expanded (val) {
69 | this.localExpanded = val
70 | },
71 | localExpanded (val) {
72 | if (val) {
73 | this.clampAt(this.text.length)
74 | } else {
75 | this.update()
76 | }
77 | if (this.expanded !== val) {
78 | this.$emit('update:expanded', val)
79 | }
80 | },
81 | isClamped: {
82 | handler (val) {
83 | this.$nextTick(() => this.$emit('clampchange', val))
84 | },
85 | immediate: true
86 | }
87 | },
88 | mounted () {
89 | this.init()
90 |
91 | this.$watch(
92 | (vm) => [vm.maxLines, vm.maxHeight, vm.ellipsis, vm.isClamped, vm.location].join(),
93 | this.update
94 | )
95 | this.$watch((vm) => [vm.tag, vm.text, vm.autoresize].join(), this.init)
96 | },
97 | updated () {
98 | this.text = this.getText()
99 | this.applyChange()
100 | },
101 | beforeDestroy () {
102 | this.cleanUp()
103 | },
104 | methods: {
105 | init () {
106 | const contents = this.$slots.default
107 | if (!contents) {
108 | return
109 | }
110 |
111 | this.offset = this.text.length
112 |
113 | this.cleanUp()
114 |
115 | if (this.autoresize) {
116 | addListener(this.$el, this.update)
117 | this.unregisterResizeCallback = () => {
118 | removeListener(this.$el, this.update)
119 | }
120 | }
121 | this.update()
122 | },
123 | update () {
124 | if (this.localExpanded) {
125 | return
126 | }
127 | this.applyChange()
128 | if (this.isOverflow() || this.isClamped) {
129 | this.search()
130 | }
131 | },
132 | expand () {
133 | this.localExpanded = true
134 | },
135 | collapse () {
136 | this.localExpanded = false
137 | },
138 | toggle () {
139 | this.localExpanded = !this.localExpanded
140 | },
141 | getLines () {
142 | return Object.keys(
143 | Array.prototype.slice.call(this.$refs.content.getClientRects()).reduce(
144 | (prev, { top, bottom }) => {
145 | const key = `${top}/${bottom}`
146 | if (!prev[key]) {
147 | prev[key] = true
148 | }
149 | return prev
150 | },
151 | {}
152 | )
153 | ).length
154 | },
155 | isOverflow () {
156 | if (!this.maxLines && !this.maxHeight) {
157 | return false
158 | }
159 |
160 | if (this.maxLines) {
161 | if (this.getLines() > this.maxLines) {
162 | return true
163 | }
164 | }
165 |
166 | if (this.maxHeight) {
167 | if (this.$el.scrollHeight > this.$el.offsetHeight) {
168 | return true
169 | }
170 | }
171 | return false
172 | },
173 | getText () {
174 | // Look for the first non-empty text node
175 | const [content] = (this.$slots.default || []).filter(
176 | (node) => !node.tag && !node.isComment
177 | )
178 | return content ? content.text.trim() : ''
179 | },
180 | moveEdge (steps) {
181 | this.clampAt(this.offset + steps)
182 | },
183 | clampAt (offset) {
184 | this.offset = offset
185 | this.applyChange()
186 | },
187 | applyChange () {
188 | this.$refs.text.textContent = this.realText
189 | },
190 | stepToFit () {
191 | this.fill()
192 | this.clamp()
193 | },
194 | fill () {
195 | while (
196 | (!this.isOverflow() || this.getLines() < 2) &&
197 | this.offset < this.text.length
198 | ) {
199 | this.moveEdge(1)
200 | }
201 | },
202 | clamp () {
203 | while (this.isOverflow() && this.getLines() > 1 && this.offset > 0) {
204 | this.moveEdge(-1)
205 | }
206 | },
207 | search (...range) {
208 | const [from = 0, to = this.offset] = range
209 | if (to - from <= 3) {
210 | this.stepToFit()
211 | return
212 | }
213 | const target = Math.floor((to + from) / 2)
214 | this.clampAt(target)
215 | if (this.isOverflow()) {
216 | this.search(from, target)
217 | } else {
218 | this.search(target, to)
219 | }
220 | },
221 | cleanUp () {
222 | if (this.unregisterResizeCallback) {
223 | this.unregisterResizeCallback()
224 | }
225 | }
226 | },
227 | render (h) {
228 | const contents = [
229 | h(
230 | 'span',
231 | this.$isServer
232 | ? {}
233 | : {
234 | ref: 'text',
235 | attrs: {
236 | 'aria-label': this.text.trim()
237 | }
238 | },
239 | this.$isServer ? this.text : this.realText
240 | )
241 | ]
242 |
243 | const { expand, collapse, toggle } = this
244 | const scope = {
245 | expand,
246 | collapse,
247 | toggle,
248 | clamped: this.isClamped,
249 | expanded: this.localExpanded
250 | }
251 | const before = this.$scopedSlots.before
252 | ? this.$scopedSlots.before(scope)
253 | : this.$slots.before
254 | if (before) {
255 | contents.unshift(...(Array.isArray(before) ? before : [before]))
256 | }
257 | const after = this.$scopedSlots.after
258 | ? this.$scopedSlots.after(scope)
259 | : this.$slots.after
260 | if (after) {
261 | contents.push(...(Array.isArray(after) ? after : [after]))
262 | }
263 | const lines = [
264 | h(
265 | 'span',
266 | {
267 | style: {
268 | boxShadow: 'transparent 0 0'
269 | },
270 | ref: 'content'
271 | },
272 | contents
273 | )
274 | ]
275 | return h(
276 | this.tag,
277 | {
278 | style: {
279 | maxHeight: this.realMaxHeight,
280 | overflow: 'hidden'
281 | }
282 | },
283 | lines
284 | )
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 |
4 | Vue.config.productionTip = false
5 |
6 | new Vue({
7 | render: h => h(App)
8 | }).$mount('#app')
9 |
--------------------------------------------------------------------------------
/src/vue-lang.js:
--------------------------------------------------------------------------------
1 | export default function (hljs) {
2 | const XML_IDENT_RE = '[A-Za-z0-9\\._:-]+'
3 | const TAG_INTERNALS = {
4 | endsWithParent: true,
5 | illegal: /,
6 | relevance: 0,
7 | contains: [
8 | {
9 | className: 'attr',
10 | begin: XML_IDENT_RE,
11 | relevance: 0
12 | },
13 | {
14 | begin: /=\s*/,
15 | relevance: 0,
16 | contains: [
17 | {
18 | className: 'string',
19 | endsParent: true,
20 | variants: [
21 | { begin: /"/, end: /"/ },
22 | { begin: /'/, end: /'/ },
23 | { begin: /[^\s"'=<>`]+/ }
24 | ]
25 | }
26 | ]
27 | }
28 | ]
29 | }
30 | return {
31 | case_insensitive: true,
32 | contains: [
33 | hljs.COMMENT('', {
34 | relevance: 10
35 | }),
36 | {
37 | className: 'tag',
38 | /*
39 | The lookahead pattern (?=...) ensures that 'begin' only matches
40 | '',
50 | returnEnd: true,
51 | subLanguage: ['css', 'less', 'scss', 'stylus']
52 | }
53 | },
54 | {
55 | className: 'tag',
56 | // See the comment in the