Use Zulip emoji list

This commit is contained in:
Heerko 2021-07-21 23:07:59 +02:00
parent 91540eb7f6
commit dd5bd9a7a7
8 changed files with 5364 additions and 77 deletions

1
front/dist/index.html vendored Normal file
View file

@ -0,0 +1 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>ChattyPub</title><link href="/css/app.141fb685.css" rel="preload" as="style"><link href="/css/chunk-vendors.fa36ffc8.css" rel="preload" as="style"><link href="/js/app.bb16d12e.js" rel="preload" as="script"><link href="/js/chunk-vendors.d11bbf9b.js" rel="preload" as="script"><link href="/css/chunk-vendors.fa36ffc8.css" rel="stylesheet"><link href="/css/app.141fb685.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but Chattypub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.d11bbf9b.js"></script><script src="/js/app.bb16d12e.js"></script></body></html>

View file

@ -135,14 +135,14 @@ You should be able to enter all regular CSS rules this way.
It is possible to bypass the parser and add arbitrary code to the CSS on the page. This allows you to add, for example, `@keyframes` for an animation or media queries. To do this send any message to the `#rules` channel and wrap the message in three backticks like this: It is possible to bypass the parser and add arbitrary code to the CSS on the page. This allows you to add, for example, `@keyframes` for an animation or media queries. To do this send any message to the `#rules` channel and wrap the message in three backticks like this:
<code> ~~~~
``` ```
@keyframes example { @keyframes example {
from {background-color: red;} from {background-color: red;}
to {background-color: yellow;} to {background-color: yellow;}
} }
``` ```
</code> ~~~~
--- ---

188
front/package-lock.json generated
View file

@ -1,7 +1,7 @@
{ {
"name": "Chattypub", "name": "Chattypub",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 1, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
@ -10,7 +10,6 @@
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"emoji-js": "^3.5.0",
"markdown-it": "^12.0.6", "markdown-it": "^12.0.6",
"moment": "^2.29.1", "moment": "^2.29.1",
"pagedjs": "^0.2.0", "pagedjs": "^0.2.0",
@ -27,6 +26,7 @@
"@vue/cli-service": "^4.5.13", "@vue/cli-service": "^4.5.13",
"@vue/compiler-sfc": "^3.0.0", "@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"emoji-js": "^3.5.0",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0", "eslint-plugin-vue": "^7.0.0",
"github-markdown-css": "^4.0.0", "github-markdown-css": "^4.0.0",
@ -4608,7 +4608,7 @@
"strip-ansi": "^5.0.0" "strip-ansi": "^5.0.0"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=6"
} }
}, },
"node_modules/clone": { "node_modules/clone": {
@ -6164,12 +6164,14 @@
"node_modules/emoji-datasource": { "node_modules/emoji-datasource": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/emoji-datasource/-/emoji-datasource-4.1.0.tgz", "resolved": "https://registry.npmjs.org/emoji-datasource/-/emoji-datasource-4.1.0.tgz",
"integrity": "sha1-tEVX94ot+sLzUDkzkbFwpWfsKK0=" "integrity": "sha1-tEVX94ot+sLzUDkzkbFwpWfsKK0=",
"dev": true
}, },
"node_modules/emoji-js": { "node_modules/emoji-js": {
"version": "3.5.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/emoji-js/-/emoji-js-3.5.0.tgz", "resolved": "https://registry.npmjs.org/emoji-js/-/emoji-js-3.5.0.tgz",
"integrity": "sha512-5uaULzdR3g6ALBC8xUzyoxAx6izT1M4+DEsxHLRS2/gaOKC/p62831itMoMsYfUj1fKX3YG01u5YVz2v7qpsWg==", "integrity": "sha512-5uaULzdR3g6ALBC8xUzyoxAx6izT1M4+DEsxHLRS2/gaOKC/p62831itMoMsYfUj1fKX3YG01u5YVz2v7qpsWg==",
"dev": true,
"dependencies": { "dependencies": {
"emoji-datasource": "4.1.0" "emoji-datasource": "4.1.0"
} }
@ -17747,7 +17749,8 @@
"version": "4.5.13", "version": "4.5.13",
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.13.tgz", "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.13.tgz",
"integrity": "sha512-I1S9wZC7iI0Wn8kw8Zh+A2Qkf6s1M6vTGBkx8boXjuzfwEEyEHRxadsVCecZc8Mkpydo0nykj+MyYF96TKFuVA==", "integrity": "sha512-I1S9wZC7iI0Wn8kw8Zh+A2Qkf6s1M6vTGBkx8boXjuzfwEEyEHRxadsVCecZc8Mkpydo0nykj+MyYF96TKFuVA==",
"dev": true "dev": true,
"requires": {}
}, },
"@vue/cli-service": { "@vue/cli-service": {
"version": "4.5.13", "version": "4.5.13",
@ -17828,17 +17831,6 @@
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
}, },
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"cliui": { "cliui": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@ -17865,25 +17857,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "dev": true
}, },
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"ssri": { "ssri": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
@ -17893,28 +17866,6 @@
"minipass": "^3.1.1" "minipass": "^3.1.1"
} }
}, },
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.3.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.3.0.tgz",
"integrity": "sha512-UDgni/tUVSdwHuQo+vuBmEgamWx88SuSlEb5fgdvHrlJSPB9qMBRF6W7bfPWSqDns425Gt1wxAUif+f+h/rWjg==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
}
},
"wrap-ansi": { "wrap-ansi": {
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
@ -20404,6 +20355,14 @@
"assert-plus": "^1.0.0" "assert-plus": "^1.0.0"
} }
}, },
"de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
"dev": true,
"optional": true,
"peer": true
},
"debug": { "debug": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
@ -20924,12 +20883,14 @@
"emoji-datasource": { "emoji-datasource": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/emoji-datasource/-/emoji-datasource-4.1.0.tgz", "resolved": "https://registry.npmjs.org/emoji-datasource/-/emoji-datasource-4.1.0.tgz",
"integrity": "sha1-tEVX94ot+sLzUDkzkbFwpWfsKK0=" "integrity": "sha1-tEVX94ot+sLzUDkzkbFwpWfsKK0=",
"dev": true
}, },
"emoji-js": { "emoji-js": {
"version": "3.5.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/emoji-js/-/emoji-js-3.5.0.tgz", "resolved": "https://registry.npmjs.org/emoji-js/-/emoji-js-3.5.0.tgz",
"integrity": "sha512-5uaULzdR3g6ALBC8xUzyoxAx6izT1M4+DEsxHLRS2/gaOKC/p62831itMoMsYfUj1fKX3YG01u5YVz2v7qpsWg==", "integrity": "sha512-5uaULzdR3g6ALBC8xUzyoxAx6izT1M4+DEsxHLRS2/gaOKC/p62831itMoMsYfUj1fKX3YG01u5YVz2v7qpsWg==",
"dev": true,
"requires": { "requires": {
"emoji-datasource": "4.1.0" "emoji-datasource": "4.1.0"
} }
@ -27004,6 +26965,15 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"dev": true "dev": true
}, },
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
},
"string-collapse-leading-whitespace": { "string-collapse-leading-whitespace": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/string-collapse-leading-whitespace/-/string-collapse-leading-whitespace-5.1.0.tgz", "resolved": "https://registry.npmjs.org/string-collapse-leading-whitespace/-/string-collapse-leading-whitespace-5.1.0.tgz",
@ -27100,15 +27070,6 @@
"define-properties": "^1.1.3" "define-properties": "^1.1.3"
} }
}, },
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
},
"strip-ansi": { "strip-ansi": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
@ -28014,6 +27975,87 @@
} }
} }
}, },
"vue-loader-v16": {
"version": "npm:vue-loader@16.3.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.3.0.tgz",
"integrity": "sha512-UDgni/tUVSdwHuQo+vuBmEgamWx88SuSlEb5fgdvHrlJSPB9qMBRF6W7bfPWSqDns425Gt1wxAUif+f+h/rWjg==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"vue-router": { "vue-router": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.8.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.8.tgz",
@ -28040,6 +28082,18 @@
} }
} }
}, },
"vue-template-compiler": {
"version": "2.6.14",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz",
"integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==",
"dev": true,
"optional": true,
"peer": true,
"requires": {
"de-indent": "^1.0.2",
"he": "^1.1.0"
}
},
"vue-template-es2015-compiler": { "vue-template-es2015-compiler": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz",

View file

@ -18,7 +18,7 @@
</div> </div>
<div class="reactions ui"> <div class="reactions ui">
<span v-for="reaction in reactions" :key="reaction" :title="reaction"> <span v-for="reaction in reactions" :key="reaction" :title="reaction">
{{ reaction }} {{ shortcodeToEmoji(reaction) }}
</span> </span>
</div> </div>
</div> </div>

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,7 @@
// let u = (codeUnit) => { // let u = (codeUnit) => {
// return "\\u" + codeUnit.toString(16).toUpperCase(); // return "\\u" + codeUnit.toString(16).toUpperCase();
// } // }
import zulip_emoji from "../data/emoji_codes.json";
export default { export default {
data() { data() {
@ -27,6 +28,14 @@ export default {
return emoji.replace(/\p{Emoji}/gu, (m) => m.codePointAt(0).toString(16)); return emoji.replace(/\p{Emoji}/gu, (m) => m.codePointAt(0).toString(16));
}, },
shortcodeToEmoji: (code) => {
if (code.indexOf(":") !== 0) return code;
let k = code.replaceAll(":", '').trim();
let emoji = zulip_emoji.name_to_codepoint[k];
console.log(k, emoji, parseInt(emoji,16), String.fromCodePoint(parseInt("0x"+emoji)))
return String.fromCodePoint(parseInt("0x"+emoji))
},
// toEmojiCode: (emoji) => { // toEmojiCode: (emoji) => {
// emoji.replace(/\p{Emoji}/gu, function (m) { // emoji.replace(/\p{Emoji}/gu, function (m) {
// toUTF16(m.codePointAt(0)); // toUTF16(m.codePointAt(0));

View file

@ -6,11 +6,14 @@ import { createStore } from 'vuex'
import emoji from "../mixins/emoji" import emoji from "../mixins/emoji"
import { stripHtml } from "string-strip-html" import { stripHtml } from "string-strip-html"
var EmojiConvertor = require('emoji-js');
var emojiConv = new EmojiConvertor(); // var EmojiConvertor = require('emoji-js');
// var emojiConv = new EmojiConvertor();
// let emojis = require('emojis'); // let emojis = require('emojis');
let toCSS = (message, currentStream) => { let toCSS = (message, currentStream) => {
let content = stripHtml(message.content).result; let content = stripHtml(message.content).result;
let className = "", let className = "",
@ -21,7 +24,7 @@ let toCSS = (message, currentStream) => {
is_codeblock = message.content.includes("<code>") || message.content.startsWith("```"), is_codeblock = message.content.includes("<code>") || message.content.startsWith("```"),
is_font = /<p><a href=".+?\.(ttf|otf|woff)/gm.test(message.content); is_font = /<p><a href=".+?\.(ttf|otf|woff)/gm.test(message.content);
// let regex = /[/s]?(?<selector>.+)\s*\n?{\n?(?<prop>[\s\w.~:>-]+\s*:\s*.+;?\n?)*\n?}/gm // let regex = /[/s]?(?<selector>.+)\s*\n?{\n?(?<prop>[\s\w.~:>-]+\s*:\s*.+;?\n?)*\n?}/gm
console.log(message);
let type = is_codeblock ? "raw" : is_font ? "font" : "rule"; // okay okay okay, i know this is ugly :) let type = is_codeblock ? "raw" : is_font ? "font" : "rule"; // okay okay okay, i know this is ugly :)
let regex = /\s?(?<selector>.+)\s*\n?{\n?(?<props>(.*;\n?)+)}/gm let regex = /\s?(?<selector>.+)\s*\n?{\n?(?<props>(.*;\n?)+)}/gm
@ -35,10 +38,12 @@ let toCSS = (message, currentStream) => {
} else if (is_codeblock) { } else if (is_codeblock) {
return { className: '', emoji_code: '', rules: [], parentClassName: '', id: id, content: content, type: type } return { className: '', emoji_code: '', rules: [], parentClassName: '', id: id, content: content, type: type }
} else if (results.length > 0) { // rule and raw } else if (results.length > 0) { // rule and raw
className = emojiConv.replace_colons(results[0]['groups']['selector']); // className = emojiConv.replace_colons(results[0]['groups']['selector']);
className = emoji.methods.shortcodeToEmoji(results[0]['groups']['selector']);
console.log(className)
if (emoji.methods.containsEmoji(className)) { if (emoji.methods.containsEmoji(className)) {
emoji_code = emoji.methods.toEmojiCode(className); emoji_code = emoji.methods.toEmojiCode(className);
console.log("??",className, emoji_code);
} }
rules = results[0]['groups']['props'].split("\n"); rules = results[0]['groups']['props'].split("\n");
rules = rules.filter((rule) => validateRule(rule)) rules = rules.filter((rule) => validateRule(rule))

View file

@ -121,7 +121,7 @@ export default {
if (state !== undefined) this.show_ui = state; if (state !== undefined) this.show_ui = state;
else this.show_ui = !this.show_ui; else this.show_ui = !this.show_ui;
this.$forceUpdate(); this.$forceUpdate();
Splitpanes.updatePaneComponents(); // Splitpanes.updatePaneComponents();
}, },
}, },
}; };