├── .eslintignore ├── .eslintrc.js ├── .github ├── dependabot.yml └── workflows │ ├── combine-prs.yml │ ├── quality-check.yml │ └── stale.yml ├── .gitignore ├── .prettierrc.js ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets └── js │ └── ecoindex-badge.js ├── babel.config.js ├── package-lock.json ├── package.json ├── src └── ts │ └── ecoindex-badge.ts ├── tsconfig.eslint.json ├── tsconfig.json └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // root: true, 3 | env: { 4 | browser: true, 5 | es2021: true 6 | }, 7 | plugins: [ 8 | "@typescript-eslint", 9 | "prettier" 10 | ], 11 | extends: [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/recommended", 14 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 15 | "plugin:@typescript-eslint/eslint-recommended" 16 | ], 17 | parser: '@typescript-eslint/parser', 18 | parserOptions: { 19 | project: './tsconfig.json', 20 | tsconfigRootDir: __dirname, 21 | }, 22 | rules: { 23 | "prettier/prettier": "error" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "monthly" -------------------------------------------------------------------------------- /.github/workflows/combine-prs.yml: -------------------------------------------------------------------------------- 1 | name: 'Combine PRs' 2 | 3 | # Controls when the action will run - in this case triggered manually 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | branchPrefix: 8 | description: 'Branch prefix to find combinable PRs based on' 9 | required: true 10 | default: 'dependabot' 11 | mustBeGreen: 12 | description: 'Only combine PRs that are green (status is success). Set to false if repo does not run checks' 13 | type: boolean 14 | required: true 15 | default: true 16 | combineBranchName: 17 | description: 'Name of the branch to combine PRs into' 18 | required: true 19 | default: 'combine-prs-branch' 20 | ignoreLabel: 21 | description: 'Exclude PRs with this label' 22 | required: true 23 | default: 'nocombine' 24 | 25 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 26 | jobs: 27 | # This workflow contains a single job called "combine-prs" 28 | combine-prs: 29 | # The type of runner that the job will run on 30 | runs-on: ubuntu-latest 31 | 32 | # Steps represent a sequence of tasks that will be executed as part of the job 33 | steps: 34 | - uses: actions/github-script@v6 35 | id: create-combined-pr 36 | name: Create Combined PR 37 | with: 38 | github-token: ${{secrets.GITHUB_TOKEN}} 39 | script: | 40 | const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', { 41 | owner: context.repo.owner, 42 | repo: context.repo.repo 43 | }); 44 | let branchesAndPRStrings = []; 45 | let baseBranch = null; 46 | let baseBranchSHA = null; 47 | for (const pull of pulls) { 48 | const branch = pull['head']['ref']; 49 | console.log('Pull for branch: ' + branch); 50 | if (branch.startsWith('${{ github.event.inputs.branchPrefix }}')) { 51 | console.log('Branch matched prefix: ' + branch); 52 | let statusOK = true; 53 | if(${{ github.event.inputs.mustBeGreen }}) { 54 | console.log('Checking green status: ' + branch); 55 | const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) { 56 | repository(owner: $owner, name: $repo) { 57 | pullRequest(number:$pull_number) { 58 | commits(last: 1) { 59 | nodes { 60 | commit { 61 | statusCheckRollup { 62 | state 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | }` 70 | const vars = { 71 | owner: context.repo.owner, 72 | repo: context.repo.repo, 73 | pull_number: pull['number'] 74 | }; 75 | const result = await github.graphql(stateQuery, vars); 76 | const [{ commit }] = result.repository.pullRequest.commits.nodes; 77 | const state = commit.statusCheckRollup.state 78 | console.log('Validating status: ' + state); 79 | if(state != 'SUCCESS') { 80 | console.log('Discarding ' + branch + ' with status ' + state); 81 | statusOK = false; 82 | } 83 | } 84 | console.log('Checking labels: ' + branch); 85 | const labels = pull['labels']; 86 | for(const label of labels) { 87 | const labelName = label['name']; 88 | console.log('Checking label: ' + labelName); 89 | if(labelName == '${{ github.event.inputs.ignoreLabel }}') { 90 | console.log('Discarding ' + branch + ' with label ' + labelName); 91 | statusOK = false; 92 | } 93 | } 94 | if (statusOK) { 95 | console.log('Adding branch to array: ' + branch); 96 | const prString = '#' + pull['number'] + ' ' + pull['title']; 97 | branchesAndPRStrings.push({ branch, prString }); 98 | baseBranch = pull['base']['ref']; 99 | baseBranchSHA = pull['base']['sha']; 100 | } 101 | } 102 | } 103 | if (branchesAndPRStrings.length == 0) { 104 | core.setFailed('No PRs/branches matched criteria'); 105 | return; 106 | } 107 | try { 108 | await github.rest.git.createRef({ 109 | owner: context.repo.owner, 110 | repo: context.repo.repo, 111 | ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}', 112 | sha: baseBranchSHA 113 | }); 114 | } catch (error) { 115 | console.log(error); 116 | core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?'); 117 | return; 118 | } 119 | 120 | let combinedPRs = []; 121 | let mergeFailedPRs = []; 122 | for(const { branch, prString } of branchesAndPRStrings) { 123 | try { 124 | await github.rest.repos.merge({ 125 | owner: context.repo.owner, 126 | repo: context.repo.repo, 127 | base: '${{ github.event.inputs.combineBranchName }}', 128 | head: branch, 129 | }); 130 | console.log('Merged branch ' + branch); 131 | combinedPRs.push(prString); 132 | } catch (error) { 133 | console.log('Failed to merge branch ' + branch); 134 | mergeFailedPRs.push(prString); 135 | } 136 | } 137 | 138 | console.log('Creating combined PR'); 139 | const combinedPRsString = combinedPRs.join('\n'); 140 | let body = '✅ This PR was created by the Combine PRs action by combining the following PRs:\n' + combinedPRsString; 141 | if(mergeFailedPRs.length > 0) { 142 | const mergeFailedPRsString = mergeFailedPRs.join('\n'); 143 | body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString 144 | } 145 | await github.rest.pulls.create({ 146 | owner: context.repo.owner, 147 | repo: context.repo.repo, 148 | title: 'Combined PR', 149 | head: '${{ github.event.inputs.combineBranchName }}', 150 | base: baseBranch, 151 | body: body 152 | }); 153 | -------------------------------------------------------------------------------- /.github/workflows/quality-check.yml: -------------------------------------------------------------------------------- 1 | name: "Check Ecoindex Badge quality" 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | lint: 7 | name: "✅ Quality check" 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: "🚀 Checkout code" 11 | uses: actions/checkout@v4 12 | 13 | - name: "👷 Install dependencies" 14 | run: npm install 15 | 16 | - name: "🚨 Lint source code" 17 | run: npm run lint 18 | 19 | # - name: "🐛 Run tests" 20 | # run: npm run test -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v8 11 | with: 12 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' 13 | days-before-stale: 30 14 | days-before-close: 5 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // prettier.config.js or .prettierrc.js 2 | module.exports = { 3 | trailingComma: "es5", 4 | tabWidth: 4, 5 | singleQuote: true, 6 | }; -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 44 | 45 | [homepage]: https://www.contributor-covenant.org 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ecoindex_badge 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## We Develop with Github 12 | 13 | We use github to host code, to track issues and feature requests, as well as accept pull requests. 14 | 15 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests 16 | 17 | Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests: 18 | 19 | 1. Fork the repo and create your branch from `master`. 20 | 2. If you've added code that should be tested, add tests. 21 | 3. Sources are in the `src` directory. 22 | 4. Make sure your code lints, run `npm run lint` or `npm run lint:fix` to fix. 23 | 5. To generate the minifier file, run `npm run build` 24 | 6. Issue that pull request! 25 | 26 | ## Any contributions you make will be under the MIT Software License 27 | 28 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 29 | 30 | ## Report bugs using Github's issues 31 | 32 | We use GitHub issues to track public bugs. Report a bug by opening a new issue; it's that easy! 33 | 34 | ## Write bug reports with detail, background, and sample code 35 | 36 | **Great Bug Reports** tend to have: 37 | 38 | - A quick summary and/or background 39 | - Steps to reproduce 40 | - Be specific! 41 | - Give sample code if you can. [An example question](http://stackoverflow.com/q/12488905/180626) includes sample code that *anyone* with a base R setup can run to reproduce what I was seeing 42 | - What you expected would happen 43 | - What actually happens 44 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 45 | 46 | People *love* thorough bug reports. I'm not even kidding. 47 | 48 | ## Use a Consistent Coding Style 49 | 50 | We use [black](https://github.com/psf/black) for linting. 51 | 52 | ## License 53 | 54 | By contributing, you agree that your contributions will be licensed under its MIT License. 55 | 56 | ## References 57 | 58 | This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | Attribution-NonCommercial-NoDerivs 3.0 Unported 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR 10 | DAMAGES RESULTING FROM ITS USE. 11 | 12 | License 13 | 14 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE 15 | COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY 16 | COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS 17 | AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 18 | 19 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE 20 | TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY 21 | BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS 22 | CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND 23 | CONDITIONS. 24 | 25 | 1. Definitions 26 | 27 | a. "Adaptation" means a work based upon the Work, or upon the Work and 28 | other pre-existing works, such as a translation, adaptation, 29 | derivative work, arrangement of music or other alterations of a 30 | literary or artistic work, or phonogram or performance and includes 31 | cinematographic adaptations or any other form in which the Work may be 32 | recast, transformed, or adapted including in any form recognizably 33 | derived from the original, except that a work that constitutes a 34 | Collection will not be considered an Adaptation for the purpose of 35 | this License. For the avoidance of doubt, where the Work is a musical 36 | work, performance or phonogram, the synchronization of the Work in 37 | timed-relation with a moving image ("synching") will be considered an 38 | Adaptation for the purpose of this License. 39 | b. "Collection" means a collection of literary or artistic works, such as 40 | encyclopedias and anthologies, or performances, phonograms or 41 | broadcasts, or other works or subject matter other than works listed 42 | in Section 1(f) below, which, by reason of the selection and 43 | arrangement of their contents, constitute intellectual creations, in 44 | which the Work is included in its entirety in unmodified form along 45 | with one or more other contributions, each constituting separate and 46 | independent works in themselves, which together are assembled into a 47 | collective whole. A work that constitutes a Collection will not be 48 | considered an Adaptation (as defined above) for the purposes of this 49 | License. 50 | c. "Distribute" means to make available to the public the original and 51 | copies of the Work through sale or other transfer of ownership. 52 | d. "Licensor" means the individual, individuals, entity or entities that 53 | offer(s) the Work under the terms of this License. 54 | e. "Original Author" means, in the case of a literary or artistic work, 55 | the individual, individuals, entity or entities who created the Work 56 | or if no individual or entity can be identified, the publisher; and in 57 | addition (i) in the case of a performance the actors, singers, 58 | musicians, dancers, and other persons who act, sing, deliver, declaim, 59 | play in, interpret or otherwise perform literary or artistic works or 60 | expressions of folklore; (ii) in the case of a phonogram the producer 61 | being the person or legal entity who first fixes the sounds of a 62 | performance or other sounds; and, (iii) in the case of broadcasts, the 63 | organization that transmits the broadcast. 64 | f. "Work" means the literary and/or artistic work offered under the terms 65 | of this License including without limitation any production in the 66 | literary, scientific and artistic domain, whatever may be the mode or 67 | form of its expression including digital form, such as a book, 68 | pamphlet and other writing; a lecture, address, sermon or other work 69 | of the same nature; a dramatic or dramatico-musical work; a 70 | choreographic work or entertainment in dumb show; a musical 71 | composition with or without words; a cinematographic work to which are 72 | assimilated works expressed by a process analogous to cinematography; 73 | a work of drawing, painting, architecture, sculpture, engraving or 74 | lithography; a photographic work to which are assimilated works 75 | expressed by a process analogous to photography; a work of applied 76 | art; an illustration, map, plan, sketch or three-dimensional work 77 | relative to geography, topography, architecture or science; a 78 | performance; a broadcast; a phonogram; a compilation of data to the 79 | extent it is protected as a copyrightable work; or a work performed by 80 | a variety or circus performer to the extent it is not otherwise 81 | considered a literary or artistic work. 82 | g. "You" means an individual or entity exercising rights under this 83 | License who has not previously violated the terms of this License with 84 | respect to the Work, or who has received express permission from the 85 | Licensor to exercise rights under this License despite a previous 86 | violation. 87 | h. "Publicly Perform" means to perform public recitations of the Work and 88 | to communicate to the public those public recitations, by any means or 89 | process, including by wire or wireless means or public digital 90 | performances; to make available to the public Works in such a way that 91 | members of the public may access these Works from a place and at a 92 | place individually chosen by them; to perform the Work to the public 93 | by any means or process and the communication to the public of the 94 | performances of the Work, including by public digital performance; to 95 | broadcast and rebroadcast the Work by any means including signs, 96 | sounds or images. 97 | i. "Reproduce" means to make copies of the Work by any means including 98 | without limitation by sound or visual recordings and the right of 99 | fixation and reproducing fixations of the Work, including storage of a 100 | protected performance or phonogram in digital form or other electronic 101 | medium. 102 | 103 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce, 104 | limit, or restrict any uses free from copyright or rights arising from 105 | limitations or exceptions that are provided for in connection with the 106 | copyright protection under copyright law or other applicable laws. 107 | 108 | 3. License Grant. Subject to the terms and conditions of this License, 109 | Licensor hereby grants You a worldwide, royalty-free, non-exclusive, 110 | perpetual (for the duration of the applicable copyright) license to 111 | exercise the rights in the Work as stated below: 112 | 113 | a. to Reproduce the Work, to incorporate the Work into one or more 114 | Collections, and to Reproduce the Work as incorporated in the 115 | Collections; and, 116 | b. to Distribute and Publicly Perform the Work including as incorporated 117 | in Collections. 118 | 119 | The above rights may be exercised in all media and formats whether now 120 | known or hereafter devised. The above rights include the right to make 121 | such modifications as are technically necessary to exercise the rights in 122 | other media and formats, but otherwise you have no rights to make 123 | Adaptations. Subject to 8(f), all rights not expressly granted by Licensor 124 | are hereby reserved, including but not limited to the rights set forth in 125 | Section 4(d). 126 | 127 | 4. Restrictions. The license granted in Section 3 above is expressly made 128 | subject to and limited by the following restrictions: 129 | 130 | a. You may Distribute or Publicly Perform the Work only under the terms 131 | of this License. You must include a copy of, or the Uniform Resource 132 | Identifier (URI) for, this License with every copy of the Work You 133 | Distribute or Publicly Perform. You may not offer or impose any terms 134 | on the Work that restrict the terms of this License or the ability of 135 | the recipient of the Work to exercise the rights granted to that 136 | recipient under the terms of the License. You may not sublicense the 137 | Work. You must keep intact all notices that refer to this License and 138 | to the disclaimer of warranties with every copy of the Work You 139 | Distribute or Publicly Perform. When You Distribute or Publicly 140 | Perform the Work, You may not impose any effective technological 141 | measures on the Work that restrict the ability of a recipient of the 142 | Work from You to exercise the rights granted to that recipient under 143 | the terms of the License. This Section 4(a) applies to the Work as 144 | incorporated in a Collection, but this does not require the Collection 145 | apart from the Work itself to be made subject to the terms of this 146 | License. If You create a Collection, upon notice from any Licensor You 147 | must, to the extent practicable, remove from the Collection any credit 148 | as required by Section 4(c), as requested. 149 | b. You may not exercise any of the rights granted to You in Section 3 150 | above in any manner that is primarily intended for or directed toward 151 | commercial advantage or private monetary compensation. The exchange of 152 | the Work for other copyrighted works by means of digital file-sharing 153 | or otherwise shall not be considered to be intended for or directed 154 | toward commercial advantage or private monetary compensation, provided 155 | there is no payment of any monetary compensation in connection with 156 | the exchange of copyrighted works. 157 | c. If You Distribute, or Publicly Perform the Work or Collections, You 158 | must, unless a request has been made pursuant to Section 4(a), keep 159 | intact all copyright notices for the Work and provide, reasonable to 160 | the medium or means You are utilizing: (i) the name of the Original 161 | Author (or pseudonym, if applicable) if supplied, and/or if the 162 | Original Author and/or Licensor designate another party or parties 163 | (e.g., a sponsor institute, publishing entity, journal) for 164 | attribution ("Attribution Parties") in Licensor's copyright notice, 165 | terms of service or by other reasonable means, the name of such party 166 | or parties; (ii) the title of the Work if supplied; (iii) to the 167 | extent reasonably practicable, the URI, if any, that Licensor 168 | specifies to be associated with the Work, unless such URI does not 169 | refer to the copyright notice or licensing information for the Work. 170 | The credit required by this Section 4(c) may be implemented in any 171 | reasonable manner; provided, however, that in the case of a 172 | Collection, at a minimum such credit will appear, if a credit for all 173 | contributing authors of Collection appears, then as part of these 174 | credits and in a manner at least as prominent as the credits for the 175 | other contributing authors. For the avoidance of doubt, You may only 176 | use the credit required by this Section for the purpose of attribution 177 | in the manner set out above and, by exercising Your rights under this 178 | License, You may not implicitly or explicitly assert or imply any 179 | connection with, sponsorship or endorsement by the Original Author, 180 | Licensor and/or Attribution Parties, as appropriate, of You or Your 181 | use of the Work, without the separate, express prior written 182 | permission of the Original Author, Licensor and/or Attribution 183 | Parties. 184 | d. For the avoidance of doubt: 185 | 186 | i. Non-waivable Compulsory License Schemes. In those jurisdictions in 187 | which the right to collect royalties through any statutory or 188 | compulsory licensing scheme cannot be waived, the Licensor 189 | reserves the exclusive right to collect such royalties for any 190 | exercise by You of the rights granted under this License; 191 | ii. Waivable Compulsory License Schemes. In those jurisdictions in 192 | which the right to collect royalties through any statutory or 193 | compulsory licensing scheme can be waived, the Licensor reserves 194 | the exclusive right to collect such royalties for any exercise by 195 | You of the rights granted under this License if Your exercise of 196 | such rights is for a purpose or use which is otherwise than 197 | noncommercial as permitted under Section 4(b) and otherwise waives 198 | the right to collect royalties through any statutory or compulsory 199 | licensing scheme; and, 200 | iii. Voluntary License Schemes. The Licensor reserves the right to 201 | collect royalties, whether individually or, in the event that the 202 | Licensor is a member of a collecting society that administers 203 | voluntary licensing schemes, via that society, from any exercise 204 | by You of the rights granted under this License that is for a 205 | purpose or use which is otherwise than noncommercial as permitted 206 | under Section 4(b). 207 | e. Except as otherwise agreed in writing by the Licensor or as may be 208 | otherwise permitted by applicable law, if You Reproduce, Distribute or 209 | Publicly Perform the Work either by itself or as part of any 210 | Collections, You must not distort, mutilate, modify or take other 211 | derogatory action in relation to the Work which would be prejudicial 212 | to the Original Author's honor or reputation. 213 | 214 | 5. Representations, Warranties and Disclaimer 215 | 216 | UNLESS OTHERWISE MUTUALLY AGREED BY THE PARTIES IN WRITING, LICENSOR 217 | OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY 218 | KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, 219 | INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, 220 | FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF 221 | LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, 222 | WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION 223 | OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 224 | 225 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE 226 | LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR 227 | ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES 228 | ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS 229 | BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 230 | 231 | 7. Termination 232 | 233 | a. This License and the rights granted hereunder will terminate 234 | automatically upon any breach by You of the terms of this License. 235 | Individuals or entities who have received Collections from You under 236 | this License, however, will not have their licenses terminated 237 | provided such individuals or entities remain in full compliance with 238 | those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any 239 | termination of this License. 240 | b. Subject to the above terms and conditions, the license granted here is 241 | perpetual (for the duration of the applicable copyright in the Work). 242 | Notwithstanding the above, Licensor reserves the right to release the 243 | Work under different license terms or to stop distributing the Work at 244 | any time; provided, however that any such election will not serve to 245 | withdraw this License (or any other license that has been, or is 246 | required to be, granted under the terms of this License), and this 247 | License will continue in full force and effect unless terminated as 248 | stated above. 249 | 250 | 8. Miscellaneous 251 | 252 | a. Each time You Distribute or Publicly Perform the Work or a Collection, 253 | the Licensor offers to the recipient a license to the Work on the same 254 | terms and conditions as the license granted to You under this License. 255 | b. If any provision of this License is invalid or unenforceable under 256 | applicable law, it shall not affect the validity or enforceability of 257 | the remainder of the terms of this License, and without further action 258 | by the parties to this agreement, such provision shall be reformed to 259 | the minimum extent necessary to make such provision valid and 260 | enforceable. 261 | c. No term or provision of this License shall be deemed waived and no 262 | breach consented to unless such waiver or consent shall be in writing 263 | and signed by the party to be charged with such waiver or consent. 264 | d. This License constitutes the entire agreement between the parties with 265 | respect to the Work licensed here. There are no understandings, 266 | agreements or representations with respect to the Work not specified 267 | here. Licensor shall not be bound by any additional provisions that 268 | may appear in any communication from You. This License may not be 269 | modified without the mutual written agreement of the Licensor and You. 270 | e. The rights granted under, and the subject matter referenced, in this 271 | License were drafted utilizing the terminology of the Berne Convention 272 | for the Protection of Literary and Artistic Works (as amended on 273 | September 28, 1979), the Rome Convention of 1961, the WIPO Copyright 274 | Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 275 | and the Universal Copyright Convention (as revised on July 24, 1971). 276 | These rights and subject matter take effect in the relevant 277 | jurisdiction in which the License terms are sought to be enforced 278 | according to the corresponding provisions of the implementation of 279 | those treaty provisions in the applicable national law. If the 280 | standard suite of rights granted under applicable copyright law 281 | includes additional rights not granted under this License, such 282 | additional rights are deemed to be included in the License; this 283 | License is not intended to restrict the license of any rights under 284 | applicable law. 285 | 286 | 287 | Creative Commons Notice 288 | 289 | Creative Commons is not a party to this License, and makes no warranty 290 | whatsoever in connection with the Work. Creative Commons will not be 291 | liable to You or any party on any legal theory for any damages 292 | whatsoever, including without limitation any general, special, 293 | incidental or consequential damages arising in connection to this 294 | license. Notwithstanding the foregoing two (2) sentences, if Creative 295 | Commons has expressly identified itself as the Licensor hereunder, it 296 | shall have all rights and obligations of Licensor. 297 | 298 | Except for the limited purpose of indicating to the public that the 299 | Work is licensed under the CCPL, Creative Commons does not authorize 300 | the use by either party of the trademark "Creative Commons" or any 301 | related trademark or logo of Creative Commons without the prior 302 | written consent of Creative Commons. Any permitted use will be in 303 | compliance with Creative Commons' then-current trademark usage 304 | guidelines, as may be published on its website or otherwise made 305 | available upon request from time to time. For the avoidance of doubt, 306 | this trademark restriction does not form part of this License. 307 | 308 | Creative Commons may be contacted at http://creativecommons.org/. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ecoindex Badge 2 | 3 | [![](https://data.jsdelivr.com/v1/package/gh/cnumr/ecoindex_badge/badge)](https://www.jsdelivr.com/package/gh/cnumr/ecoindex_badge) 4 | 5 | Ce projet a pour but de proposer un badge à intégrer à vos pages web facilement. Ce badge va afficher le score Ecoindex de la page web sur laquelle il est intégré et permettra de rediriger l'utilisateur vers la page de détails du score Ecoindex. 6 | 7 | ## Comment l'intégrer ? 8 | 9 | La version la plus simple est de copier-coller le code suivant dans votre page web : 10 | 11 | ![Ecoindex Badge](https://cdn.jsdelivr.net/gh/cnumr/ecoindex_badge@2.2/assets/svg/light/A.svg) 12 | 13 | ```html 14 |
15 | 16 | ``` 17 | 18 | Il existe une variante pour les thèmes sombres : 19 | 20 | ![Ecoindex Badge](https://cdn.jsdelivr.net/gh/cnumr/ecoindex_badge@2.2/assets/svg/dark/A.svg) 21 | 22 | ```html 23 |
24 | 25 | ``` 26 | 27 | Cette version est optimisée et permet de ne pas surcharger votre page web avec des scripts inutiles. : 28 | 29 | - Badge au format SVG (pas de pixelisation - taille 779 octets) 30 | - JS servi par un CDN (Cache - taille 569 octets) 31 | 32 | ## Comment ça fonctionne ? 33 | 34 | Le script javascript va faire appel au Micro service Back For Front d'Ecoindex pour récupérer l'image SVG correspondante à votre page en se basant sur son url et intégrer le code HTML à la place de la balise `
`. 35 | 36 | ## Et si je ne veux pas de JS ? 37 | 38 | Vous pouvez utiliser la version statique du badge en utilisant le snippet suivant, en modifiant les paramètres `{url}` et `{theme}` (light ou dark)) : 39 | 40 | ```html 41 | 42 | Ecoindex Badge 43 | 44 | ``` 45 | 46 | ## À propos du cache 47 | 48 | Le badge utilise un cache de 7 jours à 2 niveaux: 49 | - Au niveau [serveur](https://github.com/cnumr/ecoindex_bff#about-caching) 50 | - Au niveau local 51 | 52 | > **Bon à savoir:** 53 | > - On peut **forcer le refresh du cache serveur** en utilisant le [plugin ecoindex](https://github.com/cnumr/ecoindex-browser-plugin). Lorsque l'on clique sur le bouton du plugin pour afficher le détail du résultat, on force une mise à jour du cache serveur. 54 | > - On peut **forcer la mise à jour du cache local** en rechargeant la page avec `ctrl+maj+R` 55 | 56 | ## Pour contribuer 57 | 58 | Vous pouvez contribuer à ce projet en proposant des améliorations ou en signalant des bugs. Pour cela, vous pouvez utiliser les fonctionnalités de GitHub. Retrouvez plus d'infos sur la page [Contribuer](CONTRIBUTING.md). 59 | 60 | ## Disclaimer 61 | 62 | Les valeurs d'ACV utilisées par ecoindex_cli pour évaluer les impacts environnementaux ne sont pas sous licence libre - ©Frédéric Bordage Veuillez également vous référer aux mentions fournies dans les fichiers de code pour les spécificités du régime IP. 63 | 64 | ## [Licence](LICENSE) 65 | 66 | ## [Code de conduite](CODE_OF_CONDUCT.md) 67 | -------------------------------------------------------------------------------- /assets/js/ecoindex-badge.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var e;const t="https://bff.ecoindex.fr",n=document.getElementById("ecoindex-badge"),r="light",o=(null!==(e=null==n?void 0:n.getAttribute("data-theme"))&&void 0!==e?e:r)===r,d=window.location.href,i="#0d2e38",a=o?i:"#fff",l=o?"#eee":i;(()=>{if(!n)return;n.style.display="none";const e=`${t}/redirect/?url=${d}`,r="Résultat analyse écoIndex : ",o="badge écoIndex : ";let i=r+"Page non mesurée",s=o+"page non mesurée",c="?",u="grey";fetch(`${t}/api/results/?url=${d}`,{method:"GET"}).then((e=>e.ok?e.json():new Promise(((e,t)=>t(`Erreur dans l'appel à l'écoindex de : ${d}`))))).then((e=>{const t=e["latest-result"],n=t.grade;i=r+n,s=o+"résultat analyse = "+n,c=n,u=t.color})).catch(console.error).finally((()=>{const t=(e=>{const t=document.createElement("span");return t.setAttribute("id","ecoindex-badge-letter"),t.append(e),t})(c),r=((e,t,n)=>{const r=document.createElement("a");return r.setAttribute("href",e),r.setAttribute("target","_blank"),r.setAttribute("rel","noreferrer"),r.setAttribute("id","ecoindex-badge-link"),r.setAttribute("title",t),r.setAttribute("aria-label",n),r.append("Ecoindex"),r})(e,i,s),o=(e=>{const t=document.createElement("style"),n=`#ecoindex-badge{font-family:Arial,sans-serif;width:108px}#ecoindex-badge-link{align-items:center;border-radius:1.3em;display:flex;font-size:13px;gap:.85em;padding:.5em .5em .5em .8em;text-decoration:none;background-color:${l};color:${a};}#ecoindex-badge-letter{align-items:center;border-radius:50%;color:#fff;display:flex;font-size:11px;font-weight:700;height:22px;justify-content:center;width:22px;background-color:${e}};`;return t.append(n),t})(u);r.appendChild(t),n.appendChild(o),n.appendChild(r),n.style.display=""}))})()})(); -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | ] 6 | ] 7 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecoindexbadge", 3 | "version": "1.0.0", 4 | "description": "[![](https://data.jsdelivr.com/v1/package/gh/cnumr/ecoindex_badge/badge)](https://www.jsdelivr.com/package/gh/cnumr/ecoindex_badge)", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack", 9 | "start": "webpack serve", 10 | "lint": "eslint ./ --ignore-path .eslintignore --ext .ts", 11 | "lint:fix": "eslint ./ --ignore-path .eslintignore --ext .ts --fix" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "@babel/cli": "^7.23.0", 18 | "@babel/core": "^7.23.2", 19 | "@babel/preset-env": "^7.23.2", 20 | "@typescript-eslint/eslint-plugin": "^6.9.0", 21 | "@typescript-eslint/parser": "^6.9.0", 22 | "babel-loader": "^9.1.2", 23 | "clean-webpack-plugin": "^4.0.0", 24 | "compression-webpack-plugin": "^10.0.0", 25 | "eslint": "^8.52.0", 26 | "eslint-config-prettier": "^9.0.0", 27 | "eslint-plugin-prettier": "^4.2.1", 28 | "eslint-webpack-plugin": "^4.0.1", 29 | "prettier": "^2.8.8", 30 | "prettier-loader": "^3.3.0", 31 | "terser-webpack-plugin": "^5.3.7", 32 | "ts-loader": "^9.5.0", 33 | "typescript": "^5.0.4", 34 | "webpack": "^5.89.0", 35 | "webpack-cli": "^5.0.1", 36 | "webpack-dev-server": "^4.13.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ts/ecoindex-badge.ts: -------------------------------------------------------------------------------- 1 | interface ApiResponseInterface { 2 | 'latest-result': { 3 | color: string; 4 | grade: string; 5 | }; 6 | } 7 | 8 | const baseUrl = 'https://bff.ecoindex.fr'; 9 | const badge: HTMLElement | null = document.getElementById('ecoindex-badge'); 10 | const LightTheme = 'light'; 11 | const isLightTheme: boolean = 12 | (badge?.getAttribute('data-theme') ?? LightTheme) === LightTheme; 13 | const currentUrl: string = window.location.href; 14 | const darkColor = '#0d2e38'; 15 | const textColorTheme: string = isLightTheme ? darkColor : '#fff'; 16 | const backgroundColorTheme: string = isLightTheme ? '#eee' : darkColor; 17 | 18 | /** 19 | * Méthode servant à créer le badge de résultat de mesure. 20 | */ 21 | const createBadgeLink = ( 22 | href: string, 23 | title: string, 24 | ariaLabel: string 25 | ): HTMLAnchorElement => { 26 | const badgeElement = document.createElement('a'); 27 | badgeElement.setAttribute('href', href); 28 | badgeElement.setAttribute('target', '_blank'); 29 | badgeElement.setAttribute('rel', 'noreferrer'); 30 | badgeElement.setAttribute('id', 'ecoindex-badge-link'); 31 | badgeElement.setAttribute('title', title); 32 | badgeElement.setAttribute('aria-label', ariaLabel); 33 | badgeElement.append('Ecoindex'); 34 | 35 | return badgeElement; 36 | }; 37 | 38 | /** 39 | * Methode servant à afficher la note ou des actions/informations pour déclencher une mesure. 40 | * @param {String} grade Note obtenu par l'API 41 | */ 42 | const createGrade = (grade: string) => { 43 | const gradeElement = document.createElement('span'); 44 | gradeElement.setAttribute('id', 'ecoindex-badge-letter'); 45 | gradeElement.append(grade); 46 | return gradeElement; 47 | }; 48 | 49 | /** 50 | * Méthode servant à générer le design du badge et ses enfants/alternatives. 51 | */ 52 | const createStyle = (gradeColor: string): HTMLStyleElement => { 53 | const styleElement: HTMLStyleElement = document.createElement('style'); 54 | const style = 55 | /* 56 | #ecoindex-badge { 57 | width: 108px; 58 | font-family: "Arial", sans-serif; 59 | } 60 | */ 61 | '#ecoindex-badge{font-family:Arial,sans-serif;width:108px}' + 62 | /* 63 | #ecoindex-badge-link { 64 | border-radius: 1.3em; 65 | padding: .5em .5em .5em .8em; 66 | text-decoration: none; 67 | display: flex; 68 | align-items: center; 69 | gap: 0.85em; 70 | font-size: 13px; 71 | background-color: = ${backgroundColorTheme}; 72 | color: ${textColorTheme}; 73 | } 74 | */ 75 | `#ecoindex-badge-link{align-items:center;border-radius:1.3em;display:flex;font-size:13px;gap:.85em;padding:.5em .5em .5em .8em;text-decoration:none;background-color:${backgroundColorTheme};color:${textColorTheme};}` + 76 | /* 77 | #ecoindex-badge-letter { 78 | height: 22px; 79 | width: 22px; 80 | display: flex; 81 | align-items: center; 82 | justify-content: center; 83 | color: #FFF; 84 | border-radius: 50%; 85 | font-size: 11px ; 86 | font-weight: bold; 87 | background-color: ${gradeColor}; 88 | } 89 | */ 90 | `#ecoindex-badge-letter{align-items:center;border-radius:50%;color:#fff;display:flex;font-size:11px;font-weight:700;height:22px;justify-content:center;width:22px;background-color:${gradeColor}};`; 91 | styleElement.append(style); 92 | return styleElement; 93 | }; 94 | 95 | /** 96 | * Méthode appelée lorsque le script se charge. Il ajoute à la balise div#ecoindex-badge le badge de ecoindex. 97 | */ 98 | const displayBadge = (): void => { 99 | if (!badge) { 100 | return; 101 | } 102 | 103 | badge.style.display = 'none'; 104 | const LinkHref = `${baseUrl}/redirect/?url=${currentUrl}`; 105 | const title = 'Résultat analyse écoIndex : '; 106 | const ariaLabel = 'badge écoIndex : '; 107 | let linkTitle = title + 'Page non mesurée'; 108 | let linkAriaLabel = ariaLabel + 'page non mesurée'; 109 | let gradeLetter = '?'; 110 | let gradeColor = 'grey'; 111 | fetch(`${baseUrl}/api/results/?url=${currentUrl}`, { method: `GET` }) 112 | .then((response) => { 113 | if (response.ok) { 114 | return response.json(); 115 | } 116 | return new Promise((resolve, reject) => 117 | reject(`Erreur dans l'appel à l'écoindex de : ${currentUrl}`) 118 | ); 119 | }) 120 | .then((data: ApiResponseInterface) => { 121 | const lastResult = data['latest-result']; 122 | const grade = lastResult.grade; 123 | linkTitle = title + grade; 124 | linkAriaLabel = ariaLabel + 'résultat analyse = ' + grade; 125 | gradeLetter = grade; 126 | gradeColor = lastResult.color; 127 | }) 128 | .catch(console.error) 129 | .finally(() => { 130 | const grade = createGrade(gradeLetter); 131 | const a = createBadgeLink(LinkHref, linkTitle, linkAriaLabel); 132 | const style = createStyle(gradeColor); 133 | a.appendChild(grade); 134 | badge.appendChild(style); 135 | badge.appendChild(a); 136 | badge.style.display = ''; 137 | }); 138 | }; 139 | 140 | displayBadge(); 141 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | // extend your base config to share compilerOptions, etc 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | // ensure that nobody can accidentally use this config for a build 6 | //"noEmit": true 7 | }, 8 | "include": [ 9 | // whatever paths you intend to lint 10 | "src" 11 | ] 12 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*.ts"], 3 | "compilerOptions": { 4 | "target": "ES6", 5 | "module": "ESNext", 6 | 7 | /* Strict Type-Checking Options */ 8 | "strict": true /* Enable all strict type-checking options. */, 9 | "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 10 | "strictNullChecks": true /* Enable strict null checks. */, 11 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 12 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 13 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 14 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 15 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 16 | 17 | /* Additional Checks */ 18 | "noUnusedLocals": true /* Report errors on unused locals. */, 19 | // "noUnusedParameters": true /* Report errors on unused parameters. */, 20 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 21 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | const CompressionPlugin = require("compression-webpack-plugin"); 4 | const TerserPlugin = require("terser-webpack-plugin"); 5 | const ESLintPlugin = require('eslint-webpack-plugin'); 6 | 7 | module.exports = { 8 | resolve: { 9 | extensions: ['.js', '.ts'] 10 | }, 11 | entry: { 12 | main: path.join(__dirname, 'src/ts/ecoindex-badge.ts'), 13 | }, 14 | output: { 15 | path: path.join(__dirname, 'assets/js'), 16 | filename: 'ecoindex-badge.js', 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.(js)$/, 22 | exclude: /(node_modules)/, 23 | use: ['babel-loader'] 24 | }, 25 | { 26 | test: /\.ts/, 27 | exclude: /(node_modules)/, 28 | use: ['ts-loader'], 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new CleanWebpackPlugin(), 34 | new CompressionPlugin({ 35 | filename: "[path].br[query]", 36 | algorithm: "brotliCompress", 37 | test: /\.(js|css|html|svg)$/, 38 | compressionOptions: { level: 11 }, 39 | threshold: 10240, 40 | minRatio: 0.8, 41 | deleteOriginalAssets: false 42 | }), 43 | new ESLintPlugin({ 44 | extensions: ['ts'] 45 | }), 46 | ], 47 | optimization: { 48 | minimize: true, 49 | minimizer: [ 50 | new TerserPlugin({ 51 | test: /\.js$/i, 52 | terserOptions: { 53 | mangle: true 54 | } 55 | }) 56 | ] 57 | }, 58 | mode: "production", 59 | }; 60 | --------------------------------------------------------------------------------