code.gitea.io/gitea@v1.21.7/web_src/js/features/notification.js (about)

     1  import $ from 'jquery';
     2  
     3  const {appSubUrl, csrfToken, notificationSettings, assetVersionEncoded} = window.config;
     4  let notificationSequenceNumber = 0;
     5  
     6  export function initNotificationsTable() {
     7    const table = document.getElementById('notification_table');
     8    if (!table) return;
     9  
    10    // when page restores from bfcache, delete previously clicked items
    11    window.addEventListener('pageshow', (e) => {
    12      if (e.persisted) { // page was restored from bfcache
    13        const table = document.getElementById('notification_table');
    14        const unreadCountEl = document.querySelector('.notifications-unread-count');
    15        let unreadCount = parseInt(unreadCountEl.textContent);
    16        for (const item of table.querySelectorAll('.notifications-item[data-remove="true"]')) {
    17          item.remove();
    18          unreadCount -= 1;
    19        }
    20        unreadCountEl.textContent = unreadCount;
    21      }
    22    });
    23  
    24    // mark clicked unread links for deletion on bfcache restore
    25    for (const link of table.querySelectorAll('.notifications-item[data-status="1"] .notifications-link')) {
    26      link.addEventListener('click', (e) => {
    27        e.target.closest('.notifications-item').setAttribute('data-remove', 'true');
    28      });
    29    }
    30  
    31    $('#notification_table .button').on('click', function () {
    32      (async () => {
    33        const data = await updateNotification(
    34          $(this).data('url'),
    35          $(this).data('status'),
    36          $(this).data('page'),
    37          $(this).data('q'),
    38          $(this).data('notification-id'),
    39        );
    40  
    41        if ($(data).data('sequence-number') === notificationSequenceNumber) {
    42          $('#notification_div').replaceWith(data);
    43          initNotificationsTable();
    44        }
    45        await updateNotificationCount();
    46      })();
    47      return false;
    48    });
    49  }
    50  
    51  async function receiveUpdateCount(event) {
    52    try {
    53      const data = JSON.parse(event.data);
    54  
    55      for (const count of document.querySelectorAll('.notification_count')) {
    56        count.classList.toggle('gt-hidden', data.Count === 0);
    57        count.textContent = `${data.Count}`;
    58      }
    59      await updateNotificationTable();
    60    } catch (error) {
    61      console.error(error, event);
    62    }
    63  }
    64  
    65  export function initNotificationCount() {
    66    const notificationCount = $('.notification_count');
    67  
    68    if (!notificationCount.length) {
    69      return;
    70    }
    71  
    72    let usingPeriodicPoller = false;
    73    const startPeriodicPoller = (timeout, lastCount) => {
    74      if (timeout <= 0 || !Number.isFinite(timeout)) return;
    75      usingPeriodicPoller = true;
    76      lastCount = lastCount ?? notificationCount.text();
    77      setTimeout(async () => {
    78        await updateNotificationCountWithCallback(startPeriodicPoller, timeout, lastCount);
    79      }, timeout);
    80    };
    81  
    82    if (notificationSettings.EventSourceUpdateTime > 0 && window.EventSource && window.SharedWorker) {
    83      // Try to connect to the event source via the shared worker first
    84      const worker = new SharedWorker(`${__webpack_public_path__}js/eventsource.sharedworker.js?v=${assetVersionEncoded}`, 'notification-worker');
    85      worker.addEventListener('error', (event) => {
    86        console.error('worker error', event);
    87      });
    88      worker.port.addEventListener('messageerror', () => {
    89        console.error('unable to deserialize message');
    90      });
    91      worker.port.postMessage({
    92        type: 'start',
    93        url: `${window.location.origin}${appSubUrl}/user/events`,
    94      });
    95      worker.port.addEventListener('message', (event) => {
    96        if (!event.data || !event.data.type) {
    97          console.error('unknown worker message event', event);
    98          return;
    99        }
   100        if (event.data.type === 'notification-count') {
   101          const _promise = receiveUpdateCount(event.data);
   102        } else if (event.data.type === 'no-event-source') {
   103          // browser doesn't support EventSource, falling back to periodic poller
   104          if (!usingPeriodicPoller) startPeriodicPoller(notificationSettings.MinTimeout);
   105        } else if (event.data.type === 'error') {
   106          console.error('worker port event error', event.data);
   107        } else if (event.data.type === 'logout') {
   108          if (event.data.data !== 'here') {
   109            return;
   110          }
   111          worker.port.postMessage({
   112            type: 'close',
   113          });
   114          worker.port.close();
   115          window.location.href = appSubUrl;
   116        } else if (event.data.type === 'close') {
   117          worker.port.postMessage({
   118            type: 'close',
   119          });
   120          worker.port.close();
   121        }
   122      });
   123      worker.port.addEventListener('error', (e) => {
   124        console.error('worker port error', e);
   125      });
   126      worker.port.start();
   127      window.addEventListener('beforeunload', () => {
   128        worker.port.postMessage({
   129          type: 'close',
   130        });
   131        worker.port.close();
   132      });
   133  
   134      return;
   135    }
   136  
   137    startPeriodicPoller(notificationSettings.MinTimeout);
   138  }
   139  
   140  async function updateNotificationCountWithCallback(callback, timeout, lastCount) {
   141    const currentCount = $('.notification_count').text();
   142    if (lastCount !== currentCount) {
   143      callback(notificationSettings.MinTimeout, currentCount);
   144      return;
   145    }
   146  
   147    const newCount = await updateNotificationCount();
   148    let needsUpdate = false;
   149  
   150    if (lastCount !== newCount) {
   151      needsUpdate = true;
   152      timeout = notificationSettings.MinTimeout;
   153    } else if (timeout < notificationSettings.MaxTimeout) {
   154      timeout += notificationSettings.TimeoutStep;
   155    }
   156  
   157    callback(timeout, newCount);
   158    if (needsUpdate) {
   159      await updateNotificationTable();
   160    }
   161  }
   162  
   163  async function updateNotificationTable() {
   164    const notificationDiv = $('#notification_div');
   165    if (notificationDiv.length > 0) {
   166      const data = await $.ajax({
   167        type: 'GET',
   168        url: `${appSubUrl}/notifications${window.location.search}`,
   169        data: {
   170          'div-only': true,
   171          'sequence-number': ++notificationSequenceNumber,
   172        }
   173      });
   174      if ($(data).data('sequence-number') === notificationSequenceNumber) {
   175        notificationDiv.replaceWith(data);
   176        initNotificationsTable();
   177      }
   178    }
   179  }
   180  
   181  async function updateNotificationCount() {
   182    const data = await $.ajax({
   183      type: 'GET',
   184      url: `${appSubUrl}/notifications/new`,
   185      headers: {
   186        'X-Csrf-Token': csrfToken,
   187      },
   188    });
   189  
   190    const notificationCount = $('.notification_count');
   191    if (data.new === 0) {
   192      notificationCount.addClass('gt-hidden');
   193    } else {
   194      notificationCount.removeClass('gt-hidden');
   195    }
   196  
   197    notificationCount.text(`${data.new}`);
   198  
   199    return `${data.new}`;
   200  }
   201  
   202  async function updateNotification(url, status, page, q, notificationID) {
   203    if (status !== 'pinned') {
   204      $(`#notification_${notificationID}`).remove();
   205    }
   206  
   207    return $.ajax({
   208      type: 'POST',
   209      url,
   210      data: {
   211        _csrf: csrfToken,
   212        notification_id: notificationID,
   213        status,
   214        page,
   215        q,
   216        noredirect: true,
   217        'sequence-number': ++notificationSequenceNumber,
   218      },
   219    });
   220  }