├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── cypress ├── .gitignore ├── README.md ├── cypress.config.js ├── cypress │ ├── e2e │ │ └── import_category │ │ │ └── create_project.cy.js │ ├── fixtures │ │ └── fixtures.js │ ├── plugins │ │ └── index.js │ └── support │ │ ├── commands.js │ │ ├── e2e.js │ │ └── openrefine_api.js ├── package.json ├── run_headless.sh └── yarn.lock ├── module ├── MOD-INF │ ├── .gitignore │ ├── controller.js │ └── module.properties ├── externals │ └── suggest │ │ ├── css │ │ └── suggest-4_3.min.css │ │ └── suggest-4_3a.js ├── langs │ ├── translation-ar.json │ ├── translation-az.json │ ├── translation-bn.json │ ├── translation-bn_IN.json │ ├── translation-br.json │ ├── translation-ca.json │ ├── translation-ceb.json │ ├── translation-cs.json │ ├── translation-da.json │ ├── translation-de.json │ ├── translation-el.json │ ├── translation-en.json │ ├── translation-en_GB.json │ ├── translation-es.json │ ├── translation-eu.json │ ├── translation-fa.json │ ├── translation-fi.json │ ├── translation-fil.json │ ├── translation-fr.json │ ├── translation-he.json │ ├── translation-hi.json │ ├── translation-hu.json │ ├── translation-id.json │ ├── translation-it.json │ ├── translation-iu.json │ ├── translation-ja.json │ ├── translation-ko.json │ ├── translation-ml.json │ ├── translation-mr.json │ ├── translation-nb_NO.json │ ├── translation-nl.json │ ├── translation-pa.json │ ├── translation-pa_PK.json │ ├── translation-pl.json │ ├── translation-pt.json │ ├── translation-pt_BR.json │ ├── translation-pt_PT.json │ ├── translation-ro.json │ ├── translation-ru.json │ ├── translation-sv.json │ ├── translation-ta.json │ ├── translation-tl.json │ ├── translation-tr.json │ ├── translation-uk.json │ ├── translation-zh_Hans.json │ └── translation-zh_Hant.json ├── scripts │ ├── index │ │ ├── category-suggest.js │ │ ├── commons-importing-controller.js │ │ ├── commons-parsing-panel.html │ │ ├── commons-source-ui.js │ │ └── import-from-commons-form.html │ └── project │ │ └── thumbnail-renderer.js └── styles │ ├── commons-importing-controller.less │ ├── theme.less │ └── thumbnails.less ├── pom.xml └── src ├── assembly └── extension.xml ├── main └── java │ └── org │ └── openrefine │ └── extensions │ └── commons │ ├── functions │ ├── ExtractCategories.java │ └── ExtractFromTemplate.java │ ├── importer │ ├── CategoryWithDepth.java │ ├── CommonsImporter.java │ ├── CommonsImportingController.java │ ├── FileFetcher.java │ ├── FileRecord.java │ ├── FileRecordToRows.java │ └── RelatedCategoryFetcher.java │ └── utils │ └── WikitextParsingUtilities.java └── test ├── conf └── tests.xml └── java └── org └── openrefine └── extensions └── commons ├── functions ├── ExtractCategoriesTests.java └── ExtractFromTemplateTest.java ├── importer ├── CommonsImporterTest.java ├── CommonsImportingControllerTest.java ├── FileFetcherTest.java ├── FileRecordToRowsTest.java └── RelatedCategoryFetcherTest.java └── utils ├── HistoryEntryManagerStub.java ├── ProjectManagerStub.java └── RefineServletStub.java /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [openrefine] 4 | patreon: # Replace with a single Patreon username 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 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://donorbox.org/open-refine'] 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | # For java deps 7 | - package-ecosystem: maven 8 | directory: "/" 9 | schedule: 10 | interval: daily 11 | open-pull-requests-limit: 15 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | release: 12 | types: [created] 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Set up JDK 11 22 | uses: actions/setup-java@v2 23 | with: 24 | java-version: '11' 25 | distribution: 'temurin' 26 | cache: maven 27 | 28 | - name: Setup Node 29 | uses: actions/setup-node@v4 30 | with: 31 | node-version: '20' 32 | 33 | - name: Restore dependency cache 34 | uses: actions/cache@v4 35 | with: 36 | path: | 37 | cypress/openrefine-*.tar.gz 38 | **/node_modules 39 | key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} 40 | 41 | - name: Build with Maven 42 | run: mvn -B package 43 | 44 | - name: Install Cypress 45 | run: | 46 | cd ./cypress 47 | npm i -g yarn 48 | yarn install 49 | 50 | - name: Run Cypress tests 51 | run: ./cypress/run_headless.sh 52 | 53 | - name: Get release upload URL 54 | id: get_release_upload_url 55 | if: github.event_name == 'release' 56 | uses: bruceadams/get-release@v1.3.2 57 | env: 58 | GITHUB_TOKEN: ${{ github.token }} 59 | 60 | - name: Upload release asset 61 | id: upload-release-asset 62 | if: github.event_name == 'release' 63 | uses: shogo82148/actions-upload-release-asset@v1.7.5 64 | with: 65 | upload_url: ${{ steps.get_release_upload_url.outputs.upload_url }} 66 | asset_path: ./target/openrefine-commons-extension-${{ env.VERSION_STRING }}.zip 67 | asset_name: openrefine-commons-extension-${{ env.VERSION_STRING }}.zip 68 | asset_content_type: application/zip 69 | 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS ignored files 2 | .DS_Store 3 | 4 | # eclipse-related files 5 | .classpath 6 | .project 7 | .settings 8 | 9 | # maven output 10 | target/ 11 | test-output 12 | 13 | # dependencies pulled locally 14 | module/MOD-INF/lib/ 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2022, OpenRefine contributors. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wikimedia Commons Extension for OpenRefine 2 | 3 | 4 | This extension provides several helpful functionalities for OpenRefine users who want to edit (structured data of) **media files** (images, videos, PDFs...) on **[Wikimedia Commons](https://commons.wikimedia.org)**. For more info, documentation and how-tos about OpenRefine for Wikimedia Commons, see **https://commons.wikimedia.org/wiki/Commons:OpenRefine**. 5 | 6 | Features included in this extension: 7 | * Start an OpenRefine project by loading file names from one or more **Wikimedia Commons categories** (including category depth) 8 | * Add **columns** with Commons categories and/or M-ids of each file name 9 | * File names will already be **reconciled** when starting the project 10 | * A few dedicated **GREL commands** allow basic processing and extraction of Wikitext: `extractFromTemplate` and `value.extractCategories` 11 | * (In this extension's 0.1.1 release and later) Basic support for **file thumbnail previews** of existing Wikimedia Commons files. Thumbnails are displayed for some (but not all) file types/extensions. There is currently thumbnail support for jpeg, gif, png, djvu, pdf, svg, webm and ogv files. 12 | 13 | It works with **OpenRefine 3.6.x and later versions of OpenRefine**. It is not compatible with OpenRefine 3.5.x or earlier. *(OpenRefine supports editing Wikimedia Commons from version 3.6; this is not possible in earlier versions.)* 14 | 15 | *This extension was first released in October 2022. It has been funded by a [Wikimedia project grant](https://meta.wikimedia.org/wiki/Grants:Project/CS%26S/Structured_Data_on_Wikimedia_Commons_functionalities_in_OpenRefine).* 16 | 17 | ## How to use this extension 18 | 19 | ### Install this extension in OpenRefine 20 | 21 | Download the .zip file of the [latest release of this extension](https://github.com/OpenRefine/CommonsExtension/releases). 22 | Unzip this file and place the unzipped folder in your OpenRefine extensions folder. [Read more about installing extensions in OpenRefine's user manual](https://docs.openrefine.org/manual/installing#installing-extensions). 23 | 24 | 25 | 26 | When this extension is installed correctly, you will now see the additional option 'Wikimedia Commons' when starting a new project in OpenRefine. 27 | 28 | ### Start an OpenRefine project from one or more Wikimedia Commons categories 29 | 30 | After installing this extension, click the 'Wikimedia Commons' option to start a new project in OpenRefine. You will be prompted to add one or more [Wikimedia Commons categories](https://commons.wikimedia.org/wiki/Commons:Categories). 31 | 32 | 33 | 34 | There's no need to type the Category: prefix. 35 | 36 | You can specify category depth by typing or selecting a number in the input field after each category. Depth `0` means only files from the current category level; depth `1` will retrieve files from one sub-category level down, etc. 37 | 38 | Next, in the project preview screen (`Configure parsing options`), you can choose to also include a column with each file's M-id (unique [MediaInfo identifier](https://www.mediawiki.org/wiki/Extension:WikibaseMediaInfo#MediaInfo_Entity)) and/or Commons categories. 39 | 40 | File names will already be reconciled when your project starts. 41 | 42 | When you load larger categories (thousands of files) in a new project, OpenRefine will start slowly and will give you a memory warning. [This is a known issue](https://github.com/OpenRefine/CommonsExtension/issues/72). Wait for a bit; the project will eventually start. The Commons Extension has been tested with a project of more than 450,000 files. 43 | 44 | ### GREL commands to extract data from Wikitext 45 | 46 | The Wikimedia Commons Extension also enables two dedicated GREL commands, which help to extract specific information from the Wikitext of Wikimedia Commons files. *(GREL, General Refine Expression Language, is a dedicated scripting language used in OpenRefine for many flexible data operations. For a general reference on using GREL in OpenRefine, see https://docs.openrefine.org/manual/grelfunctions.)* 47 | 48 | Firstly, retrieve the Wikitext from a list of Commons files in your project. In the column menu of the reconciled file names' column, select `Edit column` > `Add column from reconciled values...` and select `Wikitext` in the resulting dialog window. 49 | 50 | From this new column with Wikitext, you can now extract values and categories as described below. Start by selecting `Edit column` > `Add column based on this column...` in the column menu. In the next dialog window, you can use various specific GREL commands: 51 | 52 | #### Extract values from template parameters: `extractFromTemplate` 53 | 54 | 55 | 56 | Use the following syntax: 57 | 58 | ``` 59 | extractFromTemplate(value, "BHL", "source")[0] 60 | ``` 61 | 62 | where you replace `BHL` with the name of the template (without curly brackets) and `source` with the parameter from which you want to extract the value. This GREL syntax will return the first (and usually the only) value of said parameter, e.g. `https://www.flickr.com/photos/biodivlibrary/10329116385`. 63 | 64 | #### Extract Wikimedia Commons categories: `value.extractCategories` 65 | 66 | 67 | 68 | Use the following syntax: 69 | 70 | ``` 71 | value.extractCategories().join('#') 72 | ``` 73 | 74 | This GREL syntax will return all categories mentioned in the Wikitext, separated by the `#` character, which you can then use to split the resulting cell further as needed. 75 | 76 | ## Development 77 | 78 | ### Building from source 79 | 80 | Run 81 | ``` 82 | mvn package 83 | ``` 84 | 85 | This creates a zip file in the `target` folder, which can then be [installed in OpenRefine](https://docs.openrefine.org/manual/installing#installing-extensions). 86 | 87 | ### Developing it 88 | 89 | To avoid having to unzip the extension in the corresponding directory every time you want to test it, you can also use another set up: simply create a symbolic link from your extensions folder in OpenRefine to the local copy of this repository. With this setup, you do not need to run `mvn package` when making changes to the extension, but you will still to compile it with `mvn compile` if you are making changes to Java files, and restart OpenRefine if you make changes to any files. 90 | 91 | ### Releasing it 92 | 93 | - Make sure you are on the `master` branch and it is up to date (`git pull`) 94 | - Open `pom.xml` and set the version to the desired version number, such as `0.1.0` 95 | - Commit and push those changes to master 96 | - Add a corresponding git tag, with `git tag -a v0.1.0 -m "Version 0.1.0"` (when working from GitHub Desktop, you can follow [this process](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/managing-commits/managing-tags) and manually add the `v0.1.0` tag with the description `Version 0.1.0`) 97 | - Push the tag to GitHub: `git push --tags` (in GitHub Desktop, just push again) 98 | - Create a new release on GitHub at https://github.com/OpenRefine/CommonsExtension/releases/new, providing a release title (such as "Commons extension 0.1.0") and a description of the features in this release. 99 | - Open `pom.xml` and set the version to the expected next version number, followed by `-SNAPSHOT`. For instance, if you just released 0.1.0, you could set `0.1.1-SNAPSHOT` 100 | - Commit and push those changes. 101 | -------------------------------------------------------------------------------- /cypress/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .yarn/ 3 | .yarnrc.yml 4 | openrefine* 5 | -------------------------------------------------------------------------------- /cypress/README.md: -------------------------------------------------------------------------------- 1 | # Commons extension E2E tests 2 | 3 | Inspired from [OpenRefine's own Cypress test suite](https://openrefine.org/docs/technical-reference/functional-tests) 4 | 5 | Install the dependencies with `yarn install`. 6 | 7 | Run the test suite with `yarn run cypress open`. 8 | 9 | This requires a running instance of OpenRefine with the CommonsExtension installed at `http://localhost:3333/`. 10 | -------------------------------------------------------------------------------- /cypress/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | keystrokeDelay: 0, 5 | viewportWidth: 1280, 6 | viewportHeight: 768, 7 | retries: { 8 | runMode: 3, 9 | openMode: 0, 10 | }, 11 | env: { 12 | OPENREFINE_URL: 'http://localhost:3333', 13 | DISABLE_PROJECT_CLEANUP: 0, 14 | }, 15 | e2e: { 16 | experimentalRunAllSpecs: true, 17 | // We've imported your old cypress plugins here. 18 | // You may want to clean this up later by importing these. 19 | setupNodeEvents(on, config) { 20 | return require('./cypress/plugins/index.js')(on, config) 21 | }, 22 | specPattern: './cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /cypress/cypress/e2e/import_category/create_project.cy.js: -------------------------------------------------------------------------------- 1 | describe(__filename, function () { 2 | afterEach(() => { 3 | cy.addProjectForDeletion(); 4 | }); 5 | 6 | it('Test the create project from a Commons category', function () { 7 | cy.visitOpenRefine(); 8 | cy.navigateTo('Create project'); 9 | cy.get('#create-project-ui-source-selection-tabs > a') 10 | .contains('Wikimedia Commons') 11 | .click(); 12 | // enter a category name 13 | cy.get( 14 | 'input.category-input-box' 15 | ).type('Official OpenRefine logos'); 16 | cy.get( 17 | '.fbs-item-name' 18 | ) 19 | .contains('Official OpenRefine logos') 20 | .click(); 21 | 22 | cy.get( 23 | '.create-project-ui-source-selection-tab-body.selected button.button-primary' 24 | ) 25 | .contains('Next »') 26 | .click(); 27 | 28 | // then ensure we are on the preview page 29 | cy.get('.create-project-ui-panel').contains('Configure parsing options'); 30 | 31 | // preview and click next 32 | cy.get('button[bind="createProjectButton"]') 33 | .contains('Create project »') 34 | .click(); 35 | cy.waitForProjectTable(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /cypress/cypress/fixtures/fixtures.js: -------------------------------------------------------------------------------- 1 | 2 | const fixtures = { 3 | }; 4 | 5 | export default fixtures; 6 | -------------------------------------------------------------------------------- /cypress/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // / 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | /** 15 | * @type {Cypress.PluginConfig} 16 | */ 17 | 18 | module.exports = (on, config) => { 19 | // `on` is used to hook into various events Cypress emits 20 | // `config` is the resolved Cypress config 21 | return config; 22 | }; 23 | -------------------------------------------------------------------------------- /cypress/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | import 'cypress-file-upload'; 12 | 13 | // /** 14 | // * Reconcile a column 15 | // * Internally using the "apply" behavior for not having to go through the whole user interface 16 | // */ 17 | // Cypress.Commands.add('reconcileColumn', (columnName, autoMatch = true) => { 18 | // cy.setPreference( 19 | // 'reconciliation.standardServices', 20 | // encodeURIComponent( 21 | // JSON.stringify([ 22 | // { 23 | // name: 'CSV Reconciliation service', 24 | // identifierSpace: 'http://localhost:8000/', 25 | // schemaSpace: 'http://localhost:8000/', 26 | // defaultTypes: [], 27 | // view: { url: 'http://localhost:8000/view/{{id}}' }, 28 | // preview: { 29 | // width: 500, 30 | // url: 'http://localhost:8000/view/{{id}}', 31 | // height: 350, 32 | // }, 33 | // suggest: { 34 | // entity: { 35 | // service_url: 'http://localhost:8000', 36 | // service_path: '/suggest', 37 | // flyout_service_url: 'http://localhost:8000', 38 | // flyout_sercice_path: '/flyout', 39 | // }, 40 | // }, 41 | // url: 'http://localhost:8000/reconcile', 42 | // ui: { handler: 'ReconStandardServicePanel', access: 'jsonp' }, 43 | // }, 44 | // ]) 45 | // ) 46 | // ).then(() => { 47 | // const apply = [ 48 | // { 49 | // op: 'core/recon', 50 | // engineConfig: { 51 | // facets: [], 52 | // mode: 'row-based', 53 | // }, 54 | // columnName: columnName, 55 | // config: { 56 | // mode: 'standard-service', 57 | // service: 'http://localhost:8000/reconcile', 58 | // identifierSpace: 'http://localhost:8000/', 59 | // schemaSpace: 'http://localhost:8000/', 60 | // type: { 61 | // id: '/csv-recon', 62 | // name: 'CSV-recon', 63 | // }, 64 | // autoMatch: autoMatch, 65 | // columnDetails: [], 66 | // limit: 0, 67 | // }, 68 | // description: 'Reconcile cells in column species to type /csv-recon', 69 | // }, 70 | // ]; 71 | // cy.get('a#or-proj-undoRedo').click(); 72 | // cy.get('#refine-tabs-history .history-panel-controls') 73 | // .contains('Apply') 74 | // .click(); 75 | // cy.get('.dialog-container .history-operation-json').invoke( 76 | // 'val', 77 | // JSON.stringify(apply) 78 | // ); 79 | // cy.get('.dialog-container button[bind="applyButton"]').click(); 80 | // }); 81 | // }); 82 | 83 | /** 84 | * Reconcile a column 85 | * Internally using the "apply" behavior for not having to go through the whole user interface 86 | */ 87 | Cypress.Commands.add('assertColumnIsReconciled', (columnName) => { 88 | cy.get( 89 | `table.data-table thead th[title="${columnName}"] div.column-header-recon-stats-matched` 90 | ).should('to.exist'); 91 | }); 92 | 93 | /** 94 | * Return the .facets-container for a given facet name 95 | */ 96 | Cypress.Commands.add('getFacetContainer', (facetName) => { 97 | return cy 98 | .get( 99 | `#refine-tabs-facets .facets-container .facet-container span[bind="titleSpan"]:contains("${facetName}")`, 100 | { log: false } 101 | ) 102 | .parentsUntil('.facets-container', { log: false }); 103 | }); 104 | 105 | Cypress.Commands.add('getNumericFacetContainer', (facetName) => { 106 | return cy 107 | .get( 108 | `#refine-tabs-facets .facets-container .facet-container span[bind="facetTitle"]:contains("${facetName}")`, 109 | { log: false } 110 | ) 111 | .parentsUntil('.facets-container', { log: false }); 112 | }); 113 | 114 | /** 115 | * Edit a cell, for a given row index, a column name and a value 116 | */ 117 | Cypress.Commands.add('editCell', (rowIndex, columnName, value) => { 118 | cy.getCell(rowIndex, columnName) 119 | .trigger('mouseover') 120 | .find('button.data-table-cell-edit') 121 | .click(); 122 | cy.get('.menu-container.data-table-cell-editor textarea').type(value); 123 | cy.get('.menu-container button[bind="okButton"]').click(); 124 | }); 125 | 126 | /** 127 | * Ensure a textarea have a value that id equal to the JSON given as parameter 128 | */ 129 | Cypress.Commands.add('assertTextareaHaveJsonValue', (selector, json) => { 130 | cy.get(selector).then((el) => { 131 | // expected json needs to be parsed / restringified, to avoid inconsitencies about spaces and tabs 132 | const present = JSON.parse(el.val()); 133 | cy.expect(JSON.stringify(present)).to.equal(JSON.stringify(json)); 134 | }); 135 | }); 136 | 137 | /** 138 | * Open OpenRefine 139 | */ 140 | Cypress.Commands.add('visitOpenRefine', (options) => { 141 | cy.visit(Cypress.env('OPENREFINE_URL'), options); 142 | }); 143 | 144 | Cypress.Commands.add('createProjectThroughUserInterface', (fixtureFile) => { 145 | cy.navigateTo('Create project'); 146 | 147 | const uploadFile = { filePath: fixtureFile, mimeType: 'application/csv' }; 148 | cy.get( 149 | '.create-project-ui-source-selection-tab-body.selected input[type="file"]' 150 | ).attachFile(uploadFile); 151 | cy.get( 152 | '.create-project-ui-source-selection-tab-body.selected button.button-primary' 153 | ).click(); 154 | }); 155 | 156 | /** 157 | * Cast a whole column to the given type, using Edit Cell / Common transform / To {type} 158 | */ 159 | Cypress.Commands.add('castColumnTo', (selector, target) => { 160 | cy.get('body[ajax_in_progress="false"]'); 161 | cy.get( 162 | '.data-table th:contains("' + selector + '") .column-header-menu' 163 | ).click(); 164 | 165 | const targetAction = 'To ' + target; 166 | 167 | cy.get('body > .menu-container').eq(0).contains('Edit cells').click(); 168 | cy.get('body > .menu-container').eq(1).contains('Common transforms').click(); 169 | cy.get('body > .menu-container').eq(2).contains(targetAction).click(); 170 | }); 171 | 172 | /** 173 | * Return the td element for a given row index and column name 174 | */ 175 | Cypress.Commands.add('getCell', (rowIndex, columnName) => { 176 | const cssRowIndex = rowIndex + 1; 177 | // first get the header, to know the cell index 178 | cy.get(`table.data-table thead th[title="${columnName}"]`).then(($elem) => { 179 | // there are 3 td at the beginning of each row 180 | const columnIndex = $elem.index() + 3; 181 | return cy.get( 182 | `table.data-table tbody tr:nth-child(${cssRowIndex}) td:nth-child(${columnIndex})` 183 | ); 184 | }); 185 | }); 186 | 187 | /** 188 | * Make an assertion about the content of a cell, for a given row index and column name 189 | */ 190 | Cypress.Commands.add('assertCellEquals', (rowIndex, columnName, value) => { 191 | const cssRowIndex = rowIndex + 1; 192 | // first get the header, to know the cell index 193 | cy.get(`table.data-table thead th[title="${columnName}"]`).then(($elem) => { 194 | // there are 3 td at the beginning of each row 195 | const columnIndex = $elem.index() + 3; 196 | cy.get( 197 | `table.data-table tbody tr:nth-child(${cssRowIndex}) td:nth-child(${columnIndex}) div.data-table-cell-content div > span` 198 | ).should(($cellSpan) => { 199 | if (value == null) { 200 | // weird, "null" is returned as a string in this case, bug in Chai ? 201 | expect($cellSpan.text()).equals('null'); 202 | } else { 203 | expect($cellSpan.text()).equals(value); 204 | } 205 | }); 206 | }); 207 | }); 208 | 209 | /** 210 | * Make an assertion about the content of the whole grid 211 | * The values parameter is the same as the one provided in fixtures 212 | * Example 213 | * cy.assertGridEquals( 214 | * [ 215 | * ['Column A', 'Column B', 'Column C'], 216 | * ['Row 0 A', 'Row 0 B', 'Row 0 C'], 217 | * ['Row 1 A', 'Row 1 B', 'Row 1 C'] 218 | * ] 219 | * ) 220 | */ 221 | Cypress.Commands.add('assertGridEquals', (values) => { 222 | /** 223 | * This assertion is performed inside a should() method 224 | * So it will be retried until the timeout expires 225 | * "Should()" expect assertions to be made, so promises can't be used 226 | * Hence the use of Jquery with the Cypress.$ wrapper, to collect values for headers and grid cells 227 | */ 228 | cy.get('table.data-table').should((table) => { 229 | const headers = Cypress.$('table.data-table th') 230 | .filter(function (index, element) { 231 | return element.innerText != 'All'; 232 | }) 233 | .map(function (index, element) { 234 | return element.innerText; 235 | }) 236 | .get(); 237 | 238 | const cells = Cypress.$('table.data-table tbody tr') 239 | .map(function (i, el) { 240 | const innerTexts = Cypress.$('td', el).filter(index => index > 2) 241 | .map(function (index, element) { 242 | return element.querySelector('div.data-table-cell-content div > span').innerText; 243 | }).get(); 244 | return [ innerTexts 245 | .map(function (innerText) { 246 | // a nulled cell value is exposed in the DOM as the string "null" 247 | return innerText === 'null' ? null : innerText 248 | }) ]; 249 | }) 250 | .get(); 251 | const fullTable = [headers, ...cells]; 252 | expect(fullTable).to.deep.equal(values); 253 | }); 254 | }); 255 | 256 | /** 257 | * Navigate to one of the entries of the main left menu of OpenRefine (Create project, Open Project, Import Project, Language Settings) 258 | */ 259 | Cypress.Commands.add('navigateTo', (target) => { 260 | cy.get('#action-area-tabs li').contains(target).click(); 261 | }); 262 | 263 | /** 264 | * Wait for OpenRefine to finish an Ajax load 265 | * 266 | * @deprecated 267 | * 268 | * NOTE: This command is unreliable because if you call it after starting an operation e.g. with a click(), the loading 269 | * indicator may have come and gone already by the time waitForOrOperation() is called, causing the cypress test to 270 | * wait forever on ajax_in_progress=true until it fails due to timeout. 271 | * 272 | */ 273 | Cypress.Commands.add('waitForOrOperation', () => { 274 | cy.get('body[ajax_in_progress="true"]'); 275 | cy.get('body[ajax_in_progress="false"]'); 276 | }); 277 | 278 | /** 279 | * Wait for OpenRefine parsing options to be updated 280 | * 281 | * @deprecated 282 | * 283 | * NOTE: This command is unreliable because if you call it after starting an operation e.g. with a click(), the loading 284 | * indicator may have come and gone already by the time waitForImportUpdate() is called, causing the cypress test to 285 | * wait forever on ('#or-import-updating').should('be.visible') until it fails due to timeout. 286 | * 287 | */ 288 | Cypress.Commands.add('waitForImportUpdate', () => { 289 | cy.get('#or-import-updating').should('be.visible'); 290 | cy.get('#or-import-updating').should('not.be.visible'); 291 | cy.wait(1500); // eslint-disable-line 292 | }); 293 | 294 | /** 295 | * Utility method to fill something into the expression input 296 | * Need to wait for OpenRefine to preview the result, hence the cy.wait 297 | */ 298 | Cypress.Commands.add('typeExpression', (expression, options = {}) => { 299 | cy.get('textarea.expression-preview-code', options).type(expression); 300 | const expectedText = expression.length <= 30 ? expression : `${expression.substring(0, 30)} ...`; 301 | cy.get('tbody > tr:nth-child(1) > td:nth-child(3)', options).should('contain', expectedText); 302 | }); 303 | 304 | /** 305 | * Delete a column from the grid 306 | */ 307 | Cypress.Commands.add('deleteColumn', (columnName) => { 308 | cy.get('.data-table th[title="' + columnName + '"]').should('exist'); 309 | cy.columnActionClick(columnName, ['Edit column', 'Remove this column']); 310 | cy.get('.data-table th[title="' + columnName + '"]').should('not.exist'); 311 | }); 312 | 313 | /** 314 | * Wait until a dialog panel appear 315 | */ 316 | Cypress.Commands.add('waitForDialogPanel', () => { 317 | cy.get('body > .dialog-container > .dialog-frame').should('be.visible'); 318 | }); 319 | 320 | /** 321 | * Click on the OK button of a dialog panel 322 | */ 323 | Cypress.Commands.add('confirmDialogPanel', () => { 324 | cy.get( 325 | 'body > .dialog-container > .dialog-frame .dialog-footer button[bind="okButton"]' 326 | ).click(); 327 | cy.get('body > .dialog-container > .dialog-frame').should('not.exist'); 328 | }); 329 | 330 | /** 331 | * Click on the Cancel button of a dialog panel 332 | */ 333 | Cypress.Commands.add('cancelDialogPanel', () => { 334 | cy.get( 335 | 'body > .dialog-container > .dialog-frame .dialog-footer button[bind="cancelButton"]' 336 | ).click(); 337 | cy.get('body > .dialog-container > .dialog-frame').should('not.exist'); 338 | }); 339 | 340 | /** 341 | * Will click on a menu entry for a given column name 342 | */ 343 | Cypress.Commands.add('columnActionClick', (columnName, actions) => { 344 | cy.get('body[ajax_in_progress="false"]'); // OR must not be loading at the moment, column headers will be detached from the dom 345 | cy.get( 346 | '.data-table th:contains("' + columnName + '") .column-header-menu' 347 | ).click(); 348 | 349 | for (let i = 0; i < actions.length; i++) { 350 | cy.get('body > .menu-container').eq(i).contains(actions[i]).click(); 351 | } 352 | cy.get('body[ajax_in_progress="false"]'); 353 | }); 354 | 355 | /** 356 | * Go to a project, given it's id 357 | */ 358 | Cypress.Commands.add('visitProject', (projectId) => { 359 | cy.visit(Cypress.env('OPENREFINE_URL') + '/project?project=' + projectId); 360 | cy.get('#project-title').should('exist'); 361 | }); 362 | 363 | /** 364 | * Load a new project in OpenRefine, and open the project 365 | * The fixture can be 366 | * * an arbitrary array that will be loaded in the grid. The first row is for the columns names 367 | * * a file referenced in fixtures.js (food.mini | food.small) 368 | */ 369 | Cypress.Commands.add( 370 | 'loadAndVisitProject', 371 | (fixture, projectName = Cypress.currentTest.title +'-'+Date.now()) => { 372 | cy.loadProject(fixture, projectName, "fooTag").then((projectId) => { 373 | cy.visit(Cypress.env('OPENREFINE_URL') + '/project?project=' + projectId); 374 | cy.waitForProjectTable(); 375 | }); 376 | } 377 | ); 378 | 379 | Cypress.Commands.add('waitForProjectTable', (numRows) => { 380 | cy.url().should('contain', '/project?project=') 381 | cy.get('#left-panel', { log: false }).should('be.visible'); 382 | cy.get('#right-panel', { log: false }).should('be.visible'); 383 | cy.get('#project-title').should('exist'); 384 | cy.get(".data-table").find("tr").its('length').should('be.gte', 0); 385 | if (numRows) { 386 | cy.get('#summary-bar').should('to.contain', numRows+' rows'); 387 | } 388 | }); 389 | 390 | Cypress.Commands.add('assertNotificationContainingText', (text) => { 391 | cy.get('#notification-container').should('be.visible'); 392 | cy.get('#notification').should('be.visible').should('to.contain', text); 393 | }); 394 | 395 | Cypress.Commands.add( 396 | 'assertCellNotString', 397 | (rowIndex, columnName, expectedType) => { 398 | cy.getCell(rowIndex, columnName) 399 | .find('.data-table-value-nonstring') 400 | .should('to.exist'); 401 | } 402 | ); 403 | 404 | /** 405 | * Performs drag and drop on target and source item 406 | * sourcSelector jquery selector for the element to be dragged 407 | * targetSelector jquery selector for the element to be dropped on 408 | * position position relative to the target element to perform the drop 409 | */ 410 | Cypress.Commands.add('dragAndDrop', (sourceSelector, targetSelector, position = 'center') => { 411 | cy.get(sourceSelector).trigger('mousedown', { which: 1 }); 412 | 413 | cy.get(targetSelector) // eslint-disable-line 414 | .trigger('mousemove',position) 415 | .trigger('mouseup', { force: true }); 416 | }); 417 | 418 | Cypress.Commands.add( 419 | 'loadAndVisitSampleJSONProject', 420 | (projectName, fixture) => { 421 | cy.visitOpenRefine(); 422 | cy.navigateTo('Create project'); 423 | cy.get('#create-project-ui-source-selection-tabs > a') 424 | .contains('Clipboard') 425 | .click(); 426 | 427 | cy.get('textarea').invoke('val', fixture); 428 | cy.get( 429 | '.create-project-ui-source-selection-tab-body.selected button.button-primary' 430 | ) 431 | .contains('Next »') 432 | .click(); 433 | cy.get( 434 | '.default-importing-wizard-header input[bind="projectNameInput"]' 435 | ).clear(); 436 | cy.get( 437 | '.default-importing-wizard-header input[bind="projectNameInput"]' 438 | ).type(projectName); 439 | // need to disable es-lint as force is required to true, if not then 440 | // cypress won't detect the element due to `position:fixed` and overlays 441 | cy.get('[data-cy=element0]') // eslint-disable-line 442 | .first() 443 | .scrollIntoView() 444 | .click({ force: true }); 445 | 446 | // wait for preview and click next to create the project 447 | cy.get('div[bind="dataPanel"] table.data-table').should('to.exist'); 448 | cy.get('.default-importing-wizard-header button[bind="nextButton"]') 449 | .contains('Create project »') 450 | .click(); 451 | cy.waitForProjectTable(); 452 | } 453 | ); 454 | -------------------------------------------------------------------------------- /cypress/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | import './openrefine_api'; 19 | 20 | let token; 21 | let loadedProjectIds = []; 22 | // Hide fetch/XHR requests 23 | const app = window.top; 24 | if (!app.document.head.querySelector('[data-hide-command-log-request]')) { 25 | const style = app.document.createElement('style'); 26 | style.innerHTML = 27 | '.command-name-request, .command-name-xhr { display: none }'; 28 | style.setAttribute('data-hide-command-log-request', ''); 29 | 30 | app.document.head.appendChild(style); 31 | } 32 | 33 | beforeEach(() => { 34 | cy.wrap(token, { log: false }).as('token'); 35 | cy.wrap(token, { log: false }).as('deletetoken'); 36 | cy.wrap(loadedProjectIds, { log: false }).as('loadedProjectIds'); 37 | }); 38 | 39 | afterEach(() => { 40 | // DISABLE_PROJECT_CLEANUP is used to disable projects deletion 41 | // Mostly used in CI/CD for performances 42 | if(parseInt(Cypress.env('DISABLE_PROJECT_CLEANUP')) != 1){ 43 | cy.cleanupProjects(); 44 | } 45 | }); 46 | 47 | before(() => { 48 | cy.request( 49 | Cypress.env('OPENREFINE_URL') + '/command/core/get-csrf-token' 50 | ).then((response) => { 51 | // store one unique token for block of runs 52 | token = response.body.token; 53 | }); 54 | }); 55 | 56 | 57 | // See https://docs.cypress.io/api/events/catalog-of-events#Uncaught-Exceptions 58 | // We want to catch some Javascript exception that we consider harmless 59 | Cypress.on('uncaught:exception', (err, runnable) => { 60 | // This message occasionally appears with edge 61 | // Doesn't seems like a blocket, and the test should not fail 62 | if (err.message.includes("Cannot read property 'offsetTop' of undefined") 63 | || err.message.includes("Cannot read properties of undefined (reading 'offsetTop')") 64 | ) { 65 | return false 66 | } 67 | // we still want to ensure there are no other unexpected 68 | // errors, so we let them fail the test 69 | }) 70 | -------------------------------------------------------------------------------- /cypress/cypress/support/openrefine_api.js: -------------------------------------------------------------------------------- 1 | const fixtures = require('../fixtures/fixtures.js'); 2 | 3 | Cypress.Commands.add('addProjectForDeletion', () => { 4 | const path = '/project?project='; 5 | cy.url().then(($url) => { 6 | if($url.includes(path)) { 7 | const projectId = $url.split('=').slice(-1)[0]; 8 | cy.get('@loadedProjectIds', { log: true }).then((loadedProjectIds) => { 9 | loadedProjectIds.push(projectId); 10 | cy.wrap(loadedProjectIds, { log: true }) 11 | .as('loadedProjectIds'); 12 | }); 13 | } 14 | }) 15 | }); 16 | 17 | Cypress.Commands.add('setPreference', (preferenceName, preferenceValue) => { 18 | const openRefineUrl = Cypress.env('OPENREFINE_URL'); 19 | return cy 20 | .request(openRefineUrl + '/command/core/get-csrf-token') 21 | .then((response) => { 22 | return cy 23 | .request({ 24 | method: 'POST', 25 | url: `${openRefineUrl}/command/core/set-preference`, 26 | body: `name=${preferenceName}&value=${preferenceValue}&csrf_token=${response.body.token}`, 27 | form: false, 28 | headers: { 29 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 30 | }, 31 | }) 32 | .then((resp) => { 33 | cy.log( 34 | 'Set preference ' + 35 | preferenceName + 36 | ' with value ' + 37 | preferenceValue 38 | ); 39 | }); 40 | }); 41 | }); 42 | 43 | Cypress.Commands.add('deletePreference', (preferenceName) => { 44 | const openRefineUrl = Cypress.env('OPENREFINE_URL'); 45 | return cy 46 | .request(openRefineUrl + '/command/core/get-csrf-token') 47 | .then((response) => { 48 | return cy 49 | .request({ 50 | method: 'POST', 51 | url: `${openRefineUrl}/command/core/set-preference`, 52 | body: `name=${preferenceName}&csrf_token=${response.body.token}`, 53 | form: false, 54 | headers: { 55 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 56 | }, 57 | }) 58 | .then((resp) => { 59 | cy.log( 60 | 'Delete preference ' + 61 | preferenceName 62 | ); 63 | }); 64 | }); 65 | }); 66 | 67 | Cypress.Commands.add('importProject', (projectTarFile, projectName) => { 68 | const openRefineUrl = Cypress.env('OPENREFINE_URL'); 69 | 70 | 71 | cy.get('@token', { log: false }).then((token) => { 72 | // cy.request(Cypress.env('OPENREFINE_URL')+'/command/core/get-csrf-token').then((response) => { 73 | const openRefineFormat = 'text/line-based/*sv'; 74 | 75 | // the following code can be used to inject tags in created projects 76 | // It's conflicting though, breaking up the CSV files 77 | // It is a hack to parse out CSV files in the openrefine while creating a project with tags 78 | const options = { 79 | encoding: 'US-ASCII', 80 | separator: ',', 81 | ignoreLines: -1, 82 | headerLines: 1, 83 | skipDataLines: 0, 84 | limit: -1, 85 | storeBlankRows: true, 86 | guessCellValueTypes: false, 87 | processQuotes: true, 88 | quoteCharacter: '"', 89 | storeBlankCellsAsNulls: true, 90 | includeFileSources: false, 91 | includeArchiveFileName: false, 92 | trimStrings: false, 93 | projectName: openRefineProjectName, 94 | projectTags: [tagName], 95 | }; 96 | let postData; 97 | if (tagName == undefined) { 98 | postData = 99 | '------BOUNDARY\r\nContent-Disposition: form-data; name="project-file"; filename="test.csv"' + 100 | '\r\nContent-Type: "text/csv"\r\n\r\n' + 101 | content + 102 | '\r\n------BOUNDARY\r\nContent-Disposition: form-data; name="project-name"\r\n\r\n' + 103 | openRefineProjectName + 104 | '\r\n------BOUNDARY\r\nContent-Disposition: form-data; name="format"\r\n\r\n' + 105 | openRefineFormat + 106 | '\r\n------BOUNDARY--'; 107 | } else { 108 | postData = 109 | '------BOUNDARY\r\nContent-Disposition: form-data; name="project-file"; filename="test.csv"' + 110 | '\r\nContent-Type: "text/csv"\r\n\r\n' + 111 | content + 112 | '\r\n------BOUNDARY\r\nContent-Disposition: form-data; name="project-name"\r\n\r\n' + 113 | openRefineProjectName + 114 | '\r\n------BOUNDARY\r\nContent-Disposition: form-data; name="options"\r\n\r\n' + 115 | JSON.stringify(options) + 116 | '\r\n------BOUNDARY\r\nContent-Disposition: form-data; name="format"\r\n\r\n' + 117 | openRefineFormat + 118 | '\r\n------BOUNDARY--'; 119 | } 120 | 121 | cy.request({ 122 | method: 'POST', 123 | url: 124 | `${openRefineUrl}/command/core/create-project-from-upload?csrf_token=` + 125 | token, 126 | body: postData, 127 | headers: { 128 | 'content-type': 'multipart/form-data; boundary=----BOUNDARY', 129 | }, 130 | }).then((resp) => { 131 | const location = resp.allRequestResponses[0]['Response Headers'].location; 132 | const projectId = location.split('=').slice(-1)[0]; 133 | cy.log('Created OR project', projectId); 134 | 135 | cy.get('@loadedProjectIds', { log: false }).then((loadedProjectIds) => { 136 | loadedProjectIds.push(projectId); 137 | cy.wrap(loadedProjectIds, { log: false }) 138 | .as('loadedProjectIds') 139 | .then(() => { 140 | return projectId; 141 | }); 142 | }); 143 | }); 144 | }); 145 | 146 | 147 | }) 148 | 149 | Cypress.Commands.add('cleanupProjects', () => { 150 | const openRefineUrl = Cypress.env('OPENREFINE_URL'); 151 | cy.get('@deletetoken', { log: false }).then((token) => { 152 | cy.get('@loadedProjectIds', { log: false }).then((loadedProjectIds) => { 153 | for (const projectId of loadedProjectIds) { 154 | cy.request({ 155 | method: 'POST', 156 | url: 157 | `${openRefineUrl}/command/core/delete-project?csrf_token=` + token, 158 | body: { project: projectId }, 159 | form: true, 160 | }).then((resp) => { 161 | cy.log('Deleted OR project ' + projectId); 162 | }); 163 | } 164 | // clear loaded projects 165 | loadedProjectIds.length = 0; 166 | }); 167 | }); 168 | }); 169 | 170 | Cypress.Commands.add('loadProject', (fixture, projectName, tagName) => { 171 | const openRefineUrl = Cypress.env('OPENREFINE_URL'); 172 | const openRefineProjectName = projectName ? projectName : 'cypress-test'; 173 | 174 | let jsonFixture; 175 | let content; 176 | let escapedFields = []; 177 | const csv = []; 178 | 179 | if (fixture.includes('.csv')) { 180 | cy.fixture(fixture).then((value) => { 181 | content = value; 182 | }); 183 | } else { 184 | if (typeof fixture == 'string') { 185 | jsonFixture = fixtures[fixture]; 186 | } else { 187 | jsonFixture = fixture; 188 | } 189 | jsonFixture.forEach((row) => { 190 | escapedFields = []; 191 | row.forEach((field) => { 192 | escapedFields.push(field ? field.replaceAll('"','""') : field); 193 | }); 194 | csv.push('"' + escapedFields.join('","') + '"'); 195 | }); 196 | content = csv.join('\n'); 197 | } 198 | 199 | cy.get('@token', { log: false }).then((token) => { 200 | // cy.request(Cypress.env('OPENREFINE_URL')+'/command/core/get-csrf-token').then((response) => { 201 | const openRefineFormat = 'text/line-based/*sv'; 202 | 203 | // the following code can be used to inject tags in created projects 204 | // It's conflicting though, breaking up the CSV files 205 | // It is a hack to parse out CSV files in the openrefine while creating a project with tags 206 | const options = { 207 | encoding: 'UTF-8', 208 | separator: ',', 209 | ignoreLines: -1, 210 | headerLines: 1, 211 | skipDataLines: 0, 212 | limit: -1, 213 | storeBlankRows: true, 214 | guessCellValueTypes: false, 215 | processQuotes: true, 216 | quoteCharacter: '"', 217 | storeBlankCellsAsNulls: true, 218 | includeFileSources: false, 219 | includeArchiveFileName: false, 220 | trimStrings: false, 221 | projectName: openRefineProjectName, 222 | projectTags: [tagName], 223 | }; 224 | let postData; 225 | if (tagName == undefined) { 226 | postData = 227 | '------BOUNDARY\r\nContent-Disposition: form-data; name="project-file"; filename="test.csv"' + 228 | '\r\nContent-Type: "text/csv"\r\n\r\n' + 229 | content + 230 | '\r\n------BOUNDARY\r\nContent-Disposition: form-data; name="project-name"\r\n\r\n' + 231 | openRefineProjectName + 232 | '\r\n------BOUNDARY\r\nContent-Disposition: form-data; name="format"\r\n\r\n' + 233 | openRefineFormat + 234 | '\r\n------BOUNDARY--'; 235 | } else { 236 | postData = 237 | '------BOUNDARY\r\nContent-Disposition: form-data; name="project-file"; filename="test.csv"' + 238 | '\r\nContent-Type: "text/csv"\r\n\r\n' + 239 | content + 240 | '\r\n------BOUNDARY\r\nContent-Disposition: form-data; name="project-name"\r\n\r\n' + 241 | openRefineProjectName + 242 | '\r\n------BOUNDARY\r\nContent-Disposition: form-data; name="options"\r\n\r\n' + 243 | JSON.stringify(options) + 244 | '\r\n------BOUNDARY\r\nContent-Disposition: form-data; name="format"\r\n\r\n' + 245 | openRefineFormat + 246 | '\r\n------BOUNDARY--'; 247 | } 248 | 249 | cy.request({ 250 | method: 'POST', 251 | url: 252 | `${openRefineUrl}/command/core/create-project-from-upload?csrf_token=` + 253 | token, 254 | body: postData, 255 | headers: { 256 | 'content-type': 'multipart/form-data; boundary=----BOUNDARY', 257 | }, 258 | }).then((resp) => { 259 | const location = resp.allRequestResponses[0]['Response Headers'].location; 260 | const projectId = location.split('=').slice(-1)[0]; 261 | cy.log('Created OR project', projectId); 262 | 263 | cy.get('@loadedProjectIds', { log: false }).then((loadedProjectIds) => { 264 | loadedProjectIds.push(projectId); 265 | cy.wrap(loadedProjectIds, { log: false }) 266 | .as('loadedProjectIds') 267 | .then(() => { 268 | return projectId; 269 | }); 270 | }); 271 | }); 272 | }); 273 | }); 274 | -------------------------------------------------------------------------------- /cypress/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "commons-extension-cypress-test-suite", 3 | "version": "1.0.0", 4 | "description": "Cypress tests for OpenRefine's Commons extension", 5 | "license": "BSD-3-Clause", 6 | "author": "OpenRefine", 7 | "private": true, 8 | "engines": { 9 | "node": ">=16.0.0", 10 | "npm": ">=8.11.0", 11 | "yarn": ">=1.22.15" 12 | }, 13 | "scripts": { 14 | "test": "cypress run --browser electron --headless --quiet", 15 | "fix-lint": "prettier --write . && eslint --fix .", 16 | "lint": "prettier --check . && eslint ." 17 | }, 18 | "dependencies": { 19 | "cypress": "13.6.4", 20 | "cypress-file-upload": "^5.0.8" 21 | }, 22 | "devDependencies": { 23 | "eslint": "^8.56.0", 24 | "eslint-config-google": "^0.14.0", 25 | "eslint-config-prettier": "^9.1.0", 26 | "eslint-plugin-cypress": "^2.15.1", 27 | "prettier": "3.2.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cypress/run_headless.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This runs the Cypress tests in headless mode, for the CI 4 | 5 | set -e 6 | 7 | EXTENSION_NAME="commons" 8 | CYPRESS_BROWSER="chrome" 9 | CYPRESS_SPECS="cypress/e2e/**/*.cy.js" 10 | OPENREFINE_DIR="/tmp/openrefine_CommonsExtension" 11 | 12 | # Detect target OpenRefine version 13 | cd "$(dirname "$0")/.." 14 | EXTENSION_DIR=`pwd` 15 | OPENREFINE_VERSION=$(mvn org.apache.maven.plugins:maven-help-plugin:3.1.0:evaluate -Dexpression=openrefine.version -q -DforceStdout) 16 | 17 | echo "Setting up OpenRefine $OPENREFINE_VERSION" 18 | cd "$(dirname "$0")" 19 | rm -rf $OPENREFINE_DIR 20 | echo "Downloading OpenRefine" 21 | wget -N --quiet https://github.com/OpenRefine/OpenRefine/releases/download/$OPENREFINE_VERSION/openrefine-linux-$OPENREFINE_VERSION.tar.gz 22 | echo "Decompressing archive" 23 | tar -zxf openrefine-linux-$OPENREFINE_VERSION.tar.gz 24 | mv openrefine-$OPENREFINE_VERSION $OPENREFINE_DIR 25 | mkdir -p $OPENREFINE_DIR/webapp/extensions 26 | cd $OPENREFINE_DIR/webapp/extensions 27 | ln -s $EXTENSION_DIR $EXTENSION_NAME 28 | cd $OPENREFINE_DIR 29 | 30 | # Start OpenRefine 31 | echo "Starting OpenRefine" 32 | ./refine -x refine.headless=true -x refine.autoreload=false -x butterfly.autoreload=false -d /tmp/openrefine_commons_cypress > /dev/null & 33 | REFINE_PID="$!" 34 | 35 | # Wait for OpenRefine to start 36 | sleep 5 37 | if curl -s http://127.0.0.1:3333/ | grep "OpenRefine" > /dev/null 2>&1; then 38 | echo "OpenRefine is running" 39 | else 40 | echo "Waiting for OpenRefine to start..." 41 | sleep 10 42 | fi 43 | 44 | # Launch Cypress 45 | cd $EXTENSION_DIR/cypress 46 | if yarn run cypress run --browser $CYPRESS_BROWSER --spec "$CYPRESS_SPECS" --headless --quiet; then 47 | echo "All Cypress tests passed" 48 | RETURN_VALUE=0 49 | else 50 | echo "Cypress tests failed" 51 | RETURN_VALUE=1 52 | fi 53 | 54 | # Kill OpenRefine 55 | kill $REFINE_PID 56 | 57 | exit $RETURN_VALUE 58 | -------------------------------------------------------------------------------- /module/MOD-INF/.gitignore: -------------------------------------------------------------------------------- 1 | /classes/ 2 | -------------------------------------------------------------------------------- /module/MOD-INF/controller.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Controller for Commons extension. 3 | * 4 | * This is run in the Butterfly (ie Refine) server context using the Rhino 5 | * Javascript interpreter. 6 | */ 7 | 8 | var html = "text/html"; 9 | var encoding = "UTF-8"; 10 | var version = "0.3"; 11 | 12 | // Register our Javascript (and CSS) files to get loaded 13 | var ClientSideResourceManager = Packages.com.google.refine.ClientSideResourceManager; 14 | 15 | /* 16 | * Function invoked to initialize the extension. 17 | */ 18 | function init() { 19 | 20 | // Register our GREL functions so that they are visible in OpenRefine 21 | var CFR = Packages.com.google.refine.grel.ControlFunctionRegistry; 22 | 23 | CFR.registerFunction("extractCategories", new Packages.org.openrefine.extensions.commons.functions.ExtractCategories()); 24 | CFR.registerFunction("extractFromTemplate", new Packages.org.openrefine.extensions.commons.functions.ExtractFromTemplate()); 25 | 26 | // Register importer and exporter 27 | var IM = Packages.com.google.refine.importing.ImportingManager; 28 | 29 | IM.registerController( 30 | module, 31 | "commons-importing-controller", 32 | new Packages.org.openrefine.extensions.commons.importer.CommonsImportingController() 33 | ); 34 | 35 | // Script files to inject into /index page 36 | ClientSideResourceManager.addPaths( 37 | "index/scripts", 38 | module, 39 | [ 40 | "scripts/index/commons-importing-controller.js", 41 | "scripts/index/commons-source-ui.js", 42 | /* add suggest library from core */ 43 | "externals/suggest/suggest-4_3a.js", 44 | "scripts/index/category-suggest.js" 45 | ] 46 | ); 47 | 48 | // Script files to inject into /project page 49 | ClientSideResourceManager.addPaths( 50 | "project/scripts", 51 | module, 52 | [ 53 | "scripts/project/thumbnail-renderer.js" 54 | ] 55 | ); 56 | 57 | 58 | // Style files to inject into /index page 59 | ClientSideResourceManager.addPaths( 60 | "index/styles", 61 | module, 62 | [ 63 | "styles/commons-importing-controller.less", 64 | "externals/suggest/css/suggest-4_3.min.css" 65 | ] 66 | ); 67 | 68 | // Style files to inject into /project page 69 | ClientSideResourceManager.addPaths( 70 | "project/styles", 71 | module, 72 | [ 73 | "styles/thumbnails.less" 74 | ] 75 | ); 76 | 77 | } 78 | -------------------------------------------------------------------------------- /module/MOD-INF/module.properties: -------------------------------------------------------------------------------- 1 | name = commons 2 | description = Integration with Wikimedia Commons 3 | requires = core 4 | -------------------------------------------------------------------------------- /module/externals/suggest/css/suggest-4_3.min.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2012, Google Inc. 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are 8 | * met: 9 | * 10 | * * Redistributions of source code must retain the above copyright 11 | * notice, this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above 13 | * copyright notice, this list of conditions and the following disclaimer 14 | * in the documentation and/or other materials provided with the 15 | * distribution. 16 | * * Neither the name of Google Inc. nor the names of its 17 | * contributors may be used to endorse or promote products derived from 18 | * this software without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * Additional Licenses for Third Party components can be found here: 33 | * http://wiki.freebase.com/wiki/Freebase_Site_License 34 | * 35 | */ 36 | .fbs-reset,.fbs-reset h1,.fbs-reset h2,.fbs-reset h3,.fbs-reset h4,.fbs-reset h5,.fbs-reset h6,.fbs-reset p,.fbs-reset img,.fbs-reset dl,.fbs-reset dt,.fbs-reset dd,.fbs-reset ol,.fbs-reset ul,.fbs-reset li{margin:0;padding:0;border:0;outline:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline}.fbs-pane,.fbs-flyout-pane,li.fbs-nomatch,a.fbs-more-link,li.fbs-selected,.fbs-flyout-images,#fbs-topic-image{background:#fff}.fbs-suggestnew{color:#444}.fbs-pane,.fbs-flyout-pane,.fbs-flyout-subtitle,.fbs-topic-properties strong{color:#666}h3.fbs-topic-properties,.fbs-flyout-pane p{color:#444}.fbs-item-name,li.fbs-help-on-focus,li.fbs-nomatch,.fbs-nomatch-text,.fbs-flyout-pane h3,.fbs-properties-header{color:#333}.fbs-pane,.fbs-flyout-pane{border:1px solid #bbb;padding:2px}.fbs-flyout-pane{border-color:#ccc}.fbs-list,.fbs-list-icons,.fbs-flyout-content,.fbs-attribution,.fbs-header{background-color:#f5f5f5}.fbs-header{background:#fefefe}li.fbs-help-on-focus,li.fbs-nomatch{border-bottom:1px solid #dae3e9}.fbs-item-name{border:1px solid #f5f5f5}h1#fbs-flyout-title,li.fbs-selected .fbs-item-name{color:#f60}li.fbs-selected .fbs-item-name{border-color:#f60;background:#fff}.fbs-nomatch-text{border-bottom:1px solid #dae3e9;background:#f8f8f8}.fbs-suggestnew{background:#eee;border-top:1px solid #dae3e9}#fbs-flyout-title .fbs-flyout-label{color:#aaa}.fbs-citation{white-space:nowrap;color:#999;font-size:11px}#fbs-topic-image,.fbs-flyout-images{border:1px solid #a9a9a9}.fbs-suggestnew-button,.fbs-flyout-pane{border:1px solid #9a9a9a;color:#999}.fbs-suggestnew-button{color:#444}ul.fbs-list,.fbs-flyout-content,.fbs-attribution,.fbs-header{border:1px solid #dae3e9}.fbs-header{border-bottom:0}li.fbs-item{border-bottom:1px solid #dae3e9;list-style-type:none}.fbs-attribution{border-top:0}.fbs-pane,.fbs-flyout-pane{font-size:16px;font-family:Helvetica Neue,Arial,Helvetica,sans-serif}ul.fbs-list,.fbs-flyout-content,.fbs-attribution,div.fbs-header{font-size:62.5%}.fbs-pane strong,.fbs-flyout-pane strong{font-weight:bold}.fbs-flyout-content,.fbs-attribution{margin:2px}.fbs-flyout-content{margin-bottom:0}.fbs-attribution{margin-top:0}.fbs-pane{width:325px}.fbs-flyout-pane{width:319px;margin-left:3px}ul.fbs-list{max-height:320px;overflow:auto;overflow-x:hidden;border-bottom:0;border-top:0}.fbs-flyout-content,.fbs-attribution{padding:5px}.fbs-flyout-content:after{content:".";display:block;height:0;clear:both;visibility:hidden}li.fbs-help-on-focus,li.fbs-nomatch{padding:6px 8px 7px 6px;font-size:1.4em;line-height:1}li.fbs-more{padding:0;background:transparent}a.fbs-more-link{display:block;padding:4px;font-weight:bold;font-size:12px}.fbs-more .fbs-help{display:none}.fbs-header{font-weight:bold;padding:4px 6px;margin:2px 2px -2px 2px}.fbs-item-name label,.fbs-item-name span{font-size:.9em}.fbs-item-name label{color:black}.fbs-item-type,.fbs-item-name label,.fbs-item-name span{display:block;overflow:hidden;white-space:nowrap}.fbs-item-name{padding:2px 8px 1px 6px;font-size:1.4em;line-height:1.4em;background:#f4f8fb}.fbs-item-name strong{font-weight:bold}.fbs-item-type{color:#777;float:right;font-size:.7em;max-width:40%;padding-left:.25em}li.fbs-selected{cursor:pointer}.fbs-status{border:1px solid #dae3e9;padding:4px 5px;color:#000;font-size:.7em}li.fbs-nomatch{padding:0}.fbs-nomatch-text{display:block;font-weight:bold;line-height:1;font-size:.9em}.fbs-nomatch-text,.fbs-nomatch h3,ul.fbs-search-tips{padding:6px 8px 7px 6px}.fbs-nomatch h3{font-weight:bold;font-size:.9em}ul.fbs-search-tips li{list-style:disc;margin-left:1.6em;margin-bottom:.3em;font-size:.9em}.fbs-suggestnew{padding:.4em .3em .5em 8px}.fbs-suggestnew-button{cursor:pointer;padding:.2em .3em;margin-left:0!important;max-width:17em;font-size:.8em}.fbs-suggestnew-description{margin-bottom:.6em;font-size:.7em}.fbs-more-shortcut,.fbs-suggestnew-shortcut{margin-left:.4em;font-size:70%;color:#999}.fbs-placeholder{color:#99a;font-style:italic}.fbs-flyout-id{color:#999!important}h1#fbs-flyout-title{font-size:1.2em;font-weight:bold;margin-bottom:.5em;margin-top:.3em}h1#fbs-flyout-title .fbs-flyout-template-label{color:#999;font-size:.8em}#fbs-flyout-title .fbs-flyout-label{font-weight:normal}#fbs-topic-image{float:left;padding:1px;margin-right:5px;margin-bottom:5px}.fbs-flyout-images{float:left;margin:0 10px 0 0;padding:1px 0 1px 1px}.fbs-flyout-images img{float:left;margin-right:1px}.fbs-flyout-subtitle{font-size:1.1em;margin-bottom:.5em}.fbs-flyout-pane h3{font-size:1em;line-height:1.4;margin-bottom:.25em}.fbs-properties-header{font-size:1em;font-weight:bold;margin:.5em 0}h3.fbs-topic-properties{font-size:1.2em;font-weight:bold}.fbs-topic-properties strong{display:inline;font-size:.8em}.fbs-flyout-pane p{font-size:1.2em;line-height:1.4;max-height:10em;overflow:auto}p.fbs-flyout-image-true,h3.fbs-flyout-image-true,h1.fbs-flyout-image-true{margin-left:85px}.fbs-meta-info{margin-left:110px}#fbs-user-flyout li{margin-left:100px}#fbs-domain-flyout .fbs-meta-info{margin-left:145px}.fbs-flyout-list li{font-size:1em;margin-left:15px}#fbs-domain-flyout #fbs-flyout-title{margin-bottom:.5em}.fbs-attribution{padding-right:72px;background-image:url("//www.gstatic.com/freebase/img/freebase-cc-by-61x23.png");background-repeat:no-repeat;background-position:center right;min-height:15px}.fbs-flyout-types{font-style:italic;line-height:1;font-size:1.2em} 37 | -------------------------------------------------------------------------------- /module/langs/translation-ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/preparing": "جاري التحضير…", 3 | "commons-import/creating": "إنشاء المشروع…", 4 | "commons-import/import-by-category": "أدخل فئة واحدة أو أكثر من فئات ويكيميديا كومنز التي تريد استرداد الملفات منها:", 5 | "commons-import/import-nested-category": "‎عمق الفئة الفرعية:", 6 | "commons-import/remove-category": "إزالة هذه الفئة", 7 | "commons-import/add": "أضف فئة أخرى", 8 | "commons-import/next->": "التالي »", 9 | "commons-import/retrieving": "استرجاع الملفات حسب الفئة…", 10 | "commons-parsing/start-over": "«ابدأ من جديد", 11 | "commons-parsing/conf-pars": "تكوين خيارات التحليل", 12 | "commons-parsing/proj-name": "اسم المشروع", 13 | "commons-parsing/create-proj": "إنشاء مشروع »", 14 | "commons-source/alert-retrieve": "لم يتم تحديد الفئة", 15 | "commons-parsing/option": "خيارات :", 16 | "commons-parsing/discard": "صف (صفوف) من البيانات", 17 | "commons-parsing/limit-next": "تحميل على الأكثر", 18 | "commons-parsing/limit": "صف (صفوف) من البيانات", 19 | "commons-parsing/wiki-option": "خيارات خاصة بويكيميديا كومنز :", 20 | "commons-parsing/discard-next": "تجاهل القيمة", 21 | "commons-parsing/categories-column": "تضمين عمود بالفئات", 22 | "commons-parsing/mids-column": "تضمين عمود يحتوي على معرفات M", 23 | "commons-parsing/preview-button": "تحديث المعاينة", 24 | "commons-parsing/disable-auto-preview": "تعطيل المعاينة التلقائية", 25 | "commons-parsing/updating-preview": "تحديث المعاينة …" 26 | } 27 | -------------------------------------------------------------------------------- /module/langs/translation-az.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-bn.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-bn_IN.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-br.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-ca.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-ceb.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/remove-category": "Odebrat tuto kategorii", 3 | "commons-import/add": "Přidat další kategorii", 4 | "commons-import/next->": "Další »", 5 | "commons-import/retrieving": "Načítání souborů podle kategorií…", 6 | "commons-parsing/start-over": "« Začít znovu", 7 | "commons-parsing/create-proj": "Vytvořit projekt »", 8 | "commons-parsing/conf-pars": "Nastavit možnosti parsování", 9 | "commons-parsing/proj-name": "Názevt projektu", 10 | "commons-parsing/discard": "řádek/řádky/řádků dat", 11 | "commons-parsing/limit": "řádek/řádky/řádků dat", 12 | "commons-parsing/mids-column": "Zahrnout sloupec s M-ID", 13 | "commons-parsing/discard-next": "Vynechat počáteční(ch)", 14 | "commons-parsing/updating-preview": "Aktualizace náhledu…", 15 | "commons-parsing/option": "Možnosti:", 16 | "commons-import/creating": "Vytváření projektu…", 17 | "commons-parsing/limit-next": "Nahrát nejvýše", 18 | "commons-parsing/disable-auto-preview": "Vypnout automatický náhled", 19 | "commons-import/preparing": "Připravuje se…", 20 | "commons-import/import-by-category": "Zadejte jednu nebo více kategorií Wikimedia Commons, ze kterých chcete získat soubory:", 21 | "commons-source/alert-retrieve": "Nevybrána žádná kategorie", 22 | "commons-import/import-nested-category": "Hloubka podkategorií:", 23 | "commons-parsing/wiki-option": "Specifické možnosti Wikimedia Commons:", 24 | "commons-parsing/categories-column": "Zahrnout sloupec s kategoriemi", 25 | "commons-parsing/preview-button": "Aktualizovat náhled" 26 | } 27 | -------------------------------------------------------------------------------- /module/langs/translation-da.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-de.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-el.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-en.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/preparing": "Preparing…", 3 | "commons-import/creating": "Creating project…", 4 | "commons-import/import-by-category": "Enter one or more Wikimedia Commons categories from which you want to retrieve files:", 5 | "commons-import/import-nested-category": "Sub-category depth:", 6 | "commons-import/remove-category": "Remove this category", 7 | "commons-import/add": "Add another category", 8 | "commons-import/next->": "Next »", 9 | "commons-import/retrieving": "Retrieving files by category…", 10 | "commons-parsing/start-over": "« Start over", 11 | "commons-parsing/conf-pars": "Configure parsing options", 12 | "commons-parsing/proj-name": "Project name", 13 | "commons-parsing/create-proj": "Create project »", 14 | "commons-source/alert-retrieve": "No category selected", 15 | "commons-parsing/option": "Options:", 16 | "commons-parsing/discard-next": "Discard initial", 17 | "commons-parsing/discard": "row(s) of data", 18 | "commons-parsing/limit-next": "Load at most", 19 | "commons-parsing/limit": "row(s) of data", 20 | "commons-parsing/wiki-option": "Wikimedia Commons specific options:", 21 | "commons-parsing/categories-column": "Include a column with categories", 22 | "commons-parsing/mids-column": "Include a column with M-IDs", 23 | "commons-parsing/preview-button": "Update preview", 24 | "commons-parsing/disable-auto-preview": "Disable auto preview", 25 | "commons-parsing/updating-preview": "Updating preview…" 26 | } 27 | -------------------------------------------------------------------------------- /module/langs/translation-en_GB.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/preparing": "Preparing…", 3 | "commons-import/creating": "Creating project…", 4 | "commons-import/next->": "Next »", 5 | "commons-parsing/start-over": "« Start over", 6 | "commons-parsing/proj-name": "Project name", 7 | "commons-parsing/option": "Options:", 8 | "commons-parsing/limit-next": "Load at most", 9 | "commons-parsing/limit": "row(s) of data", 10 | "commons-parsing/wiki-option": "Wikimedia Commons specific options:", 11 | "commons-parsing/categories-column": "Include a column with categories", 12 | "commons-parsing/preview-button": "Update preview", 13 | "commons-parsing/disable-auto-preview": "Disable auto preview", 14 | "commons-parsing/create-proj": "Create project »", 15 | "commons-import/import-nested-category": "Sub-category depth:", 16 | "commons-parsing/discard": "row(s) of data", 17 | "commons-import/add": "Add another category", 18 | "commons-parsing/mids-column": "Include a column with M-IDs", 19 | "commons-import/import-by-category": "Enter one or more Wikimedia Commons categories from which you want to retrieve files:", 20 | "commons-import/remove-category": "Remove this category", 21 | "commons-import/retrieving": "Retrieving files by category…", 22 | "commons-parsing/conf-pars": "Configure parsing options", 23 | "commons-source/alert-retrieve": "No category selected", 24 | "commons-parsing/discard-next": "Discard initial", 25 | "commons-parsing/updating-preview": "Updating preview…" 26 | } 27 | -------------------------------------------------------------------------------- /module/langs/translation-es.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/import-by-category": "Ingrese una o más categorías de Wikimedia Commons de las que desee recuperar archivos:", 3 | "commons-import/creating": "Creando proyecto…", 4 | "commons-import/remove-category": "Eliminar esta categoría", 5 | "commons-import/add": "Añadir otra categoría", 6 | "commons-import/import-nested-category": "Profundidad de subcategoría:", 7 | "commons-parsing/start-over": "« Empezar de nuevo", 8 | "commons-import/next->": "Siguiente →", 9 | "commons-parsing/create-proj": "Crear proyecto»", 10 | "commons-source/alert-retrieve": "Ninguna categoría seleccionada", 11 | "commons-parsing/option": "Opciones:", 12 | "commons-parsing/discard-next": "Descartar inicial", 13 | "commons-parsing/discard": "fila(s) de datos", 14 | "commons-parsing/limit": "fila(s) de datos", 15 | "commons-parsing/conf-pars": "Configurar opciones del análisis sintáctico", 16 | "commons-parsing/proj-name": "Nombre del proyecto", 17 | "commons-parsing/wiki-option": "Opciones específicas de Wikimedia Commons:", 18 | "commons-parsing/categories-column": "Incluir una columna con categorías", 19 | "commons-parsing/disable-auto-preview": "Desactivar previsualización automática", 20 | "commons-parsing/updating-preview": "Actualizando previsualización…", 21 | "commons-parsing/preview-button": "Actualizar previsualizar", 22 | "commons-import/retrieving": "Recuperar archivos por categoría…", 23 | "commons-import/preparing": "Preparando…" 24 | } 25 | -------------------------------------------------------------------------------- /module/langs/translation-eu.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-fa.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-fi.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-fil.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/import-by-category": "Saisissez une ou plusieurs catégories Wikimédia Commons à partir desquelles récupérer des fichiers :", 3 | "commons-parsing/conf-pars": "Configuration des options d'analyse", 4 | "commons-parsing/proj-name": "Nom du projet", 5 | "commons-source/alert-retrieve": "Aucune catégorie sélectionnée", 6 | "commons-parsing/start-over": "« Recommencer", 7 | "commons-parsing/create-proj": "Créer un projet »", 8 | "commons-parsing/option": "Options :", 9 | "commons-parsing/wiki-option": "Options spécifiques à Wikimédia Commons :", 10 | "commons-parsing/discard-next": "Ignorer la ou les", 11 | "commons-parsing/limit-next": "Charger au plus", 12 | "commons-parsing/disable-auto-preview": "Désactiver l'aperçu automatique", 13 | "commons-parsing/updating-preview": "Mise à jour de l'aperçu…", 14 | "commons-parsing/preview-button": "Mettre à jour de l'aperçu", 15 | "commons-parsing/limit": "première(s) ligne(s) de données", 16 | "commons-import/preparing": "Préparation…", 17 | "commons-import/add": "Ajouter une autre catégorie", 18 | "commons-import/next->": "Suivant »", 19 | "commons-parsing/categories-column": "Inclure une colonne avec des catégories", 20 | "commons-import/retrieving": "Récupération de fichiers par catégorie…", 21 | "commons-parsing/discard": "première(s) ligne(s) du début du fichier", 22 | "commons-import/creating": "Création du projet…", 23 | "commons-import/import-nested-category": "Profondeur de la sous-catégorie :", 24 | "commons-import/remove-category": "Retirer la catégorie", 25 | "commons-parsing/mids-column": "Inclure une colonne avec les identifiants Mid de Wikimédia Commons" 26 | } 27 | -------------------------------------------------------------------------------- /module/langs/translation-he.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/creating": "נוצר מיזם…", 3 | "commons-parsing/discard": "שורות נתונים", 4 | "commons-import/preparing": "בהכנות…", 5 | "commons-import/import-by-category": "נא למלא קטגוריה אחת או יותר של ויקישיתוף ממנה ברצונך למשוך קבצים:", 6 | "commons-import/import-nested-category": "עומק תת־קטגוריות:", 7 | "commons-import/remove-category": "הסרת הקטגוריה הזאת", 8 | "commons-import/add": "הוספת קטגוריה נוספת", 9 | "commons-import/next->": "הבא »", 10 | "commons-import/retrieving": "מתקבלים קבצים לפי קטגוריה…", 11 | "commons-parsing/start-over": "« התחלה מחדש", 12 | "commons-parsing/conf-pars": "הגדרת אפשרויות פענוח", 13 | "commons-parsing/proj-name": "שם המיזם", 14 | "commons-source/alert-retrieve": "לא נבחרה קטגוריה", 15 | "commons-parsing/create-proj": "יצירת מיזם »", 16 | "commons-parsing/option": "אפשרויות:", 17 | "commons-parsing/discard-next": "איפוס הראשוני", 18 | "commons-parsing/limit-next": "לטעון הכי הרבה", 19 | "commons-parsing/limit": "שורות נתונים", 20 | "commons-parsing/wiki-option": "אפשרויות נקודתיות לוויקישיתוף:", 21 | "commons-parsing/categories-column": "לרבות עמודה עם קטגוריות", 22 | "commons-parsing/preview-button": "עדכון תצוגה מקדימה", 23 | "commons-parsing/disable-auto-preview": "השבתת תצוגה מקדימה אוטומטית", 24 | "commons-parsing/updating-preview": "התצוגה המקדימה מתעדכנת…" 25 | } 26 | -------------------------------------------------------------------------------- /module/langs/translation-hi.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-hu.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-id.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/preparing": "In preparazione…", 3 | "commons-import/creating": "Creando progetto…", 4 | "commons-import/import-by-category": "Inserire una o più categorie di Wikimedia Commons da cui si desidera recuperare i file:", 5 | "commons-import/import-nested-category": "Livelli di sotto-categorie:", 6 | "commons-import/remove-category": "Rimuovi questa categoria", 7 | "commons-import/add": "Aggiungi un'altra categoria", 8 | "commons-import/next->": "Prossimo »", 9 | "commons-import/retrieving": "Recupero dei file per categoria…", 10 | "commons-parsing/proj-name": "Nome del progetto", 11 | "commons-parsing/start-over": "« Ricomincia", 12 | "commons-parsing/conf-pars": "Configura opzioni di analisi", 13 | "commons-parsing/option": "Opzioni:", 14 | "commons-source/alert-retrieve": "Nessuna categoria selezionata", 15 | "commons-parsing/create-proj": "Crea progetto »", 16 | "commons-parsing/discard": "riga/righe di dati", 17 | "commons-parsing/limit-next": "Carica al massimo", 18 | "commons-parsing/limit": "riga/righe di dati", 19 | "commons-parsing/wiki-option": "Opzioni specifiche per Wikimedia Commons:", 20 | "commons-parsing/discard-next": "Scarta la prima/le prime", 21 | "commons-parsing/categories-column": "Includi una colonna con categorie", 22 | "commons-parsing/mids-column": "Includi una colonna con M-ID", 23 | "commons-parsing/preview-button": "Aggiorna anteprima", 24 | "commons-parsing/disable-auto-preview": "Disabilita anteprima automatica", 25 | "commons-parsing/updating-preview": "Aggiorna anteprima…" 26 | } 27 | -------------------------------------------------------------------------------- /module/langs/translation-iu.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/preparing": "準備中…", 3 | "commons-import/creating": "プロジェクトを作成中…", 4 | "commons-import/import-by-category": "ファイルを取得したいWikimedia Commonsのカテゴリーを入力してください:", 5 | "commons-import/import-nested-category": "サブカテゴリーの深さ:", 6 | "commons-import/remove-category": "このカテゴリーを削除", 7 | "commons-import/add": "別のカテゴリーを追加", 8 | "commons-import/next->": "次 »", 9 | "commons-parsing/start-over": "« やり直し", 10 | "commons-parsing/conf-pars": "パースオプションを設定", 11 | "commons-parsing/proj-name": "プロジェクト名", 12 | "commons-import/retrieving": "カテゴリーのファイルを取得…", 13 | "commons-parsing/create-proj": "プロジェクトを作成 »", 14 | "commons-source/alert-retrieve": "カテゴリーが選択されていません", 15 | "commons-parsing/option": "オプション:", 16 | "commons-parsing/discard-next": "接頭辞を削除", 17 | "commons-parsing/discard": "データの行数", 18 | "commons-parsing/limit-next": "読み込みの最大限度", 19 | "commons-parsing/limit": "データの行数", 20 | "commons-parsing/wiki-option": "Wikimedia Commons固有のオプション:", 21 | "commons-parsing/categories-column": "カテゴリーのカラムを含める", 22 | "commons-parsing/mids-column": "M-IDsのカラムを含める", 23 | "commons-parsing/preview-button": "プレビューを更新", 24 | "commons-parsing/disable-auto-preview": "プレビューの自動更新をしない", 25 | "commons-parsing/updating-preview": "プレビューを更新中…" 26 | } 27 | -------------------------------------------------------------------------------- /module/langs/translation-ko.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-ml.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-mr.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-nb_NO.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-nl.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-pa.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-pa_PK.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-pl.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-pt.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-pt_BR.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-pt_PT.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-ro.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-ru.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-sv.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-ta.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/creating": "திட்டத்தை உருவாக்குதல்…", 3 | "commons-import/import-by-category": "நீங்கள் கோப்புகளை மீட்டெடுக்க விரும்பும் ஒன்று அல்லது அதற்கு மேற்பட்ட விக்கிமீடியா காமன்ச் வகைகளை உள்ளிடவும்:", 4 | "commons-import/import-nested-category": "துணை வகை ஆழம்:", 5 | "commons-import/remove-category": "இந்த வகையை அகற்று", 6 | "commons-import/add": "மற்றொரு வகையைச் சேர்க்கவும்", 7 | "commons-import/retrieving": "வகை அடிப்படையில் கோப்புகளை மீட்டெடுப்பது…", 8 | "commons-parsing/conf-pars": "பாகுபடுத்தும் விருப்பங்களை உள்ளமைக்கவும்", 9 | "commons-parsing/proj-name": "திட்டம் & nbsp; பெயர்", 10 | "commons-parsing/limit": "தரவின் வரிசை (கள்)", 11 | "commons-parsing/wiki-option": "விக்கிமீடியா காமன்ச் குறிப்பிட்ட விருப்பங்கள்:", 12 | "commons-parsing/categories-column": "வகைகளுடன் ஒரு நெடுவரிசையைச் சேர்க்கவும்", 13 | "commons-parsing/mids-column": "M-IDS உடன் ஒரு நெடுவரிசையைச் சேர்க்கவும்", 14 | "commons-parsing/preview-button": "புதுப்பிப்பு & nbsp; முன்னோட்டம்", 15 | "commons-parsing/disable-auto-preview": "ஆட்டோ முன்னோட்டத்தை முடக்கு", 16 | "commons-parsing/start-over": "& லாகோ; தொடக்க", 17 | "commons-source/alert-retrieve": "எந்த வகையும் தேர்ந்தெடுக்கப்படவில்லை", 18 | "commons-import/preparing": "தயாரித்தல்…", 19 | "commons-import/next->": "அடுத்து »", 20 | "commons-parsing/option": "விருப்பங்கள்:", 21 | "commons-parsing/discard-next": "ஆரம்ப நிராகரிக்கவும்", 22 | "commons-parsing/discard": "தரவின் வரிசை (கள்)", 23 | "commons-parsing/limit-next": "அதிகபட்சம் ஏற்றவும்", 24 | "commons-parsing/updating-preview": "முன்னோட்டத்தைப் புதுப்பித்தல்…", 25 | "commons-parsing/create-proj": "திட்டத்தை உருவாக்கு »" 26 | } 27 | -------------------------------------------------------------------------------- /module/langs/translation-tl.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "commons-import/preparing": "Hazırlanıyor…", 3 | "commons-import/creating": "Proje oluşturuluyor…", 4 | "commons-import/import-by-category": "Dosya almak istediğiniz bir veya daha fazla Wikimedia Commons kategorisini girin:", 5 | "commons-import/import-nested-category": "Alt kategori derinliği:", 6 | "commons-import/remove-category": "Bu kategoriyi kaldır", 7 | "commons-import/add": "Başka bir kategori ekle", 8 | "commons-import/next->": "İleri »", 9 | "commons-import/retrieving": "Kategorilere göre dosyalar getiriliyor…", 10 | "commons-parsing/start-over": "« Yeniden başla", 11 | "commons-parsing/conf-pars": "Ayrıştırma seçeneklerini yapılandır", 12 | "commons-parsing/proj-name": "Proje adı", 13 | "commons-parsing/create-proj": "Proje oluştur »", 14 | "commons-source/alert-retrieve": "Hiçbir kategori seçilmedi", 15 | "commons-parsing/option": "Seçenekler:", 16 | "commons-parsing/discard-next": "Başlangıcı çıkar", 17 | "commons-parsing/discard": "veri satırı/satırları", 18 | "commons-parsing/limit": "veri satırı/satırları", 19 | "commons-parsing/wiki-option": "Wikimedia Commons'a özel seçenekler:", 20 | "commons-parsing/categories-column": "Kategorileri içeren bir sütun ekle", 21 | "commons-parsing/mids-column": "M-ID'leri içeren bir sütun ekle", 22 | "commons-parsing/disable-auto-preview": "Otomatik önizlemeyi devre dışı bırak", 23 | "commons-parsing/preview-button": "Önizlemeyi güncelle", 24 | "commons-parsing/updating-preview": "Önizleme güncelleniyor…", 25 | "commons-parsing/limit-next": "En fazla şu kadar yükle" 26 | } 27 | -------------------------------------------------------------------------------- /module/langs/translation-uk.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-zh_Hans.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/langs/translation-zh_Hant.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /module/scripts/index/category-suggest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adaptation of the suggest widget to query the Commons autocomplete API directly 3 | */ 4 | (function() { 5 | 6 | /** 7 | * Options: 8 | * commons_endpoint: url of the Commons API 9 | * entity_type: type of entity to suggest (one of form, item, lexeme, property, sense, mediainfo…) 10 | * language: language code of the language to search in 11 | */ 12 | 13 | $.suggest( 14 | "suggestCategory", 15 | $.extend( 16 | true, 17 | {}, 18 | $.suggest.suggest.prototype, 19 | { 20 | create_item: function(data, response_data) { 21 | var css = this.options.css; 22 | 23 | var li = $("
  • ").addClass(css.item); 24 | 25 | var name = $("
    ") 26 | .addClass(css.item_name) 27 | .append( 28 | $("