├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── deploy.yml ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── magane.plugin.js └── magane.vencord.js ├── package.json ├── rollup-bd.config.js ├── rollup-vencord.config.js ├── src ├── App.svelte ├── Button.svelte ├── bd-main.js ├── meta.txt ├── styles │ └── main.scss └── vencord-main.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | indent_style = space 13 | indent_size = 4 14 | insert_final_newline = true 15 | trim_trailing_whitespace = false 16 | 17 | [*.json] 18 | indent_style = space 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': 'aqua/svelte', 3 | 'env': { 4 | browser: true, 5 | node: true, 6 | es2021: true 7 | }, 8 | 'rules': { 9 | 'comma-dangle': ['error', 'never'], 10 | 'import/order': 'off', 11 | 'semi': 'error' 12 | }, 13 | 'overrides': [ 14 | { 15 | files: [ 16 | '*.config.js' 17 | ], 18 | env: { 19 | browser: false 20 | } 21 | } 22 | ], 23 | 'ignorePatterns': [ 24 | 'dist/', 25 | 'dist-dev/' 26 | ], 27 | 'settings': { 28 | 'svelte3/ignore-warnings': ({ code }) => code === 'a11y-click-events-have-key-events' 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: pitu 4 | patreon: pitu 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Release 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | env: 15 | NODE_VERSION: 18 16 | 17 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 18 | jobs: 19 | # This workflow contains a single job called "build" 20 | build: 21 | # The type of runner that the job will run on 22 | runs-on: ubuntu-latest 23 | 24 | # Steps represent a sequence of tasks that will be executed as part of the job 25 | steps: 26 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 27 | - name: Checkout repo 28 | uses: actions/checkout@v4 29 | 30 | - name: Install Node v14 31 | uses: actions/setup-node@v4 32 | with: 33 | node-version: ${{ env.NODE_VERSION }} 34 | 35 | - name: Cache node_modules 36 | uses: actions/cache@v4 37 | id: cache-nodemodules 38 | with: 39 | path: node_modules 40 | key: ${{ runner.os }}-${{ env.NODE_VERSION }}-nodemodules-${{ hashFiles('**/yarn.lock') }} 41 | 42 | - name: Install dependencies 43 | if: steps.cache-nodemodules.outputs.cache-hit != 'true' 44 | run: yarn install --frozen-lockfile --non-interactive 45 | 46 | - name: Update browserslist/caniuse-lite 47 | run: npx browserslist@latest --update-db 48 | 49 | - name: Build application (BetterDiscord plugin) 50 | run: yarn build-bd 51 | 52 | - name: Build application (Vencord plugin) 53 | run: yarn build-vc 54 | 55 | - uses: EndBug/add-and-commit@v6 56 | with: 57 | branch: master 58 | message: 'dist: deploy new dist file' 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist-dev/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Pitu 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 |
2 | 3 |
4 | 5 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/kanadeko/Kuro/master/LICENSE) 6 | [![Chat / Support](https://img.shields.io/badge/Chat%20%2F%20Support-discord-7289DA.svg?style=flat-square)](https://discord.gg/5g6vgwn) 7 | [![Support me](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.herokuapp.com%2Fpitu&style=flat-square)](https://www.patreon.com/pitu) 8 | [![Support me](https://img.shields.io/badge/Support-Buy%20me%20a%20coffee-yellow.svg?style=flat-square)](https://www.buymeacoffee.com/kana) 9 | 10 | ### What does this do? 11 | 12 | You know how LINE and Telegram have this beautiful sticker system where you can send different reactions with just one click? This is a new approach at doing exactly that but on Discord! 13 | 14 | [It looks like this!](https://chibisafe.moe/owdxQF9m.mp4) 15 | 16 | ### How even? 17 | 18 | By injecting the script into Discord it will automatically pull a curated list of sticker packs from [chibisafe](https://chibisafe.moe) and add them to your Discord client. After that a heart button will appear in your message bar where you'll be able to see all the stickers! 19 | 20 | 21 | ### Can I make this permanent? 22 | 23 | You can! 24 | 25 | Currently the best way to get this up and running is by using [BetterDiscord](https://github.com/rauenzi/BetterDiscordApp/releases), but we also have experimental support for [Vencord](https://vencord.dev/). 26 | 27 | #### BetterDiscord 28 | 29 | - Grab Magane's BetterDiscord plugin file from [https://magane.moe/api/dist/betterdiscord](https://magane.moe/api/dist/betterdiscord). 30 | - Place the plugin file into your BetterDiscord's **plugins** directory. 31 | - Activate Magane from Discord's **User Settings > BetterDiscord > Plugins**. 32 | 33 | #### Vencord 34 | 35 | > This is for the more advanced users. 36 | > Be advised that Magane's **update checker is not available** on Vencord. 37 | > You must manually consult this repo to check for updates! Or just check back whenever it breaks due to Discord's updates... 38 | 39 | 1. Grab Magane's Vencord plugin file from [https://magane.moe/api/dist/vencord](https://magane.moe/api/dist/vencord). 40 | 2. Install Vencord from source. Follow its installation guide at [https://docs.vencord.dev/installing/](https://docs.vencord.dev/installing/). 41 | 3. In Vencord git directory, navigate to `src`, then create `userplugins` directory (e.g. `/path/to/vencord/src/userplugins`). 42 | 4. Place Magane's plugin file into it. 43 | 5. Rebuild Vencord (`pnpm build`). 44 | 6. (optional) If you had not done it before, you must inject Vencord into your Discord installation (`pnpm inject`). 45 | For those that use **Vesktop**, please see below. 46 | 7. Restart your Discord/Vesktop. 47 | 8. Activate Magane from Discord's **User Settings > Vencord > Plugins**. 48 | 49 | #### Vesktop 50 | 51 | Follow the same steps as above, until step 6. 52 | 53 | Then in your Vesktop, navigate to Discord's **User Settings > Vencord > Vesktop Settings > Open Developer Settings > Vencord Location**. Change its directory to the **dist** sub-directory of your local copy of Vencord git [(preview)](https://chibisafe.moe/pCX4Qa82.png). Then restart Vesktop. 54 | 55 | You should now be able to activate Magane from Vesktop's **Plugins** settings. 56 | 57 | ### So how exactly do I use it after adding the script? 58 | 59 | It's very easy. 60 | After opening the sticker popup click on the little tool icon and you'll see a list of available sticker packs. Clicking on a sticker pack will subscribe you to it, making every sticker on that pack available on the main window. You can also right click any sticker in the list and add them to your favorites for easy access! To remove one, right click on it again. 61 | 62 | Or watch [the preview](https://chibisafe.moe/owdxQF9m.mp4) again. 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magane", 3 | "version": "3.2.20", 4 | "description": "A lie about a lie... It turns inside-out on itself.", 5 | "author": "Pitu ", 6 | "license": "MIT", 7 | "contributors": [ 8 | { 9 | "name": "iCrawl", 10 | "email": "icrawltogo@gmail.com" 11 | }, 12 | { 13 | "name": "BobbyWibowo", 14 | "email": "bobby@fiery.me" 15 | } 16 | ], 17 | "engines": { 18 | "node": ">=18" 19 | }, 20 | "scripts": { 21 | "build-bd": "rollup -c rollup-bd.config.js", 22 | "dev-bd": "rollup -c rollup-bd.config.js -w", 23 | "build-vc": "rollup -c rollup-vencord.config.js", 24 | "dev-vc": "rollup -c rollup-vencord.config.js -w" 25 | }, 26 | "dependencies": {}, 27 | "devDependencies": { 28 | "@rollup/plugin-commonjs": "^11.1.0", 29 | "@rollup/plugin-node-resolve": "^11.2.1", 30 | "@rollup/plugin-replace": "^5.0.2", 31 | "@rollup/plugin-terser": "^0.4.4", 32 | "eslint": "^8.57.0", 33 | "eslint-config-aqua": "^9.2.1", 34 | "eslint-plugin-import": "^2.29.1", 35 | "eslint-plugin-svelte3": "^4.0.0", 36 | "postcss-preset-env": "^6.7.0", 37 | "rollup": "^1.32.1", 38 | "rollup-plugin-license": "^2.8.1", 39 | "rollup-plugin-livereload": "^1.0.0", 40 | "rollup-plugin-postcss": "^v2.8.2", 41 | "rollup-plugin-serve": "^1.1.0", 42 | "rollup-plugin-svelte": "^6.1.1", 43 | "sass": "^1.52.3", 44 | "semver": "^7.5.3", 45 | "svelte": "^3.48.0", 46 | "svelte-preprocess": "^4.10.7", 47 | "svelte-scrollto": "^0.2.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rollup-bd.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import license from 'rollup-plugin-license'; 5 | import terser from '@rollup/plugin-terser'; 6 | import replace from '@rollup/plugin-replace'; 7 | import postcss from 'rollup-plugin-postcss'; 8 | import postcssPresetEnv from 'postcss-preset-env'; 9 | import autoPreprocess from 'svelte-preprocess'; 10 | import fs from 'fs/promises'; 11 | import path from 'path'; 12 | 13 | const production = !process.env.ROLLUP_WATCH; 14 | const file = path.resolve(__dirname, production ? 'dist' : 'dist-dev', 'magane.plugin.js'); 15 | const meta = path.resolve(__dirname, 'src/meta.txt'); 16 | const metadata = { 17 | name: 'MaganeBD', 18 | displayName: 'MaganeBD', 19 | description: 'Bringing LINE stickers to Discord in a chaotic way. BetterDiscord edition.', 20 | updateUrl: 'https://raw.githubusercontent.com/Pitu/Magane/master/dist/magane.plugin.js' 21 | }; 22 | 23 | export default { 24 | input: 'src/bd-main.js', 25 | output: { 26 | file, 27 | format: 'cjs', 28 | name: 'app', 29 | // BetterDiscord won't make sourcemaps available to DevTools anyways, 30 | // and we are not minifying processed output to follow BetterDiscord guidelines, 31 | // so might as well not generate them altogether. 32 | sourcemap: false 33 | }, 34 | plugins: [ 35 | !production && { 36 | name: 'watch-extras', 37 | buildStart() { 38 | this.addWatchFile(meta); 39 | } 40 | }, 41 | svelte({ 42 | dev: !production, 43 | emitCss: true, 44 | preprocess: autoPreprocess(), 45 | onwarn: (warning, handler) => { 46 | if (warning.code === 'a11y-click-events-have-key-events') return; 47 | handler(warning); 48 | } 49 | }), 50 | postcss({ 51 | extensions: ['.css', '.scss'], 52 | plugins: [ 53 | postcssPresetEnv() 54 | ], 55 | inject: (cssVariableName, fileId) => { 56 | // Extract packaga name if available 57 | const match = fileId.match(/[\/]node_modules[\/](.*?)[\/]/); 58 | let pkg = ''; 59 | if (match) pkg = `${match[1]}-`; 60 | // Normalize basename 61 | const id = pkg + path.basename(fileId).replace(/\./, '_'); 62 | // Arguably hacky.., but cleanest method that I could think of 63 | return 'if (typeof window.MAGANE_STYLES !== "object") window.MAGANE_STYLES = {};\n' + 64 | `window.MAGANE_STYLES["${id}"] = ${cssVariableName};`; 65 | } 66 | }), 67 | resolve({ 68 | browser: true 69 | }), 70 | commonjs(), 71 | production && terser({ 72 | ecma: 2021, 73 | compress: { 74 | keep_classnames: true, 75 | keep_fnames: true, 76 | passes: 1 77 | }, 78 | mangle: false, 79 | output: { 80 | beautify: true, 81 | keep_numbers: true, 82 | indent_level: 4 83 | } 84 | }), 85 | production && replace({ 86 | delimiters: ['', ''], 87 | preventAssignment: false, 88 | values: { 89 | ' ': '\t' 90 | } 91 | }), 92 | license({ 93 | banner: { 94 | commentStyle: 'regular', 95 | content: { 96 | file: meta, 97 | encoding: 'utf-8' 98 | }, 99 | data() { return metadata; } 100 | } 101 | }), 102 | { 103 | name: 'copyDistFile', 104 | writeBundle: async () => { 105 | if (!Boolean(process.env.BD_PLUGIN_PATH)) return; 106 | await fs.copyFile(file, process.env.BD_PLUGIN_PATH); 107 | console.log(`Copied dist file to ${process.env.BD_PLUGIN_PATH}`); 108 | } 109 | } 110 | ], 111 | watch: { 112 | clearScreen: false 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /rollup-vencord.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import license from 'rollup-plugin-license'; 5 | import terser from '@rollup/plugin-terser'; 6 | import replace from '@rollup/plugin-replace'; 7 | import postcss from 'rollup-plugin-postcss'; 8 | import postcssPresetEnv from 'postcss-preset-env'; 9 | import autoPreprocess from 'svelte-preprocess'; 10 | import fs from 'fs/promises'; 11 | import path from 'path'; 12 | 13 | const production = !process.env.ROLLUP_WATCH; 14 | const file = path.resolve(__dirname, production ? 'dist' : 'dist-dev', 'magane.vencord.js'); 15 | const meta = path.resolve(__dirname, 'src/meta.txt'); 16 | const metadata = { 17 | name: 'MaganeVencord', 18 | displayName: 'MaganeVencord', 19 | description: 'Bringing LINE stickers to Discord in a chaotic way. Vencord edition.', 20 | updateUrl: 'https://raw.githubusercontent.com/Pitu/Magane/master/dist/magane.vencord.js' 21 | }; 22 | 23 | export default { 24 | input: 'src/vencord-main.js', 25 | output: { 26 | file, 27 | format: 'cjs', 28 | name: 'app', 29 | // BetterDiscord won't make sourcemaps available to DevTools anyways, 30 | // and we are not minifying processed output to follow BetterDiscord guidelines, 31 | // so might as well not generate them altogether. 32 | sourcemap: false 33 | }, 34 | plugins: [ 35 | svelte({ 36 | dev: !production, 37 | emitCss: true, 38 | preprocess: autoPreprocess(), 39 | onwarn: (warning, handler) => { 40 | if (warning.code === 'a11y-click-events-have-key-events') return; 41 | handler(warning); 42 | } 43 | }), 44 | postcss({ 45 | extensions: ['.css', '.scss'], 46 | plugins: [ 47 | postcssPresetEnv() 48 | ], 49 | inject: (cssVariableName, fileId) => { 50 | // Extract packaga name if available 51 | const match = fileId.match(/[\/]node_modules[\/](.*?)[\/]/); 52 | let pkg = ''; 53 | if (match) pkg = `${match[1]}-`; 54 | // Normalize basename 55 | const id = pkg + path.basename(fileId).replace(/\./, '_'); 56 | // Arguably hacky.., but cleanest method that I could think of 57 | return 'if (typeof window.MAGANE_STYLES !== "object") window.MAGANE_STYLES = {};\n' + 58 | `window.MAGANE_STYLES["${id}"] = ${cssVariableName};`; 59 | } 60 | }), 61 | resolve({ 62 | browser: true 63 | }), 64 | commonjs(), 65 | production && terser({ 66 | ecma: 2021, 67 | compress: { 68 | keep_classnames: true, 69 | keep_fnames: true, 70 | passes: 1 71 | }, 72 | mangle: false, 73 | output: { 74 | beautify: true, 75 | keep_numbers: true, 76 | indent_level: 4 77 | } 78 | }), 79 | production && replace({ 80 | delimiters: ['', ''], 81 | preventAssignment: false, 82 | values: { 83 | ' ': '\t' 84 | } 85 | }), 86 | production && replace({ 87 | delimiters: ['', ''], 88 | preventAssignment: false, 89 | values: { 90 | '"use strict";': '"use strict"\n__VencordImports__;' 91 | } 92 | }), 93 | !production && replace({ 94 | delimiters: ['', ''], 95 | preventAssignment: false, 96 | values: { 97 | '\'use strict\';': '\'use strict\';\n__VencordImports__;' 98 | } 99 | }), 100 | replace({ 101 | delimiters: ['', ''], 102 | preventAssignment: false, 103 | values: { 104 | '__VencordImports__;': 'import definePlugin from "@utils/types";\nimport { findByPropsLazy, findLazy } from "@webpack";\nimport { Alerts, Toasts } from "@webpack/common";', 105 | 'VencordApi.': '', 106 | 107 | // Svelte syntax, lmao.. 108 | '$$invalidate(0, mountType)': '$$invalidate(0, mountType = MountType.VENCORD)', 109 | 110 | // This is so hacky, lmao 111 | 'var vencordMain = definePlugin({': 'export default definePlugin({', 112 | 'module.exports = vencordMain;': '' 113 | } 114 | }), 115 | license({ 116 | banner: { 117 | commentStyle: 'regular', 118 | content: { 119 | file: meta, 120 | encoding: 'utf-8' 121 | }, 122 | data() { return metadata; } 123 | } 124 | }), 125 | { 126 | name: 'copyDistFile', 127 | writeBundle: async () => { 128 | if (!Boolean(process.env.VENCORD_PLUGIN_PATH)) return; 129 | await fs.copyFile(file, process.env.VENCORD_PLUGIN_PATH); 130 | console.log(`Copied dist file to ${process.env.VENCORD_PLUGIN_PATH}`); 131 | } 132 | } 133 | ], 134 | watch: { 135 | clearScreen: false 136 | } 137 | }; 138 | -------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 2341 | 2342 |
2343 |
2345 | 2346 |
2347 |
2348 | { #if !favoriteStickers.length && !subscribedPacks.length } 2349 |

It seems you aren't subscribed to any pack yet. Click the plus symbol on the bottom-left to get started! 🎉

2350 | { /if } 2351 | { #if favoriteStickers.length } 2352 |
2353 | Favorites{ @html formatStickersCount(favoriteStickers.length) } 2354 | { #each favoriteStickers as sticker, i } 2355 |
2356 | { sticker.pack } - { sticker.id } 2363 |
2366 | 2367 | 2368 | 2369 |
2370 |
2371 | { /each } 2372 |
2373 | { /if } 2374 | 2375 | { #if frequentlyUsedSorted.length } 2376 |
2377 | Frequently Used{ @html formatStickersCount(frequentlyUsedSorted.length) } 2378 | { #each frequentlyUsedSorted as sticker, i } 2379 |
2380 | { sticker.pack } - { sticker.id } 2387 | { #if favoriteStickers.findIndex(f => f.pack === sticker.pack && f.id === sticker.id) === -1 } 2388 |
2391 | 2392 | 2393 | 2394 |
2395 | { :else } 2396 |
2399 | 2400 | 2401 | 2402 |
2403 | { /if } 2404 |
2405 | { /each } 2406 |
2407 | { /if } 2408 | 2409 | { #each subscribedPacks as pack, i } 2410 |
2411 | { pack.name }{ @html formatStickersCount(pack.files.length) } 2412 | 2413 | { #each pack.files as sticker, i } 2414 |
2415 | { pack.id } - { sticker } 2421 | { #if favoriteStickers.findIndex(f => f.pack === pack.id && f.id === sticker) === -1 } 2422 |
2425 | 2426 | 2427 | 2428 |
2429 | { :else } 2430 |
2433 | 2434 | 2435 | 2436 |
2437 | { /if } 2438 |
2439 | { /each } 2440 |
2441 | { /each } 2442 |
2443 | 2444 |
2445 |
2446 |
2447 |
2450 |
2451 |
2452 | { #if favoriteSticker.length } 2453 |
scrollToStickers('#pfavorites') } 2455 | title="Favorites" > 2456 |
2457 |
2458 | { /if } 2459 | { #if frequentlyUsedSorted.length } 2460 |
scrollToStickers('#pfrequentlyused') } 2462 | title="Frequently Used" > 2463 |
2464 |
2465 | { /if } 2466 |
2467 |
2468 | 2469 |
2470 |
2471 | { #each subscribedPacks as pack, i } 2472 |
scrollToStickers(`#p${pack.id}`) } 2474 | title="{ pack.name }" 2475 | style="background-image: { `url(${formatUrl(pack.id, pack.files[0], false, 0)})` }" /> 2476 | { /each } 2477 |
2478 |
2479 |
2480 | 2481 |
2482 |