`,
+ valueNames: [{data: ['id']}, 'corpus_title', 'description', 'query', 'title']
+};
diff --git a/web/app/static/js/nopaque/lists/RessourceList.js b/web/app/static/js/nopaque/lists/RessourceList.js
new file mode 100644
index 00000000..c38ecebc
--- /dev/null
+++ b/web/app/static/js/nopaque/lists/RessourceList.js
@@ -0,0 +1,72 @@
+class RessourceList {
+ /* A wrapper class for the list.js list.
+ * This class is not meant to be used directly, instead it should be used as
+ * a base class for concrete ressource list implementations.
+ */
+ constructor(listElement, options = {}) {
+ if (listElement.dataset.userId) {
+ if (listElement.dataset.userId in nopaque.appClient.users) {
+ this.user = nopaque.appClient.users[listElement.dataset.userId];
+ } else {
+ console.error(`User not found: ${listElement.dataset.userId}`);
+ return;
+ }
+ } else {
+ this.user = nopaque.appClient.users.self;
+ }
+ this.list = new List(listElement, {...RessourceList.options, ...options});
+ this.list.list.innerHTML = `
+
+ file_downloadNothing here...
+
No ressource available (yet).
+
+
`;
+ }
+
+ eventHandler(eventType, payload) {
+ switch (eventType) {
+ case 'init':
+ this.init(payload);
+ break;
+ case 'patch':
+ this.patch(payload);
+ break;
+ default:
+ console.error(`Unknown event type: ${eventType}`);
+ break;
+ }
+ }
+
+ init(ressources) {
+ this.list.clear();
+ this.add(Object.values(ressources));
+ this.list.sort('id', {order: 'desc'});
+ }
+
+ patch(patch) {
+ /*
+ * It's not possible to generalize a patch Handler for all type of
+ * ressources. So this method is meant to be an interface.
+ */
+ console.error('patch method not implemented!');
+ }
+
+ add(values) {
+ let ressources = Array.isArray(values) ? values : [values];
+ if (typeof this.preprocessRessource === 'function') {
+ ressources = ressources.map(ressource => this.preprocessRessource(ressource));
+ }
+ // Set a callback function ('() => {return;}') to force List.js perform the
+ // add method asynchronous: https://listjs.com/api/#add
+ this.list.add(ressources, () => {return;});
+ }
+
+ remove(id) {
+ this.list.remove('id', id);
+ }
+
+ replace(id, valueName, newValue) {
+ this.list.get('id', id)[0].values({[valueName]: newValue});
+ }
+}
+RessourceList.options = {page: 5, pagination: [{innerWindow: 4, outerWindow: 1}]};
diff --git a/web/app/static/js/nopaque/index.js b/web/app/static/js/nopaque/main.js
similarity index 100%
rename from web/app/static/js/nopaque/index.js
rename to web/app/static/js/nopaque/main.js
diff --git a/web/app/templates/main/dashboard.html.j2 b/web/app/templates/main/dashboard.html.j2
index 8326654a..686c8438 100644
--- a/web/app/templates/main/dashboard.html.j2
+++ b/web/app/templates/main/dashboard.html.j2
@@ -70,7 +70,7 @@
Corpus and Query
-