diff --git a/app/corpora/routes.py b/app/corpora/routes.py
index a81734f8ac06dc1819fea7495ea7c02e93320378..25c5228f35fcb617b55c725673ad7f445c3251cb 100644
--- a/app/corpora/routes.py
+++ b/app/corpora/routes.py
@@ -54,6 +54,7 @@ def corpus(corpus_id):
     # TODO: Better solution for filtering admin
     users = User.query.filter(User.is_public == True, User.id != current_user.id, User.id != corpus.user.id, User.role_id < 4).all()
     cfa = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id, follower_id=current_user.id).first()
+    cfas = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id).all()
     if cfa is None:
         if corpus.user == current_user or current_user.is_administrator():
             cfr = CorpusFollowerRole.query.filter_by(name='Administrator').first()
@@ -61,14 +62,29 @@ def corpus(corpus_id):
             cfr = CorpusFollowerRole.query.filter_by(name='Anonymous').first()
     else:
         cfr = cfa.role
-    return render_template(
-        'corpora/corpus.html.j2',
-        title=corpus.title,
-        corpus=corpus,
-        cfrs=cfrs,
-        cfr=cfr,
-        users = users
-    )
+    if corpus.user == current_user or current_user.is_administrator():
+        return render_template(
+            'corpora/corpus.html.j2',
+            title=corpus.title,
+            corpus=corpus,
+            cfr=cfr,
+            cfrs=cfrs,
+            users = users
+        )
+    if (current_user.is_following_corpus(corpus) or corpus.is_public):
+        cfas = CorpusFollowerAssociation.query.filter_by(corpus_id=corpus_id).all()
+        return render_template(
+            'corpora/public_corpus.html.j2',
+            title=corpus.title,
+            corpus=corpus,
+            cfrs=cfrs,
+            cfr=cfr,
+            cfas=cfas,
+            cfa=cfa,
+            users = users
+        )
+    abort(403)
+
 
 
 @bp.route('/<hashid:corpus_id>/analysis')
diff --git a/app/static/js/ResourceLists/CorpusFileList.js b/app/static/js/ResourceLists/CorpusFileList.js
index 813676fb4588f6fb14b5b3566654151924d24235..ac87e8e57137c071918f0c5afc02fb78fa75a2a7 100644
--- a/app/static/js/ResourceLists/CorpusFileList.js
+++ b/app/static/js/ResourceLists/CorpusFileList.js
@@ -8,7 +8,11 @@ class CorpusFileList extends ResourceList {
   constructor(listContainerElement, options = {}) {
     super(listContainerElement, options);
     this.listjs.list.addEventListener('click', (event) => {this.onClick(event)});
+    document.querySelectorAll('.selection-action-trigger[data-selection-action]').forEach((element) => {
+      element.addEventListener('click', (event) => {this.onSelectionAction(event)});
+    });
     this.isInitialized = false;
+    this.selectedItemIds = [];
     this.userId = listContainerElement.dataset.userId;
     this.corpusId = listContainerElement.dataset.corpusId;
     this.hasPermissionView =  listContainerElement.dataset?.hasPermissionView == 'true' || false;
@@ -29,6 +33,12 @@ class CorpusFileList extends ResourceList {
     return (values) => {
       return `
         <tr class="list-item">
+          <td>
+            <label class="list-action-trigger ${this.hasPermissionView ? '' : 'hide'}" data-list-action="select">
+              <input type="checkbox">
+              <span class="disable-on-click"></span>
+            </label>
+          </td>
           <td><span class="filename"></span></td>
           <td><span class="author"></span></td>
           <td><span class="title"></span></td>
@@ -68,11 +78,20 @@ class CorpusFileList extends ResourceList {
       <table>
         <thead>
           <tr>
+            <th>
+              <label class="selection-action-trigger ${this.listContainerElement.dataset?.hasPermissionView == 'true' ? '' : 'hide'}" data-selection-action="select-all">
+                <input type="checkbox">
+                <span></span>
+              </label>
+            </th>
             <th>Filename</th>
             <th>Author</th>
             <th>Title</th>
             <th>Publishing year</th>
-            <th></th>
+            <th class="right-align">
+            <a class="selection-action-trigger btn-floating red waves-effect waves-light hide" data-selection-action="delete"><i class="material-icons">delete</i></a>
+            <a class="selection-action-trigger btn-floating service-color darken waves-effect waves-light hide" data-selection-action="download" data-service="corpus-analysis"><i class="material-icons">file_download</i></a>
+            </th>
           </tr>
         </thead>
         <tbody class="list"></tbody>
@@ -97,11 +116,12 @@ class CorpusFileList extends ResourceList {
   }
 
   onClick(event) {
+    if (event.target.closest('.disable-on-click') !== null) {return;}
     let listItemElement = event.target.closest('.list-item[data-id]');
     if (listItemElement === null) {return;}
     let itemId = listItemElement.dataset.id;
     let listActionElement = event.target.closest('.list-action-trigger[data-list-action]');
-    let listAction = listActionElement === null ? 'view' : listActionElement.dataset.listAction;
+    let listAction = listActionElement === null ? '' : listActionElement.dataset.listAction;
     switch (listAction) {
       case 'delete': {
         let values = this.listjs.get('id', itemId)[0].values();
@@ -145,12 +165,156 @@ class CorpusFileList extends ResourceList {
         window.location.href = `/corpora/${this.corpusId}/files/${itemId}`;
         break;
       }
+      case 'select': {
+        if (event.target.checked) {
+          this.selectedItemIds.push(itemId);
+        } else {
+          let index = this.selectedItemIds.indexOf(itemId);
+          if (index > -1) {
+            this.selectedItemIds.splice(index, 1);
+          }
+        }
+        this.renderingItemSelection();
+      }
+      default: {
+        break;
+      }
+    }
+  }
+
+  onSelectionAction(event) {
+    let selectionActionElement = event.target.closest('.selection-action-trigger[data-selection-action]');
+    let selectionAction = selectionActionElement.dataset.selectionAction;
+    let items = this.listjs.items;
+    let selectableItems = Array.from(items)
+      .filter(item => item.elm)
+      .map(item => item.elm.querySelector('input[type="checkbox"]'));
+
+    switch (selectionAction) {
+      case 'select-all': {
+        let selectedIds = Array.from(items)
+          .map(item => item.values().id);
+        if (event.target.checked) {
+          selectableItems.forEach(selectableItem => selectableItem.checked = true);
+          this.selectedItemIds = selectedIds;
+        } else {
+          selectableItems.forEach(checkbox => checkbox.checked = false);
+          this.selectedItemIds = this.selectedItemIds.filter(id => !selectedIds.includes(id));
+        }
+        this.renderingItemSelection();
+        break;
+      }
+      case 'delete': {
+        let modalElement = Utils.HTMLToElement(
+          `
+            <div class="modal">
+              <div class="modal-content">
+                <h4>Confirm Corpus File deletion</h4>
+                <p>Do you really want to delete the Corpus Files?</p>
+                  <ul id="selected-items-list"></ul>
+                <p>All files will be permanently deleted!</p>
+              </div>
+              <div class="modal-footer">
+                <a class="btn modal-close waves-effect waves-light">Cancel</a>
+                <a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
+              </div>
+            </div>
+          `
+        );
+        document.querySelector('#modals').appendChild(modalElement);
+        let itemList = document.querySelector('#selected-items-list');
+        this.selectedItemIds.forEach(selectedItemId => {
+          let listItem = this.listjs.get('id', selectedItemId)[0].elm;
+          let values = this.listjs.get('id', listItem.dataset.id)[0].values();
+          let itemElement = Utils.HTMLToElement(`<li> - ${values.title}</li>`);
+          itemList.appendChild(itemElement);
+        });
+        let modal = M.Modal.init(
+          modalElement,
+          {
+            dismissible: false,
+            onCloseEnd: () => {
+              modal.destroy();
+              modalElement.remove();
+            }
+          }
+        );
+        let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
+        confirmElement.addEventListener('click', (event) => {
+          this.selectedItemIds.forEach(selectedItemId => {
+            Requests.corpora.entity.files.ent.delete(this.corpusId, selectedItemId);
+          });
+          this.selectedItemIds = [];
+          this.renderingItemSelection();
+        });
+        modal.open();
+        break;
+      }
+      case 'download': {
+        this.selectedItemIds.forEach(selectedItemId => {
+          let downloadLink = document.createElement('a');
+          downloadLink.href = `/corpora/${this.corpusId}/files/${selectedItemId}/download`;
+          downloadLink.download = '';
+          downloadLink.click();
+        });
+        selectableItems.forEach(checkbox => checkbox.checked = false);
+        this.selectedItemIds = [];
+        this.renderingItemSelection();
+        break;
+      }  
       default: {
         break;
       }
     }
   }
 
+  renderingItemSelection() {
+    let selectionActionButtons;
+    if (this.hasPermissionManageFiles) {
+      selectionActionButtons = document.querySelectorAll('.selection-action-trigger:not([data-selection-action="select-all"])');
+    } else if (this.hasPermissionView) {
+      selectionActionButtons = document.querySelectorAll('.selection-action-trigger:not([data-selection-action="select-all"]):not([data-selection-action="delete"])');
+    }
+    let selectableItems = this.listjs.items;
+    let actionButtons = [];
+
+    Object.values(selectableItems).forEach(selectableItem => {
+      if (selectableItem.elm) {
+        let checkbox = selectableItem.elm.querySelector('input[type="checkbox"]');
+        if (checkbox.checked) {
+          selectableItem.elm.classList.add('grey', 'lighten-3');
+        } else {
+          selectableItem.elm.classList.remove('grey', 'lighten-3');
+        }
+        let itemActionButtons = [];
+        if (this.hasPermissionManageFiles) {
+          itemActionButtons = selectableItem.elm.querySelectorAll('.list-action-trigger:not([data-list-action="select"])');
+        } else if (this.hasPermissionView) {
+          itemActionButtons = selectableItem.elm.querySelectorAll('.list-action-trigger:not([data-list-action="select"]):not([data-list-action="delete"]):not([data-list-action="view"])');
+        }
+        itemActionButtons.forEach(itemActionButton => {
+          actionButtons.push(itemActionButton);
+        });
+      }
+    });
+    // Hide item action buttons if > 0 item is selected and show selection action buttons
+    if (this.selectedItemIds.length > 0) {
+      selectionActionButtons.forEach(selectionActionButton => {
+        selectionActionButton.classList.remove('hide');
+      });
+      actionButtons.forEach(actionButton => {
+        actionButton.classList.add('hide');
+      });
+    } else {
+      selectionActionButtons.forEach(selectionActionButton => {
+        selectionActionButton.classList.add('hide');
+      });
+      actionButtons.forEach(actionButton => {
+        actionButton.classList.remove('hide');
+      });
+    }
+  }
+
   onPatch(patch) {
     let re = new RegExp(`^/users/${this.userId}/corpora/${this.corpusId}/files/([A-Za-z0-9]*)`);
     let filteredPatch = patch.filter(operation => re.test(operation.path));
diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2
index 07d2d59a303cd1607d923a728fda3f895c06b84a..bee5b7b2fd7deb75f64a8cff9af4f3e9840505c7 100644
--- a/app/templates/_scripts.html.j2
+++ b/app/templates/_scripts.html.j2
@@ -40,7 +40,6 @@
   'js/ResourceLists/ResourceList.js',
   'js/ResourceLists/CorpusFileList.js',
   'js/ResourceLists/CorpusList.js',
-  'js/ResourceLists/FollowedCorpusList.js',
   'js/ResourceLists/PublicCorpusList.js',
   'js/ResourceLists/JobList.js',
   'js/ResourceLists/JobInputList.js',
diff --git a/app/templates/corpora/public_corpus.html.j2 b/app/templates/corpora/public_corpus.html.j2
new file mode 100644
index 0000000000000000000000000000000000000000..fb13c252844954d9ca478a4a53652fb66c64e6fb
--- /dev/null
+++ b/app/templates/corpora/public_corpus.html.j2
@@ -0,0 +1,379 @@
+{% extends "base.html.j2" %}
+{% import "materialize/wtf.html.j2" as wtf %}
+
+{% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
+
+{% block page_content %}
+<div class="container">
+  <div class="row">
+    <div class="col s12">
+      <h1>{{ corpus.title }}</h1>
+    </div>
+    <div class="col s12 l7">
+      <div class="card service-color-border border-darken" data-service="corpus-analysis" style="border-top: 10px solid">
+        <div class="card-content">
+          <span class="chip corpus-status-text corpus-status-color white-text" data-status="{{ corpus.status.name }}"></span></p>
+          <div class="row">
+
+            <div class="col s12">
+              <div class="input-field">
+                <label>Description</label>
+                <input disabled type="text" value="{{ corpus.description }}">
+              </div>
+            </div>
+
+            <div class="col s12 m6">
+              <div class="input-field">
+                <label for="corpus-creation-date">Creation date</label>
+                <input disabled type="text" value="{{ corpus.creation_date }}">
+              </div>
+            </div>
+
+            <div class="col s12 m6">
+              <div class="input-field">
+                <label for="corpus-token-ratio">Nr. of tokens used <sup><i class="material-icons tooltipped tiny" data-position="bottom" data-tooltip="Current number of tokens in this corpus. Updates after every analyze session.">help</i></sup></label>
+                <input disabled type="text" value="{{ corpus.num_tokens }}">
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    {% if cfr.has_permission('VIEW') %}
+    <div class="col s12 l5">
+      <div class="card">
+        <div class="card-content">
+          <span class="card-title">Actions</span>
+          <div class="row">
+            {% if cfr.has_permission('MANAGE_FILES') %}
+            <div class="col s12 l6" style="padding: 0 2.5px;">
+              <a class="action-button btn disabled waves-effect waves-light" data-action="build-request" style="width: 100%;"><i class="nopaque-icons left">K</i>Build</a>
+            </div>
+            <div class="col s12 l6" style="padding: 0 2.5px;">
+              {% if corpus.status.name in ['BUILT', 'STARTING_ANALYSIS_SESSION', 'RUNNING_ANALYSIS_SESSION', 'CANCELING_ANALYSIS_SESSION'] and current_user.is_following_corpus(corpus) %}
+              <a class="action-button btn waves-effect waves-light" data-action="analyze" href="{{ url_for('corpora.analysis', corpus_id=corpus.id) }}" style="width: 100%;"><i class="material-icons left">search</i>Analyze</a>
+              {% else %}
+              <a class="action-button btn disabled waves-effect waves-light" data-action="analyze" style="width: 100%;"><i class="material-icons left">search</i>Analyze</a>
+              {% endif %}
+            </div>
+            {% endif %}
+            {% if current_user.is_following_corpus(corpus) %}
+            <div class="col s12 l6" style="padding: 5px 2.5px 0 2.5px;">
+              <a class="action-button btn red waves-effect waves-light" data-action="unfollow-request" style="width: 100%;"><i class="material-icons left outlined">close</i>Unfollow Corpus</a>
+            </div>
+            {% endif %}
+          </div>
+          {% if cfr.has_permission('MANAGE_FOLLOWERS') %}
+          <span class="card-title">Social</span>
+          <div class="row">
+            <div class="col s12 l6" style="padding: 0 2.5px;">
+              <a class="btn waves-effect waves-light modal-trigger" href="#invite-user-modal" style="width: 100%;"><i class="material-icons left">person_add</i>invite user</a>
+            </div>
+            <div class="col s12 l6" style="padding: 0 2.5px;">
+              <a class="btn waves-effect waves-light modal-trigger" href="#share-link-modal" style="width: 100%;"><i class="material-icons left">link</i>Share link</a>
+            </div>
+          </div>
+          {% endif %}
+        </div>
+      </div>
+    </div>
+    {% endif %} 
+
+    <div class="col s12">
+      <div class="card">
+        <div class="card-content">
+          <span class="card-title" id="files">Corpus Owner</span>
+          <div class="row">
+            <div class="col s12">
+              <table>
+                <tr>
+                  <td style="width:10%; margin-top:25px;">
+                    <img src="{{ url_for('users.user_avatar', user_id=corpus.user.id) }}" alt="user-image" class="circle responsive-img">
+                  </td>
+                  <td></td>
+                  <td>
+                    <ul>
+                      <li><b>{{ corpus.user.username }}</b></li>
+                      {% if corpus.user.full_name %}
+                      <li>{{ corpus.user.full_name }}</li>
+                      {% endif %}
+                      {% if corpus.user.show_email %}
+                      <li></li><a href="mailto:{{ corpus.user.email }}">{{ corpus.user.email }}</a></li>
+                      {% endif %}
+                    </ul>
+                  </td>
+                </tr>
+              </table>
+              <br>
+              <p></p>
+              {% if not current_user.is_following_corpus(corpus) and corpus.user.has_profile_privacy_setting('SHOW_EMAIL') %}
+              <a class="waves-effect waves-light btn-small" href="mailto:{{ corpus.user.email }}">Request Corpus</a>
+              {% endif %}
+              <a class="waves-effect waves-light btn-small" href="{{ url_for('users.user', user_id=corpus.user.id) }}">View profile</a>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="col s12">
+      <div class="card">
+        <div class="card-content">
+          <span class="card-title" id="corpus-files">Corpus files</span>
+          <div class="corpus-file-list no-autoinit" id="corpus-file-list" data-has-permission-view="{{ cfr.has_permission('VIEW')|tojson }}" data-has-permission-manage-files="{{ cfr.has_permission('MANAGE_FILES')|tojson }}" data-corpus-id="{{ corpus.hashid }}"></div>
+        </div>
+        {% if cfr.has_permission('MANAGE_FILES') %}
+        <div class="card-action right-align">
+          <a href="{{ url_for('corpora.create_corpus_file', corpus_id=corpus.id) }}" class="btn waves-effect waves-light"><i class="material-icons left">add</i>Add corpus file</a>
+        </div>
+        {% endif %}
+      </div>
+    </div>
+
+    {% if cfr.has_permission('MANAGE_FOLLOWERS') %}
+    <div class="col s12">
+      <div class="card">
+        <div class="card-content">
+          <span class="card-title" id="corpus-followers">Corpus followers</span>
+          <div class="corpus-follower-list no-autoinit"></div>
+        </div>
+      </div>
+    </div>
+    {% endif %}
+
+  </div>
+</div>
+{% endblock page_content %}
+
+{% block modals %}
+{{ super() }}
+
+{% if cfr.has_permission('MANAGE_FOLLOWERS') %}
+<div class="modal no-autoinit" id="invite-user-modal">
+  <div class="modal-content">
+    <h4>Invite a nopaque user by username</h4>
+    <p>
+      Add other nopaque users as followers to your corpus. You can also add multiple 
+      users at the same time. Added users get the role of "viewer" 
+      by default, so they are only allowed to analyze files within nopaque, but not 
+      to download or edit them. You can customize the roles later below.
+    </p>
+    <p><b>Please make sure that the invited users are legally allowed to view the included corpus files.</b></p>
+    <div class="row">
+      <div class="col s10">
+        <div class="chips no-autoinit" id="invite-user-modal-search"></div>
+      </div>
+      <div class="col s2">
+        <br class="hide-on-med-and-down">
+        <a class="btn modal-close waves-effect waves-light" id="invite-user-modal-invite-button">Invite<i class="material-icons right">send</i></a>
+      </div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <a class="modal-close waves-effect waves-green btn-flat">Close</a>
+  </div>
+</div>
+
+<div class="modal no-autoinit" id="share-link-modal">
+  <div class="modal-content">
+    <h4>Create a link to share your corpus</h4>
+    <p>
+      With the link other users follow your corpus directly, if it has not expired. 
+      You can set different roles via the link, you can also edit them later in the menu below. 
+      It is recommended not to set the expiration date of the link too far.
+    </p>
+    <p><b>Please make sure that the invited users are legally allowed to view the included corpus files.</b></p>
+    <div class="row">
+      <div class="col s12 l2">
+        <div class="input-field">
+          <i class="material-icons prefix">badge</i>
+          <select id="share-link-modal-corpus-follower-role-select">
+            {% for cfr in cfrs %}
+            <option value="{{ cfr.name }}">{{ cfr.name }}</option>
+            {% endfor %}
+          </select>
+          <label>Role</label>
+        </div>
+      </div>
+      <div class="col s12 l3">
+        <div class="input-field">
+          <i class="material-icons prefix">calendar_month</i>
+          <input type="text" class="datepicker no-autoinit" id="share-link-modal-expiration-date-datepicker">
+          <label for="expiration-date">Expiration date</label>
+        </div>
+      </div>
+      <div class="col s12 l2">
+        <br class="hide-on-med-and-down">
+        <a class="btn waves-effect waves-light" id="share-link-modal-create-button">Create<i class="material-icons right">send</i></a>
+      </div>
+
+      <div class="col s12 l5">
+        <div class="row hide" id="share-link-modal-output-container">
+          <div class="col s9">
+            <div class="input-field">
+              <input disabled id="share-link-modal-output-field" readonly type="text">
+            </div>
+          </div>
+          <div class="col s3">
+            <br class="hide-on-med-and-down">
+            <a class="btn-small waves-effect waves-light" id="share-link-modal-output-copy-button"><i class="material-icons left">content_copy</i>Copy</a>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <a class="modal-close waves-effect waves-green btn-flat">Close</a>
+  </div>
+</div>
+{% endif %}
+
+{% endblock modals %}
+
+{% block scripts %}
+{{ super() }}
+<script>
+
+let publicCorpusFileList = new CorpusFileList(document.querySelector('#corpus-file-list'));
+publicCorpusFileList.add(
+  [
+    {% for corpus_file in corpus.files %}
+    {{ corpus_file.to_json_serializeable()|tojson }},
+    {% endfor %}
+  ]
+);
+
+{% if cfr.has_permission('MANAGE_FOLLOWERS') %}
+let publicCorpusFollowerList = new CorpusFollowerList(document.querySelector('.corpus-follower-list'));
+publicCorpusFollowerList.add(
+  [
+    {% for cfa in cfas %}
+    {{ cfa.to_json_serializeable()|tojson }},
+    {% endfor %}
+  ]
+);
+{% endif %}
+
+// #region Corpus Unfollow Request
+{% if current_user.is_following_corpus(corpus) %}
+  let unfollowRequestElement = document.querySelector('.action-button[data-action="unfollow-request"]');
+  unfollowRequestElement.addEventListener('click', () => {
+    Requests.corpora.entity.followers.entity.delete({{ corpus.hashid|tojson }}, {{ current_user.hashid|tojson }})
+      .then((response) => {
+        window.location.reload();
+      });
+  });
+{% endif %}
+// #endregion Corpus Unfollow Request
+
+{% if cfr.has_permission('MANAGE_FOLLOWERS') %}
+// #region Invite user
+let inviteUserModalElement = document.querySelector('#invite-user-modal');
+let inviteUserModalSearchElement = document.querySelector('#invite-user-modal-search');
+let inviteUserModalInviteButtonElement = document.querySelector('#invite-user-modal-invite-button');
+let users = {
+  {% for user in users %}
+    {{ user.username|tojson }}: {{ url_for('users.user_avatar', user_id=user.id)|tojson }}
+    {% if not loop.last %},{% endif %}
+  {% endfor %}
+};
+
+let inviteUserModalSearch = M.Chips.init(
+  inviteUserModalSearchElement,
+  {
+    autocompleteOptions: {
+      data: users
+    },
+    limit: 3,
+    onChipAdd: (a, chipElement) => {
+      if (!(chipElement.firstChild.data in inviteUserModalSearch.autocomplete.options.data)) {
+        chipElement.firstElementChild.click();
+      }
+    },
+    placeholder: 'Enter username',
+    secondaryPlaceholder: 'Add more users'
+  }
+);
+
+M.Modal.init(
+  inviteUserModalElement,
+  {
+    onOpenStart: (modalElement, modalTriggerElement) => {
+      while (inviteUserModalSearch.chipsData.length > 0) {
+        inviteUserModalSearch.deleteChip(0);
+      }
+    }
+  }
+)
+
+inviteUserModalInviteButtonElement.addEventListener('click', (event) => {
+  let usernames = inviteUserModalSearch.chipsData.map((chipData) => chipData.tag);
+  Requests.corpora.entity.followers.add({{ corpus.hashid|tojson }}, usernames);
+});
+// #endregion Invite user
+
+// #region Share link
+let shareLinkModalElement = document.querySelector('#share-link-modal');
+let shareLinkModalCorpusFollowerRoleSelectElement = document.querySelector('#share-link-modal-corpus-follower-role-select');
+let shareLinkModalExpirationDateDatepickerElement = document.querySelector('#share-link-modal-expiration-date-datepicker');
+let shareLinkModalCreateButtonElement = document.querySelector('#share-link-modal-create-button');
+let shareLinkModalOutputContainerElement = document.querySelector('#share-link-modal-output-container');
+let shareLinkModalOutputFieldElement = document.querySelector('#share-link-modal-output-field');
+let shareLinkModalOutputCopyButtonElement = document.querySelector('#share-link-modal-output-copy-button');
+
+let today = new Date();
+let tomorrow = new Date();
+tomorrow.setDate(today.getDate() + 1);
+let oneWeekLater = new Date();
+oneWeekLater.setDate(today.getDate() + 7);
+let fourWeeksLater = new Date();
+fourWeeksLater.setDate(today.getDate() + 28);
+
+M.Datepicker.init(
+  shareLinkModalExpirationDateDatepickerElement,
+  {
+    container: document.querySelector('main'),
+    defaultDate: oneWeekLater,
+    setDefaultDate: true,
+    minDate: tomorrow,
+    maxDate: fourWeeksLater
+  }
+);
+
+M.Modal.init(
+  shareLinkModalElement,
+  {
+    onOpenStart: (modalElement, modalTriggerElement) => {
+      shareLinkModalOutputFieldElement.value = '';
+      shareLinkModalOutputContainerElement.classList.add('hide');
+    }
+  }
+)
+
+shareLinkModalCreateButtonElement.addEventListener('click', (event) => {
+  let role = shareLinkModalCorpusFollowerRoleSelectElement.value;
+  let expiration = shareLinkModalExpirationDateDatepickerElement.value
+  Requests.corpora.entity.generateShareLink({{ corpus.hashid|tojson }}, role, expiration)
+    .then((response) => {
+      response.json()
+        .then((json) => {
+          shareLinkModalOutputContainerElement.classList.remove('hide');
+          shareLinkModalOutputFieldElement.value = json.corpusShareLink;
+        });
+    });
+});
+
+shareLinkModalOutputCopyButtonElement.addEventListener('click', (event) => {
+  navigator.clipboard.writeText(shareLinkModalOutputFieldElement.value)
+    .then(
+      () => {app.flash('Copied!');},
+      () => {app.flash('Could not copy to clipboard. Please copy manually.', 'error');}
+    );
+  
+});
+// #endregion Share link
+{% endif %}
+
+</script>
+{% endblock scripts %}