Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F7066592
D7490.id27496.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
30 KB
Subscribers
None
D7490.id27496.diff
View Options
diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,4 @@
cypress/junit/
cypress/downloads/
.eslintcache
+tree-sitter-swh_search_ql.wasm
diff --git a/Makefile.local b/Makefile.local
--- a/Makefile.local
+++ b/Makefile.local
@@ -5,12 +5,13 @@
SETTINGS_TEST ?= swh.web.settings.tests
SETTINGS_DEV ?= swh.web.settings.development
SETTINGS_PROD = swh.web.settings.production
+SWH_SEARCH_DIR := $(shell python -c "import os;from swh import search; print(os.path.dirname(search.__file__))")
yarn-install: package.json
$(YARN) install --frozen-lockfile
.PHONY: build-webpack-dev
-build-webpack-dev: yarn-install
+build-webpack-dev:
$(YARN) build-dev
.PHONY: build-webpack-test
@@ -123,3 +124,7 @@
# https://github.com/typeddjango/django-stubs/issues/166
check-mypy:
DJANGO_SETTINGS_MODULE=$(SETTINGS_DEV) $(MYPY) $(MYPYFLAGS) swh
+
+build-ts-wasm:
+ yarn run tree-sitter build-wasm $(SWH_SEARCH_DIR)/query_language
+ cp $(SWH_SEARCH_DIR)/query_language/tokens.js assets/tokens.js
diff --git a/assets/config/webpack.config.development.js b/assets/config/webpack.config.development.js
--- a/assets/config/webpack.config.development.js
+++ b/assets/config/webpack.config.development.js
@@ -149,10 +149,16 @@
alias: {
'pdfjs-dist': 'pdfjs-dist/build/pdf.min.js'
},
+ // for web-tree-sitter
+ fallback: {
+ 'path': false,
+ 'fs': false
+ },
// configure base paths for resolving modules with webpack
modules: [
'node_modules',
- path.resolve(__dirname, '../src')
+ path.resolve(__dirname, '../src'),
+ path.resolve(__dirname, '../../../swh-search/query_language/')
]
},
stats: 'errors-warnings',
@@ -212,6 +218,18 @@
}
}]
},
+ {
+ test: /\.wasm$/,
+ type: 'javascript/auto',
+ use: [{
+ loader: 'file-loader',
+ options: {
+ name: '[name].[ext]',
+ outputPath: 'js/'
+ }
+ }]
+ },
+
// expose jquery to the global context as $ and jQuery when importing it
{
test: require.resolve('jquery'),
@@ -370,6 +388,14 @@
{
from: path.resolve(nodeModules, 'mathjax/es5/output/chtml/fonts/woff-v2/**'),
to: path.resolve(__dirname, '../../static/fonts/[name][ext]')
+ },
+ {
+ from: path.resolve(__dirname, '../../tree-sitter-swh_search_ql.wasm'),
+ to: path.resolve(__dirname, '../../static/js/swh_ql.wasm')
+ },
+ {
+ from: path.resolve(__dirname, '../tokens.js'),
+ to: path.resolve(__dirname, '../../static/js/')
}
]
}),
diff --git a/assets/src/bundles/browse/origin-search.js b/assets/src/bundles/browse/origin-search.js
--- a/assets/src/bundles/browse/origin-search.js
+++ b/assets/src/bundles/browse/origin-search.js
@@ -6,6 +6,7 @@
*/
import {handleFetchError, errorMessageFromResponse, isArchivedOrigin} from 'utils/functions';
+import {initAutocomplete} from 'utils/search-ql-autocomplete';
const limit = 100;
const linksPrev = [];
@@ -195,6 +196,17 @@
export function initOriginSearch() {
$(document).ready(() => {
+ const inputBox = document.querySelector('#swh-origins-url-patterns');
+ const submitBtn = document.querySelector('#swh-search-submit');
+ const validQueryCallback = (isValid) => {
+ submitBtn.disabled = !isValid;
+ // if (!isValid)
+ // inputBox.classList.add('invalid');
+ // else
+ // inputBox.classList.remove('invalid');
+ };
+ initAutocomplete(inputBox, validQueryCallback);
+
$('#swh-search-origins').submit(event => {
event.preventDefault();
if (event.target.checkValidity()) {
diff --git a/assets/src/utils/autocomplete.css b/assets/src/utils/autocomplete.css
new file mode 100644
--- /dev/null
+++ b/assets/src/utils/autocomplete.css
@@ -0,0 +1,50 @@
+.autocomplete {
+ position: relative;
+ display: inline-block;
+}
+
+input.invalid {
+ outline: none !important;
+
+ /* border: 2px solid red; */
+}
+
+/* position the autocomplete items to be the same width as the container: */
+.autocomplete-items {
+ position: absolute;
+ border: 1px solid #d4d4d4;
+ width: 200px;
+ border-top: none;
+ z-index: 99998;
+ top: 100%;
+ left: 0;
+ right: 0;
+
+ /*
+ z-index: 99999; is taken by swh-top-bar
+ overflow-y: scroll;
+ */
+}
+
+.autocomplete-items div {
+ padding: 3px;
+ padding-left: 5px;
+ cursor: pointer;
+ background-color: #fff;
+
+ /*
+ font-size: 15px;
+ border-bottom: 1px solid #d4d4d4;
+ */
+}
+
+/* when hovering an item: */
+.autocomplete-items div:hover {
+ background-color: #e9e9e9;
+}
+
+/* when navigating through the items using the arrow keys: */
+.autocomplete-active {
+ background-color: #e20026 !important;
+ color: #fff;
+}
diff --git a/assets/src/utils/autocomplete.js b/assets/src/utils/autocomplete.js
new file mode 100644
--- /dev/null
+++ b/assets/src/utils/autocomplete.js
@@ -0,0 +1,148 @@
+import 'utils/autocomplete.css';
+
+export class Autocomplete {
+ constructor(params) {
+ const {inputBox, suggestions} = params;
+ this.inputBox = inputBox;
+ this.suggestions = suggestions;
+ this.currentIndex = -1;
+
+ this.autocompleteList = document.createElement('div');
+ this.autocompleteList.setAttribute('class', 'autocomplete-items');
+ this.inputBox.parentNode.appendChild(this.autocompleteList);
+
+ this.initListeners();
+ }
+
+ initListeners() {
+ this.inputBox.addEventListener('focus', this.updateLists.bind(this));
+ this.inputBox.addEventListener('input', this.updateLists.bind(this));
+
+ this.inputBox.addEventListener('keydown', (e) => {
+ if (e.keyCode === 40) { // down
+ e.preventDefault();
+ this.currentIndex++;
+ this.addActive();
+ } else if (e.keyCode === 38) { // up
+ e.preventDefault();
+ this.currentIndex--;
+ this.addActive();
+ } else if (e.keyCode === 13 || e.keyCode === 9) { // enter or tab
+ e.preventDefault();
+ if (this.currentIndex > -1) {
+ // Simulate a click on the "active" item:
+ if (this.autocompleteList) this.autocompleteList.children[this.currentIndex].click();
+ }
+ } else if (e.keyCode === 27) { // escape
+ e.preventDefault();
+ this.removeAllItems(e.target);
+ }
+ });
+
+ document.addEventListener('click', (e) => { this.removeAllItems(e.target); });
+ }
+
+ updateLists() {
+ const inputValue = this.inputBox.value;
+
+ const tokens = inputValue.split();
+ const lastToken = tokens[tokens.length - 1];
+ const lastChar = lastToken[lastToken.length - 1];
+
+ /* close any already open lists of autocompleted values */
+ this.removeAllItems();
+
+ this.currentIndex = -1;
+
+ const suggestions = this.suggestions.filter(s => (s.indexOf(lastToken) >= 0 || lastChar === ' '));
+
+ suggestions.slice(0, 10).forEach(suggestion => {
+ const itemDiv = document.createElement('div');
+ if (lastChar === ' ') {
+ itemDiv.innerHTML = suggestion;
+ } else {
+ const indexOfLastToken = suggestion.indexOf(lastToken);
+
+ itemDiv.innerHTML = suggestion.substr(0, indexOfLastToken) +
+ '<strong>' +
+ suggestion.substr(indexOfLastToken, lastToken.length) +
+ '</strong>' +
+ suggestion.substr(
+ indexOfLastToken + lastToken.length, suggestion.length - (lastToken.length - 2)
+ );
+
+ }
+
+ itemDiv.setAttribute('data-value', suggestion);
+ itemDiv.setAttribute('data-editable-suggestion', 'false');
+ itemDiv.setAttribute('title', 'Include repos with the provided term in their url (origin)');
+
+ const suggestionClick = (e) => {
+ const toInsert = e.target.getAttribute('data-value');
+ const isEditableSuggestion = e.target.getAttribute('data-editable-suggestion');
+
+ if (isEditableSuggestion === 'true') return;
+
+ const oldValue = this.inputBox.value;
+ const tokens = oldValue.split();
+ const lastToken = tokens[tokens.length - 1];
+ const lastChar = lastToken[lastToken.length - 1];
+
+ let newValue = '';
+
+ if (lastChar === ' ' || oldValue === '') {
+ newValue = oldValue + toInsert;
+ } else {
+ // const position = this.inputBox.selectionStart;
+ const queryWithoutLastToken = tokens.slice(0, tokens.length - 2).join(' ');
+ newValue = queryWithoutLastToken + ((queryWithoutLastToken !== '') ? ' ' : '') + toInsert;
+ }
+
+ this.inputBox.value = newValue;
+ this.inputBox.blur();
+ this.inputBox.focus();
+ // this.inputBox.dispatchEvent(new Event('input'))
+ };
+
+ itemDiv.addEventListener('click', suggestionClick.bind(this));
+
+ this.autocompleteList.appendChild(itemDiv);
+ });
+
+ if (suggestions?.length) {
+ // Select first element on each update
+ this.currentIndex = 0;
+ this.addActive();
+ }
+ }
+
+ addActive() {
+ // a function to classify an item as "active":
+ if (!this.autocompleteList) return false;
+ // start by removing the "active" class on all items:
+ const n = this.autocompleteList.childElementCount;
+ this.removeActive();
+ if (this.currentIndex >= n) this.currentIndex = 0;
+ if (this.currentIndex < 0) this.currentIndex = (n - 1);
+ // add class "autocomplete-active":
+ this.autocompleteList.children[this.currentIndex].classList.add('autocomplete-active');
+ }
+
+ removeActive() {
+ /* a function to remove the "active" class from all autocomplete items */
+ Array.from(this.autocompleteList.children).forEach(autocompleteItem => {
+ autocompleteItem.classList.remove('autocomplete-active');
+ });
+ }
+
+ removeAllItems(element) {
+ /*
+ close all autocomplete lists in the document,
+ except the one passed as an argument
+ */
+ if (element !== this.inputBox && this.autocompleteList) {
+ this.autocompleteList.innerHTML = '';
+ }
+ }
+
+}
diff --git a/assets/src/utils/search-ql-autocomplete.js b/assets/src/utils/search-ql-autocomplete.js
new file mode 100644
--- /dev/null
+++ b/assets/src/utils/search-ql-autocomplete.js
@@ -0,0 +1,224 @@
+import {staticAsset} from 'utils/functions';
+import 'web-tree-sitter/tree-sitter.wasm';
+import Parser from 'web-tree-sitter';
+import {Autocomplete} from 'utils/autocomplete.js';
+import {
+ fields, limitField, sortByField, // fields
+ sortByOptions, visitTypeOptions, // options
+ equalOp, containOp, rangeOp, choiceOp, // operators
+ AND, OR, TRUE, FALSE // special tokens
+} from '../../tokens.js';
+
+const filterNames = fields.concat(sortByField, limitField);
+
+const languageSyntax = [
+ {
+ category: 'patternFilter',
+ field: 'patternField',
+ operator: 'containOp',
+ value: 'patternVal',
+ suggestion: ['string', '"string"']
+ },
+ {
+ category: 'booleanFilter',
+ field: 'booleanField',
+ operator: 'equalOp',
+ value: 'booleanVal',
+ suggestion: [TRUE, FALSE]
+ },
+ {
+ category: 'numericFilter',
+ field: 'numericField',
+ operator: 'rangeOp',
+ value: 'numberVal',
+ suggestion: ['15']
+ },
+ {
+ category: 'boundedListFilter',
+ field: 'visitTypeField',
+ operator: 'equalOp',
+ value: 'visitTypeVal',
+ options: visitTypeOptions,
+ suggestion: ['[']
+ },
+ {
+ category: 'unboundedListFilter',
+ field: 'listField',
+ operator: 'choiceOp',
+ value: 'listVal',
+ options: ['string', '"string"'],
+ suggestion: ['[']
+ },
+ {
+ category: 'dateFilter',
+ field: 'dateField',
+ operator: 'rangeOp',
+ value: 'dateVal',
+ suggestion: ['2000-01-01', '2000-01-01T00:00Z']
+ },
+ {
+ category: 'sortBy',
+ field: 'sortByField',
+ operator: 'equalOp',
+ value: 'sortByVal',
+ options: sortByOptions,
+ suggestion: ['[']
+ },
+ {
+ category: 'limit',
+ field: 'limit',
+ operator: 'equalOp',
+ value: 'number',
+ suggestion: ['50']
+ }
+];
+
+const filterOperators = {equalOp, containOp, choiceOp, rangeOp};
+
+const findMissingNode = (node) => {
+ if (node.isMissing()) {
+ return node;
+ }
+ if (node.children.length > 0) {
+ for (let i = 0; i < node.children.length; i++) {
+ const missingNode = findMissingNode(node.children[i]);
+ if (missingNode !== null) { return missingNode; }
+ }
+ }
+
+ return null;
+};
+
+const isWrapperNode = (child, parent) => {
+ if (!child || !parent) return false;
+ if (parent.namedChildren.length === 1 && parent.type !== 'ERROR') return true;
+ return (
+ (child.startPosition.column === parent.startPosition.column) &&
+ (child.endPosition.column === parent.endPosition.column)
+ );
+};
+
+const isCategoryNode = (node) => {
+ if (!node || node === null) return false;
+ if (node.type === 'ERROR' || languageSyntax.filter(f => f.category === node.type).length > 0) { return true; }
+
+ return false;
+};
+
+const suggestNextNode = (tree, inputBox) => {
+ const cursor = inputBox.selectionStart - 1;
+ const query = inputBox.value;
+
+ let lastTokenIndex = cursor;
+ // let distFromLastToken = 0;
+ while (query[lastTokenIndex] === ' ') {
+ lastTokenIndex--;
+ // distFromLastToken++;
+ }
+
+ // if(query === "visit_type = []") debugger;
+
+ const lastTokenPosition = {row: 0, column: lastTokenIndex};
+ const lastTokenNode = tree.rootNode.descendantForPosition(lastTokenPosition, lastTokenPosition);
+
+ const missingNode = findMissingNode(tree.rootNode);
+
+ // Find last token node wrapper
+ let lastTokenNodeWrapper = lastTokenNode;
+ while (isWrapperNode(lastTokenNodeWrapper, lastTokenNodeWrapper.parent)) {
+ lastTokenNodeWrapper = lastTokenNodeWrapper.parent;
+ }
+
+ // Find last token node wrapper sibling
+ const lastTokenNodeWrapperSibling = lastTokenNodeWrapper.previousSibling;
+
+ // Find current filter category
+ let currentFilterCategory = lastTokenNode;
+ while (!isCategoryNode(currentFilterCategory)) {
+ currentFilterCategory = currentFilterCategory.parent;
+ }
+
+ console.log(lastTokenNode);
+ console.log(`LAST NODE: ${lastTokenNode.type}`);
+ console.log(`LAST NODE ANCESTOR: ${lastTokenNodeWrapper.type}`);
+ console.log(`LAST NODE ANCESTOR SIBLING: ${lastTokenNodeWrapperSibling?.type}`);
+ console.log(`LAST CATEGORY: ${currentFilterCategory.type}`);
+
+ // Suggest options for array valued filters
+ if ((lastTokenNode.type === ',' && lastTokenNodeWrapper.type.indexOf('Val') > 0) ||
+ (lastTokenNode.type === '[' && currentFilterCategory)
+ ) {
+ const filter = languageSyntax.filter(f => f.category === currentFilterCategory.type)[0];
+ console.log(filter.options);
+ return filter.options ?? [];
+ }
+ if (
+ (!tree.rootNode.hasError() && (lastTokenNodeWrapper.type.indexOf('Val') > 0)) ||
+ (lastTokenNode.type === ')' || lastTokenNode.type === ']')
+ ) {
+ // Suggest AND/OR
+ return [AND, OR];
+ }
+ if (missingNode && missingNode !== null) {
+ // Suggest missing nodes (Automatically suggested by Tree-sitter)
+ if (missingNode.type === ')') {
+ return [AND, OR, ')'];
+ } else if (missingNode.type === ']') {
+ return [',', ']'];
+ }
+ }
+
+ if (lastTokenNode.type === 'ERROR' ||
+ (lastTokenNode.type === '(') ||
+ ((lastTokenNode.type === AND || lastTokenNode.type === OR))
+ ) {
+ // Suggest field names
+ return filterNames.concat('(');
+ } else if (languageSyntax.map(f => f.field).includes(lastTokenNode.type)) {
+ // Suggest operators
+ const filter = languageSyntax.filter(f => f.field === lastTokenNode.type)[0];
+ return filterOperators[filter.operator];
+ } else if (lastTokenNode.type in filterOperators) {
+ // Suggest values
+ const filter = languageSyntax.filter(f => (
+ f.field === lastTokenNodeWrapperSibling.type
+ ))[0];
+ return filter.suggestion;
+ }
+
+ return [];
+};
+
+export const initAutocomplete = (inputBox, validQueryCallback) => {
+ Parser.init().then(async() => {
+ const parser = new Parser();
+ const swhSearchQL = await Parser.Language.load(staticAsset('js/swh_ql.wasm'));
+ parser.setLanguage(swhSearchQL);
+
+ const autocomplete = new Autocomplete(
+ {inputBox, suggestions: ['('].concat(filterNames)}
+ );
+
+ const getSuggestions = (e) => {
+ // if (e.keycode !== 32) // space
+ // return;
+ const tree = parser.parse(inputBox.value);
+
+ if (tree.rootNode.hasError()) {
+ validQueryCallback(false);
+ // inputBox.classList.add('invalid');
+ } else {
+ validQueryCallback(true);
+ // inputBox.classList.remove('invalid');
+ }
+
+ console.log(`input(${inputBox.value}) => ${tree.rootNode.toString()}`);
+
+ const suggestions = suggestNextNode(tree, inputBox);
+ // if (suggestions)
+ autocomplete.suggestions = suggestions; // .map(item => `${item} `);
+ };
+
+ inputBox.addEventListener('keydown', getSuggestions.bind(this));
+ });
+};
diff --git a/assets/src/utils/tokens.js b/assets/src/utils/tokens.js
new file mode 100644
--- /dev/null
+++ b/assets/src/utils/tokens.js
@@ -0,0 +1,109 @@
+// Copyright (C) 2021 The Software Heritage developers
+// See the AUTHORS file at the top-level directory of this distribution
+// License: GNU General Public License version 3, or any later version
+// See top-level LICENSE file for more information
+
+// Field tokens
+const visitTypeField = 'visit_type';
+const sortByField = 'sort_by';
+const limitField = 'limit';
+
+// Field categories
+const patternFields = ['origin', 'metadata'];
+const booleanFields = ['visited'];
+const numericFields = ['visits'];
+const boundedListFields = [visitTypeField];
+const listFields = ['language', 'license', 'keyword'];
+const dateFields = [
+ 'last_visit',
+ 'last_eventful_visit',
+ 'last_revision',
+ 'last_release',
+ 'created',
+ 'modified',
+ 'published'
+];
+
+const fields = [].concat(
+ patternFields,
+ booleanFields,
+ numericFields,
+ boundedListFields,
+ listFields,
+ dateFields
+);
+
+// Operators
+const equalOp = ['='];
+const containOp = [':'];
+const rangeOp = ['<', '<=', '=', '!=', '>=', '>'];
+const choiceOp = ['in', 'not in'];
+
+// Values
+const sortByOptions = [
+ 'visits',
+ 'last_visit',
+ 'last_eventful_visit',
+ 'last_revision',
+ 'last_release',
+ 'created',
+ 'modified',
+ 'published'
+];
+
+const visitTypeOptions = [
+ 'any',
+ 'bzr',
+ 'cran',
+ 'cvs',
+ 'deb',
+ 'deposit',
+ 'ftp',
+ 'hg',
+ 'git',
+ 'nixguix',
+ 'npm',
+ 'opam',
+ 'pypi',
+ 'svn',
+ 'tar'
+];
+
+// Extra tokens
+const OR = 'or';
+const AND = 'and';
+
+const TRUE = 'true';
+const FALSE = 'false';
+
+module.exports = {
+ // Field tokens
+ visitTypeField,
+ sortByField,
+ limitField,
+
+ // Field categories
+ patternFields,
+ booleanFields,
+ numericFields,
+ boundedListFields,
+ listFields,
+ dateFields,
+ fields,
+
+ // Operators
+ equalOp,
+ containOp,
+ rangeOp,
+ choiceOp,
+
+ // Values
+ sortByOptions,
+ visitTypeOptions,
+
+ // Extra tokens
+ OR,
+ AND,
+ TRUE,
+ FALSE
+};
diff --git a/assets/tokens.js b/assets/tokens.js
new file mode 100644
--- /dev/null
+++ b/assets/tokens.js
@@ -0,0 +1,109 @@
+// Copyright (C) 2021 The Software Heritage developers
+// See the AUTHORS file at the top-level directory of this distribution
+// License: GNU General Public License version 3, or any later version
+// See top-level LICENSE file for more information
+
+// Field tokens
+const visitTypeField = 'visit_type';
+const sortByField = 'sort_by';
+const limitField = 'limit';
+
+// Field categories
+const patternFields = ['origin', 'metadata'];
+const booleanFields = ['visited'];
+const numericFields = ['visits'];
+const boundedListFields = [visitTypeField];
+const listFields = ['language', 'license', 'keyword'];
+const dateFields = [
+ 'last_visit',
+ 'last_eventful_visit',
+ 'last_revision',
+ 'last_release',
+ 'created',
+ 'modified',
+ 'published'
+];
+
+const fields = [].concat(
+ patternFields,
+ booleanFields,
+ numericFields,
+ boundedListFields,
+ listFields,
+ dateFields
+);
+
+// Operators
+const equalOp = ['='];
+const containOp = [':'];
+const rangeOp = ['<', '<=', '=', '!=', '>=', '>'];
+const choiceOp = ['in', 'not in'];
+
+// Values
+const sortByOptions = [
+ 'visits',
+ 'last_visit',
+ 'last_eventful_visit',
+ 'last_revision',
+ 'last_release',
+ 'created',
+ 'modified',
+ 'published'
+];
+
+const visitTypeOptions = [
+ 'any',
+ 'bzr',
+ 'cran',
+ 'cvs',
+ 'deb',
+ 'deposit',
+ 'ftp',
+ 'hg',
+ 'git',
+ 'nixguix',
+ 'npm',
+ 'opam',
+ 'pypi',
+ 'svn',
+ 'tar'
+];
+
+// Extra tokens
+const OR = 'or';
+const AND = 'and';
+
+const TRUE = 'true';
+const FALSE = 'false';
+
+module.exports = {
+ // Field tokens
+ visitTypeField,
+ sortByField,
+ limitField,
+
+ // Field categories
+ patternFields,
+ booleanFields,
+ numericFields,
+ boundedListFields,
+ listFields,
+ dateFields,
+ fields,
+
+ // Operators
+ equalOp,
+ containOp,
+ rangeOp,
+ choiceOp,
+
+ // Values
+ sortByOptions,
+ visitTypeOptions,
+
+ // Extra tokens
+ OR,
+ AND,
+ TRUE,
+ FALSE
+};
diff --git a/mypy.ini b/mypy.ini
--- a/mypy.ini
+++ b/mypy.ini
@@ -18,6 +18,12 @@
[mypy-django_js_reverse.*]
ignore_missing_imports = True
+[mypy-django_plugin.*]
+no_implicit_reexport = False
+
+[mypy-drf_plugin.main]
+no_implicit_reexport = False
+
[mypy-htmlmin.*]
ignore_missing_imports = True
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -9,7 +9,8 @@
"build": "NODE_ENV=production webpack --config assets/config/webpack.config.production.js --color",
"mochawesome": "mochawesome-merge cypress/mochawesome/results/*.json > cypress/mochawesome/mochawesome.json && marge -o cypress/mochawesome/report cypress/mochawesome/mochawesome.json",
"eslint": "eslint -c assets/config/.eslintrc --fix assets/** cypress/integration/** cypress/plugins/** cypress/support/**",
- "preinstall": "npm -v || (SWH_WEB=$PWD && cd /tmp && yarn add npm && cd node_modules/npm && yarn link && cd $SWH_WEB && yarn link npm)",
+ "preinstall": "npm -v || (SWH_WEB=$PWD && cd /tmp && yarn add npm && cd node_modules/npm && yarn link && cd $SWH_WEB && yarn link npm) && /lib/x86_64-linux-gnu/libc.so.6",
+ "postinstall": "SWH_SEARCH_DIR=$(python3 -c 'import os;from swh import search; print(os.path.dirname(search.__file__))') && yarn run tree-sitter build-wasm $SWH_SEARCH_DIR/query_language --docker && cp $SWH_SEARCH_DIR/query_language/tokens.js assets/tokens.js",
"nyc-report": "nyc report --reporter=lcov"
},
"repository": {
@@ -89,6 +90,7 @@
"typeface-alegreya": "^1.1.13",
"typeface-alegreya-sans": "^1.1.13",
"waypoints": "^4.0.1",
+ "web-tree-sitter": "^0.20.5",
"whatwg-fetch": "^3.6.2"
},
"devDependencies": {
@@ -121,6 +123,7 @@
"eslint-webpack-plugin": "^3.1.1",
"exports-loader": "^3.1.0",
"expose-loader": "^3.1.0",
+ "file-loader": "^6.2.0",
"imports-loader": "^3.1.1",
"istanbul-lib-coverage": "^3.2.0",
"json-stable-stringify": "^1.0.1",
@@ -149,6 +152,7 @@
"stylelint": "^14.6.1",
"stylelint-config-standard": "^25.0.0",
"terser-webpack-plugin": "^5.3.1",
+ "tree-sitter-cli": "^0.20.6",
"url-loader": "^4.1.1",
"webpack": "^5.72.0",
"webpack-bundle-tracker": "^1.5.0",
diff --git a/swh/web/settings/common.py b/swh/web/settings/common.py
--- a/swh/web/settings/common.py
+++ b/swh/web/settings/common.py
@@ -145,6 +145,7 @@
if not os.path.exists(STATIC_DIR):
# static folder location when developping swh-web
STATIC_DIR = os.path.join(PROJECT_DIR, "../../../static")
+
STATICFILES_DIRS = [STATIC_DIR]
INTERNAL_IPS = ["127.0.0.1"]
diff --git a/swh/web/templates/includes/origin-search-form.html b/swh/web/templates/includes/origin-search-form.html
--- a/swh/web/templates/includes/origin-search-form.html
+++ b/swh/web/templates/includes/origin-search-form.html
@@ -10,9 +10,9 @@
<input class="form-control"
placeholder="Enter a SWHID to resolve or string pattern(s) to search for in origin urls"
type="text" id="swh-origins-url-patterns"
- oninput="swh.webapp.validateSWHIDInput(this)" autofocus required>
+ oninput="swh.webapp.validateSWHIDInput(this)" autofocus required autocomplete="off">
<div class="input-group-append">
- <button class="btn btn-primary" type="submit"><i class="swh-search-icon mdi mdi-24px mdi-magnify" aria-hidden="true"></i></button>
+ <button class="btn btn-primary" type="submit" id="swh-search-submit"><i class="swh-search-icon mdi mdi-24px mdi-magnify" aria-hidden="true"></i></button>
</div>
<div class="invalid-feedback"></div>
</div>
@@ -48,7 +48,6 @@
search in metadata (instead of URL)
</label>
</div>
- {% if user.is_authenticated and user.is_staff or "swh.web.search_ql" in user.get_all_permissions %}
<div class="custom-control custom-checkbox swhid-option">
<input class="custom-control-input" value="option-use-ql" type="checkbox"
id="swh-search-use-ql">
@@ -60,7 +59,6 @@
</a>
</label>
</div>
- {% endif %}
</form>
<script>
diff --git a/yarn.lock b/yarn.lock
--- a/yarn.lock
+++ b/yarn.lock
@@ -4094,7 +4094,7 @@
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
-deep-equal@^1.0.0:
+deep-equal@^1.0.0, deep-equal@^1.0.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"
integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==
@@ -5151,6 +5151,14 @@
dependencies:
flat-cache "^3.0.4"
+file-loader@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
+ integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
+ dependencies:
+ loader-utils "^2.0.0"
+ schema-utils "^3.0.0"
+
filelist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.1.tgz#f10d1a3ae86c1694808e8f20906f43d4c9132dbb"
@@ -9053,10 +9061,10 @@
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
-qs@6.9.7:
- version "6.9.7"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
- integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
+qs@6.7.0:
+ version "6.7.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
+ integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@~6.5.2:
version "6.5.2"
@@ -9390,14 +9398,13 @@
dependencies:
path-parse "^1.0.6"
-resolve@^1.1.4, resolve@^1.1.5, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.4.0, resolve@^1.9.0:
- version "1.22.0"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
- integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
+resolve@^1.1.4, resolve@^1.1.5, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.4.0, resolve@^1.9.0:
+ version "1.20.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
+ integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
dependencies:
- is-core-module "^2.8.1"
- path-parse "^1.0.7"
- supports-preserve-symlinks-flag "^1.0.0"
+ is-core-module "^2.2.0"
+ path-parse "^1.0.6"
restore-cursor@^3.1.0:
version "3.1.0"
@@ -9605,12 +9612,12 @@
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
-selfsigned@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.1.tgz#8b2df7fa56bf014d19b6007655fff209c0ef0a56"
- integrity sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==
+selfsigned@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.0.tgz#e927cd5377cbb0a1075302cff8df1042cc2bce5b"
+ integrity sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ==
dependencies:
- node-forge "^1"
+ node-forge "^1.2.0"
"semver@2 || 3 || 4 || 5", semver@^5.3.0:
version "5.7.1"
@@ -10572,10 +10579,10 @@
dependencies:
jquery ">=1.12.0"
-toidentifier@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
- integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+toidentifier@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
+ integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
tough-cookie@^4.0.0:
version "4.0.0"
@@ -10601,6 +10608,11 @@
dependencies:
punycode "^2.1.1"
+tree-sitter-cli@^0.20.6:
+ version "0.20.6"
+ resolved "https://registry.yarnpkg.com/tree-sitter-cli/-/tree-sitter-cli-0.20.6.tgz#2a7202190d7bd64e112b451f94573dbe40a04f04"
+ integrity sha512-tjbAeuGSMhco/EnsThjWkQbDIYMDmdkWsTPsa/NJAW7bjaki9P7oM9TkLxfdlnm4LXd1wR5wVSM2/RTLtZbm6A==
+
trim-newlines@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30"
@@ -11022,6 +11034,11 @@
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965"
integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==
+web-tree-sitter@^0.20.5:
+ version "0.20.5"
+ resolved "https://registry.yarnpkg.com/web-tree-sitter/-/web-tree-sitter-0.20.5.tgz#62c8ea29d94f6ef6f03ce9c68c860df011ee26c7"
+ integrity sha512-mpXlqIeEBE5Q71cnBnt8w6XKhIiKmllPECqsIFBtMvzcfCxA8+614iyMJXBCQo95Vs3y1zORLqiLJn25pYZ4Tw==
+
webidl-conversions@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Nov 5 2024, 3:38 PM (12 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3218919
Attached To
D7490: origin-search: Add search QL autocomplete
Event Timeline
Log In to Comment