├── .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 | $("