tona-plugins 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +48 -0
  2. package/index.d.ts +32 -0
  3. package/package.json +51 -0
  4. package/src/constants/cdn.js +12 -0
  5. package/src/index.js +30 -0
  6. package/src/plugins/background/index.js +50 -0
  7. package/src/plugins/background/index.scss +0 -0
  8. package/src/plugins/barrage/index.js +104 -0
  9. package/src/plugins/barrage/index.scss +16 -0
  10. package/src/plugins/catalog/index.js +212 -0
  11. package/src/plugins/catalog/index.scss +0 -0
  12. package/src/plugins/charts/index.js +60 -0
  13. package/src/plugins/charts/index.scss +16 -0
  14. package/src/plugins/clickEffects/index.js +137 -0
  15. package/src/plugins/clickEffects/index.scss +0 -0
  16. package/src/plugins/codeCopy/index.js +94 -0
  17. package/src/plugins/codeCopy/index.scss +65 -0
  18. package/src/plugins/codeHighlight/index.js +35 -0
  19. package/src/plugins/codeHighlight/index.scss +216 -0
  20. package/src/plugins/codeHighlight/themes.js +60 -0
  21. package/src/plugins/codeLang/index.js +51 -0
  22. package/src/plugins/codeLang/index.scss +28 -0
  23. package/src/plugins/codeLinenumbers/index.js +33 -0
  24. package/src/plugins/codeLinenumbers/index.scss +61 -0
  25. package/src/plugins/codeTrafficLight/index.js +18 -0
  26. package/src/plugins/codeTrafficLight/index.scss +0 -0
  27. package/src/plugins/colorMode/index.js +81 -0
  28. package/src/plugins/colorMode/index.scss +0 -0
  29. package/src/plugins/commentsAvatars/index.js +85 -0
  30. package/src/plugins/commentsAvatars/index.scss +3 -0
  31. package/src/plugins/darkMode/index.js +115 -0
  32. package/src/plugins/darkMode/index.scss +67 -0
  33. package/src/plugins/donation/index.js +45 -0
  34. package/src/plugins/donation/index.scss +63 -0
  35. package/src/plugins/emoji/index.js +167 -0
  36. package/src/plugins/emoji/index.scss +155 -0
  37. package/src/plugins/footer/index.js +35 -0
  38. package/src/plugins/footer/index.scss +54 -0
  39. package/src/plugins/imagePreview/index.js +98 -0
  40. package/src/plugins/imagePreview/index.scss +51 -0
  41. package/src/plugins/license/index.js +62 -0
  42. package/src/plugins/license/index.scss +51 -0
  43. package/src/plugins/live2d/index.js +67 -0
  44. package/src/plugins/live2d/index.scss +0 -0
  45. package/src/plugins/live2d/live2d-models.js +21 -0
  46. package/src/plugins/lock/index.js +77 -0
  47. package/src/plugins/lock/index.scss +56 -0
  48. package/src/plugins/musicPlayer/index.js +81 -0
  49. package/src/plugins/musicPlayer/index.scss +68 -0
  50. package/src/plugins/notation/index.js +73 -0
  51. package/src/plugins/notation/index.scss +12 -0
  52. package/src/plugins/notice/index.js +17 -0
  53. package/src/plugins/notice/index.scss +46 -0
  54. package/src/plugins/postBottomImage/index.js +27 -0
  55. package/src/plugins/postBottomImage/index.scss +26 -0
  56. package/src/plugins/postMessage/index.js +89 -0
  57. package/src/plugins/postMessage/index.scss +81 -0
  58. package/src/plugins/postTopImage/index.js +28 -0
  59. package/src/plugins/postTopImage/index.scss +29 -0
  60. package/src/plugins/qrcode/index.js +42 -0
  61. package/src/plugins/qrcode/index.scss +10 -0
  62. package/src/plugins/signature/index.js +47 -0
  63. package/src/plugins/signature/index.scss +18 -0
  64. package/src/plugins/toast/index.js +49 -0
  65. package/src/plugins/tools/index.js +251 -0
  66. package/src/plugins/tools/index.scss +114 -0
  67. package/src/plugins/webTag/index.js +40 -0
  68. package/src/plugins/webTag/index.scss +0 -0
  69. package/src/utils/cnblog.js +282 -0
  70. package/src/utils/helpers.js +418 -0
  71. package/src/utils/shared.js +2 -0
@@ -0,0 +1,167 @@
1
+ // 评论输入表情
2
+ import { getEmojiOptions } from 'tona-options'
3
+ import { isPostDetailsPage } from '../../utils/cnblog'
4
+ import { isUrl } from '../../utils/helpers'
5
+
6
+ const defaultEmojiList = [
7
+ {
8
+ value: '👍',
9
+ label: '',
10
+ },
11
+ {
12
+ value: '👏',
13
+ label: '',
14
+ },
15
+ {
16
+ value: '😄',
17
+ label: '',
18
+ },
19
+ {
20
+ value: '🎉',
21
+ label: '',
22
+ },
23
+ {
24
+ value: '🚀',
25
+ label: '',
26
+ },
27
+ {
28
+ value: '👀',
29
+ label: '',
30
+ },
31
+ ]
32
+
33
+ /**
34
+ * 创建按钮
35
+ */
36
+ function createEmojiButton(buttonIcon) {
37
+ return `<span class="qaq-btn" title="表情">${
38
+ buttonIcon.length ? buttonIcon : '🍺'
39
+ }</span>`
40
+ }
41
+
42
+ /**
43
+ * 创建表情项
44
+ */
45
+ function createEmojiItem(itemData) {
46
+ const { value, label } = itemData
47
+ const el = $('<div>').addClass('emoji-item')
48
+
49
+ if (isUrl(value)) {
50
+ const emoji = $('<img />').addClass('emoji emoji-img').attr('src', value)
51
+ el.append(emoji)
52
+ } else {
53
+ el.append(`<div class="emoji emoji-text">${value}</div>`)
54
+ }
55
+
56
+ if (typeof label === 'string') {
57
+ el.attr('title', label)
58
+ }
59
+
60
+ return el
61
+ }
62
+
63
+ /**
64
+ * 创建表情列表
65
+ */
66
+ function createEmojiList(emojiList) {
67
+ const emojis = emojiList?.length ? emojiList : defaultEmojiList
68
+ const $emoji = $('<div class="emoji-list"></div>')
69
+
70
+ emojis.forEach((item) => {
71
+ const emojiItem = createEmojiItem(item)
72
+ $emoji.append(emojiItem)
73
+ })
74
+
75
+ return $emoji
76
+ }
77
+
78
+ /**
79
+ * 创建表情面板容器
80
+ */
81
+ function createEmojiContainer() {
82
+ return $('<div>').addClass('qaq-wrap anim-scale-in')
83
+ }
84
+
85
+ /**
86
+ * 创建表情面板蒙层
87
+ */
88
+ function createMask() {
89
+ return $('<div>').addClass('qaq-mask')
90
+ }
91
+
92
+ /**
93
+ * 打开或关闭表情面板
94
+ */
95
+ function qaqToggle() {
96
+ $('.qaq-wrap').toggle()
97
+ }
98
+
99
+ /**
100
+ * 选择表情
101
+ */
102
+ function selectEmoji() {
103
+ $('.emoji-item').click(function () {
104
+ const $emoji = $(this).find('.emoji')
105
+ let emojiValue
106
+
107
+ const isImgEmoji = $emoji.hasClass('emoji-img')
108
+
109
+ if (isImgEmoji) {
110
+ const url = $emoji.attr('src')
111
+ emojiValue = `![](${url})`
112
+ } else {
113
+ const textEmoji = $emoji.html()
114
+ emojiValue = textEmoji
115
+ }
116
+
117
+ document.querySelector('#tbCommentBody').value += emojiValue
118
+ qaqToggle()
119
+ })
120
+ }
121
+
122
+ /**
123
+ * 创建表情插件
124
+ * @param {Array} emojiData
125
+ */
126
+ function createEmoji(emojiData, buttonIcon) {
127
+ const button = createEmojiButton(buttonIcon)
128
+ const emojiContainer = createEmojiContainer()
129
+ const mask = createMask()
130
+ const emojiList = createEmojiList(emojiData)
131
+
132
+ emojiContainer.append(emojiList).append(mask)
133
+
134
+ $('.commentbox_title_right').prepend(button).css('position', 'relative')
135
+
136
+ $('.qaq-btn')
137
+ .after(emojiContainer)
138
+ .click(() => qaqToggle())
139
+
140
+ $('.qaq-mask').click(() => qaqToggle())
141
+
142
+ selectEmoji()
143
+ }
144
+
145
+ export function emoji(_theme, devOptions) {
146
+ const { enable, emojiList, buttonIcon } = getEmojiOptions(devOptions)
147
+
148
+ if (!enable) {
149
+ return
150
+ }
151
+ if (!isPostDetailsPage()) {
152
+ return
153
+ }
154
+
155
+ const builder = () => {
156
+ if ($('.qaq-btn').length) {
157
+ return
158
+ }
159
+ if (!$('.commentbox_title_right').length) {
160
+ return
161
+ }
162
+ createEmoji(emojiList, buttonIcon)
163
+ }
164
+
165
+ builder()
166
+ window.buildEmojis = builder
167
+ }
@@ -0,0 +1,155 @@
1
+ @use 'sass:map';
2
+
3
+ $emoji: () !default;
4
+ $emoji: map.merge(
5
+ (
6
+ textEmojiColor: #666,
7
+ bg: #fff,
8
+ borderColor: #e1e1e1,
9
+ hoverBg: #f8f8f8,
10
+ hoverBorderColor: #eee,
11
+ fontEmojiSize: 14px,
12
+ ),
13
+ $emoji
14
+ );
15
+
16
+ $textEmojiColor: map.get($emoji, textEmojiColor);
17
+ $bg: map.get($emoji, bg);
18
+ $borderColor: map.get($emoji, borderColor);
19
+ $hoverBg: map.get($emoji, hoverBg);
20
+ $hoverBorderColor: map.get($emoji, hoverBorderColor);
21
+ $fontEmojiSize: map.get($emoji, fontEmojiSize);
22
+
23
+ .qaq-btn {
24
+ user-select: none;
25
+ cursor: pointer;
26
+ }
27
+
28
+ .qaq-wrap {
29
+ display: none;
30
+ position: absolute;
31
+ top: 34px;
32
+ left: -4%;
33
+ padding: 8px;
34
+ background-color: $bg;
35
+ border: 1px solid $borderColor;
36
+ border-radius: 4px;
37
+ width: 500px;
38
+ max-width: 283px;
39
+ z-index: 3;
40
+
41
+ &::before,
42
+ &::after {
43
+ position: absolute;
44
+ content: '';
45
+ margin: 0;
46
+ width: 0;
47
+ height: 0;
48
+ }
49
+
50
+ &:after {
51
+ top: -9px;
52
+ left: 14px;
53
+ border-left: 8px solid transparent;
54
+ border-right: 8px solid transparent;
55
+ border-bottom: 8px solid $borderColor;
56
+ }
57
+
58
+ &::before {
59
+ top: -8px;
60
+ left: 14px;
61
+ border-left: 8px solid transparent;
62
+ border-right: 8px solid transparent;
63
+ border-bottom: 8px solid $bg;
64
+ z-index: 1;
65
+ }
66
+ }
67
+
68
+ .emoji-list {
69
+ display: grid;
70
+ grid-template-columns: repeat(6, minmax(0, 1fr));
71
+ padding-right: 2px;
72
+ max-height: 200px;
73
+ overflow-y: auto;
74
+ overflow-x: hidden;
75
+ font-size: $fontEmojiSize;
76
+
77
+ .emoji-item {
78
+ display: flex;
79
+ justify-content: center;
80
+ align-items: center;
81
+ padding-top: 6px;
82
+ padding-bottom: 6px;
83
+ color: $textEmojiColor;
84
+ cursor: pointer;
85
+ box-sizing: border-box;
86
+ border: 1px solid transparent;
87
+ border-radius: 4px;
88
+ user-select: none;
89
+
90
+ img.emoji {
91
+ width: 20px;
92
+ height: 20px;
93
+ }
94
+
95
+ &:hover {
96
+ background-color: $hoverBg;
97
+ border-color: $hoverBorderColor;
98
+
99
+ & > .emoji {
100
+ transform: scale(1.2);
101
+ transition: transform 0.15s cubic-bezier(0.2, 0, 0.13, 2);
102
+ }
103
+ }
104
+ }
105
+
106
+ &::-webkit-scrollbar {
107
+ width: 8px;
108
+ height: 8px;
109
+ }
110
+
111
+ &::-webkit-scrollbar-corner {
112
+ background-color: transparent;
113
+ }
114
+
115
+ &::-webkit-scrollbar-thumb {
116
+ background-color: #d2cfcf;
117
+ border-radius: 4px;
118
+ }
119
+
120
+ &::-webkit-scrollbar-track {
121
+ background-color: transparent;
122
+ }
123
+ }
124
+
125
+ .qaq-mask {
126
+ position: fixed;
127
+ top: 0;
128
+ right: 0;
129
+ bottom: 0;
130
+ left: 0;
131
+ z-index: 2;
132
+ display: block;
133
+ cursor: default;
134
+ content: ' ';
135
+ background: transparent;
136
+ z-index: -1;
137
+ }
138
+
139
+ .anim-scale-in {
140
+ animation-name: scale-in;
141
+ animation-duration: 0.15s;
142
+ animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1.5);
143
+ }
144
+
145
+ @keyframes scale-in {
146
+ 0% {
147
+ opacity: 0;
148
+ transform: scale(0.5);
149
+ }
150
+
151
+ 100% {
152
+ opacity: 1;
153
+ transform: scale(1);
154
+ }
155
+ }
@@ -0,0 +1,35 @@
1
+ import { getLinksOptions } from 'tona-options'
2
+
3
+ /**
4
+ * 构建 copyright
5
+ */
6
+ function buildCopyright() {
7
+ const nickName = $('#profile_block a:first').text().trim()
8
+
9
+ const el = `<div id='copyright'>
10
+ <span>Copyright © ${new Date().getFullYear()} ${nickName}</span>
11
+ <span> Powered by you 🌊 Theme in ${'tona'.link('#')}</span>
12
+ </div>`
13
+
14
+ $('#footer').empty().append(el)
15
+ }
16
+
17
+ /**
18
+ * 构建自定义链接
19
+ */
20
+ function buildCustomLinks(devOptions) {
21
+ const config = getLinksOptions(devOptions)
22
+
23
+ if (config.links.length) {
24
+ const $links = $('<ul id="links"></ul>')
25
+ for (const { title, url } of config.links) {
26
+ $links.append(`<li><a href='${url}'>${title}</a></li>`)
27
+ }
28
+ $('#footer').prepend($links.prop('outerHTML'))
29
+ }
30
+ }
31
+
32
+ export function footer(_, devOptions) {
33
+ buildCopyright()
34
+ buildCustomLinks(devOptions)
35
+ }
@@ -0,0 +1,54 @@
1
+ @use 'sass:map';
2
+
3
+ $footer: () !default;
4
+ $footer: map.merge(
5
+ (
6
+ background: #232323,
7
+ ),
8
+ $footer
9
+ );
10
+
11
+ $footerBackground: map.get($footer, background);
12
+
13
+ #footer {
14
+ margin: 54px 0 0;
15
+ padding: 32.4px 0;
16
+ text-align: center;
17
+ color: #979b9e;
18
+ background: $footerBackground;
19
+ #links {
20
+ display: flex;
21
+ justify-content: center;
22
+ font-size: 16px;
23
+ li {
24
+ color: #fff;
25
+ &:hover {
26
+ text-shadow: 0 0 2px #eee;
27
+ color: var(--themeColor);
28
+ transition: all 0.25s;
29
+ }
30
+ }
31
+ & > li:not(:first-child) {
32
+ margin-left: 21.6px;
33
+ }
34
+ }
35
+ #copyright {
36
+ display: flex;
37
+ flex-direction: column;
38
+ align-items: center;
39
+ margin-top: 14.4018px;
40
+ font-size: 14px;
41
+ span {
42
+ margin-top: 7.1982px;
43
+ }
44
+ }
45
+ }
46
+
47
+ @media screen and (max-width: 767px) {
48
+ #links {
49
+ width: 90vw;
50
+ overflow: auto;
51
+ margin: 0 auto;
52
+ white-space: nowrap;
53
+ }
54
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * 博客园现在使用了与之相同的库
3
+ * 1. 随笔详情页图片灯箱已移除,博客园已拥有此功能
4
+ * 2. 相册查看图像
5
+ */
6
+ import { getImagePreviewOptions } from 'tona-options'
7
+ import { mediaZoomJs } from '../../constants/cdn'
8
+ import { isAlbumPage, isMd, isPostDetailsPage } from '../../utils/cnblog'
9
+ import { loadScript, mousewheel, poll } from '../../utils/helpers'
10
+
11
+ const customGalleryClass = 'custom-gallery'
12
+
13
+ const mediumZoomConfig = {
14
+ opacity: 0.5,
15
+ }
16
+
17
+ /**
18
+ * 去除 TinyMCE 插入的图片的容器链接
19
+ */
20
+ function removeImageOuterHref() {
21
+ if (isMd()) {
22
+ return
23
+ }
24
+ $('p img').css('display', 'block').unwrap()
25
+ }
26
+
27
+ /**
28
+ * 构建相册
29
+ */
30
+ function buildGallery() {
31
+ if (!isAlbumPage()) {
32
+ return
33
+ }
34
+ const title = $('.thumbTitle, .galleryTitle').text().trim()
35
+ const el = `
36
+ <section class="${customGalleryClass}">
37
+ <h3>${title}</h3>
38
+ <div></div>
39
+ </section>`
40
+ const gallery = $(el)
41
+ $('.gallery img').each(function () {
42
+ const src = $(this).attr('src')
43
+ const openSrc = src.replace(/t_/, 'o_')
44
+ gallery.find('div').append(`<img src="${openSrc}"/>`)
45
+ })
46
+ $('.forFlow').append(gallery)
47
+ $('.gallery').remove()
48
+ }
49
+
50
+ /**
51
+ * 构建图片灯箱
52
+ */
53
+ function build() {
54
+ // 博客园给随笔内容中的图片添加了这个功能,现将它移除
55
+ // #cnblogs_post_body img,
56
+ const imgList = $(` .blog_comment_body img, .${customGalleryClass} img`)
57
+ if (imgList === 0) {
58
+ return
59
+ }
60
+ $.each(imgList, (i) => {
61
+ const item = $(imgList[i])
62
+ item.addClass('custom-zoom')
63
+ })
64
+
65
+ const zoom = window.mediumZoom('.custom-zoom', mediumZoomConfig)
66
+
67
+ zoom.on('open', () => {
68
+ mousewheel(() => {
69
+ zoom.close()
70
+ })
71
+ })
72
+
73
+ removeImageOuterHref()
74
+ }
75
+
76
+ export function imagePreview(_, devOptions) {
77
+ const { enable } = getImagePreviewOptions(devOptions)
78
+
79
+ if (!enable) {
80
+ return
81
+ }
82
+ if (!isMd() && !isAlbumPage() && !isPostDetailsPage()) {
83
+ return
84
+ }
85
+ if ($('.custom-zoom').length) {
86
+ return
87
+ }
88
+
89
+ buildGallery()
90
+ loadScript(mediaZoomJs, () => {
91
+ poll(
92
+ () =>
93
+ $('.blog_comment_body').length || $(`.${customGalleryClass}`).length,
94
+ build,
95
+ )
96
+ })
97
+ window.imagebox = build
98
+ }
@@ -0,0 +1,51 @@
1
+ @use 'sass:map';
2
+
3
+ $imagebox: () !default;
4
+ $imagebox: map.merge(
5
+ (
6
+ background: rgba(0, 0, 0, 0.5),
7
+ ),
8
+ $imagebox
9
+ );
10
+
11
+ $imageboxBackground: map.get($imagebox, background);
12
+
13
+ body {
14
+ .medium-zoom-overlay {
15
+ background: $imageboxBackground !important;
16
+ z-index: 6;
17
+ }
18
+
19
+ .medium-zoom-image--opened {
20
+ z-index: 7;
21
+ }
22
+ }
23
+
24
+ html .medium-zoom--opened .medium-zoom-overlay {
25
+ opacity: 0.9;
26
+ }
27
+
28
+ .lightbox-post {
29
+ cursor: zoom-in;
30
+ }
31
+
32
+ // gallery start
33
+ .gallery {
34
+ display: none;
35
+ }
36
+
37
+ .custom-gallery {
38
+ div {
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ margin-top: 20px;
42
+ img {
43
+ margin: 8px;
44
+ width: 170px;
45
+ height: 170px;
46
+ border-radius: 4px;
47
+ }
48
+ }
49
+ }
50
+
51
+ // gallery end
@@ -0,0 +1,62 @@
1
+ // 构建随笔签名
2
+ import { getLicenseOptions } from 'tona-options'
3
+ import {
4
+ getBlogname,
5
+ getCurrentPage,
6
+ getCurrentPostUrl,
7
+ } from '../../utils/cnblog'
8
+
9
+ const containerId = 'post-signature'
10
+
11
+ /**
12
+ * 构建容器
13
+ */
14
+ function buildContainer() {
15
+ const author = getBlogname()
16
+ const href = getCurrentPostUrl()
17
+ const el = `<div id='${containerId}'>
18
+ <p>本文作者:${author}</p>
19
+ <p>本文链接:${href}</p>
20
+ </div>`
21
+ $('#blog_post_info_block').before(el)
22
+ }
23
+
24
+ /**
25
+ * 构建版权信息
26
+ * @param {boolean} enableLicense
27
+ * @param {string} licenseName
28
+ * @param {string} licenseLink
29
+ */
30
+ function buildLicense(enableLicense, licenseName, licenseLink) {
31
+ if (!enableLicense) return
32
+ const agreement = licenseName.length
33
+ ? licenseName
34
+ : '知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆'
35
+ const el = `<p>版权声明:本作品采用${agreement}<a href='${licenseLink}'>许可协议</a>进行许可。</p>`
36
+ $(`#${containerId}`).append(el)
37
+ }
38
+
39
+ /**
40
+ * 构建自定义内容
41
+ * @param {Array} content
42
+ */
43
+ function buildContent(content) {
44
+ let el = ''
45
+ for (let i = 0; i < content.length; i++) {
46
+ el += `<p>${content[i]}</p>`
47
+ }
48
+ $(`#${containerId}`).append(el)
49
+ }
50
+
51
+ export function license(_, devOptions) {
52
+ const { enable, license, licenseName, licenseLink, contents } =
53
+ getLicenseOptions(devOptions)
54
+
55
+ if (!enable) return
56
+ if (getCurrentPage() !== 'post') return
57
+ if ($('#MySignature').children().length) return
58
+
59
+ buildContainer()
60
+ buildLicense(license, licenseName, licenseLink)
61
+ buildContent(contents)
62
+ }
@@ -0,0 +1,51 @@
1
+ @use 'sass:map';
2
+
3
+ $postSignature: (
4
+ primary: #fa745e,
5
+ background: #f6f8fa,
6
+ borderRadius: 2px,
7
+ ) !default;
8
+
9
+ $psignaturePrimary: map.get($postSignature, primary);
10
+ $psignatureBackground: map.get($postSignature, background);
11
+ $psignatureBorderRadius: map.get($postSignature, borderRadius);
12
+
13
+ #mainContent {
14
+ #post-signature {
15
+ position: relative;
16
+ display: flex;
17
+ flex-direction: column;
18
+ margin: 25px 0;
19
+ padding: 10px 24px 10px 15px;
20
+ background: $psignatureBackground;
21
+ border-left: 5px solid $psignaturePrimary;
22
+ border-radius: $psignatureBorderRadius;
23
+
24
+ p {
25
+ margin: 0;
26
+ font-size: 14px;
27
+ line-height: 1.6em;
28
+ }
29
+
30
+ a {
31
+ color: inherit;
32
+ border-bottom: none;
33
+ }
34
+
35
+ &::before {
36
+ content: '!';
37
+ background: $psignaturePrimary;
38
+ position: absolute;
39
+ top: 16px;
40
+ left: -12px;
41
+ color: #fff;
42
+ width: 18px;
43
+ height: 18px;
44
+ border-radius: 50%;
45
+ text-align: center;
46
+ line-height: 18px;
47
+ font-weight: 800;
48
+ font-size: 12px;
49
+ }
50
+ }
51
+ }