mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-09 14:46:47 +08:00
Asynchroniously fetch actual smart annotation object data [SCI-11513]
This commit is contained in:
parent
50809dca7d
commit
321733010b
5 changed files with 105 additions and 18 deletions
|
@ -3,4 +3,10 @@
|
|||
@apply flex m-auto h-[30px] w-[30px] animate-spin;
|
||||
background: image-url("sn-loader.svg") center center no-repeat;
|
||||
}
|
||||
|
||||
.sci-loader-inline {
|
||||
@apply inline-block h-[1.2em] w-[1.2em] animate-spin;
|
||||
background: image-url("sn-loader.svg") center center no-repeat;
|
||||
background-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SmartAnnotationsController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
|
@ -14,15 +16,22 @@ class SmartAnnotationsController < ApplicationController
|
|||
}
|
||||
end
|
||||
|
||||
def redirect
|
||||
redirect_to redirect_path
|
||||
def show
|
||||
if params[:data]
|
||||
render json: {
|
||||
name: resource_readable? && resource.name,
|
||||
type: resource_tag
|
||||
}
|
||||
else
|
||||
redirect_to redirect_path
|
||||
end
|
||||
end
|
||||
|
||||
def user
|
||||
user_team_assignment = resource.user_assignments.find_by(assignable: current_team)
|
||||
|
||||
render json: {
|
||||
full_name: resource.full_name,
|
||||
name: resource.name,
|
||||
email: resource.email,
|
||||
avatar_url: user_avatar_absolute_url(resource, :thumb),
|
||||
info: I18n.t(
|
||||
|
@ -36,15 +45,23 @@ class SmartAnnotationsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def resource
|
||||
return @resource_class ||= User.find(params[:tag][1..].split('~')[1].base62_decode) if params[:tag][0] == '@'
|
||||
def sa_tag
|
||||
@sa_tag ||= params[:tag][1..].split('~')[1]
|
||||
end
|
||||
|
||||
_, resource_tag, resource_id = params[:tag][1..].split('~')
|
||||
def resource_tag
|
||||
@resource_tag ||= resource.is_a?(RepositoryRow) ? repository_acronym(resource.repository) : sa_tag
|
||||
end
|
||||
|
||||
def resource
|
||||
return @resource_class ||= User.find(sa_tag.base62_decode) if params[:tag][0] == '@'
|
||||
|
||||
resource_id = params[:tag][1..].split('~').last
|
||||
|
||||
resource_id = resource_id.base62_decode
|
||||
|
||||
resource_class =
|
||||
case resource_tag
|
||||
case sa_tag
|
||||
when 'prj'
|
||||
Project
|
||||
when 'exp'
|
||||
|
@ -58,6 +75,16 @@ class SmartAnnotationsController < ApplicationController
|
|||
@resource ||= resource_class.find(resource_id)
|
||||
end
|
||||
|
||||
def resource_readable?
|
||||
@resource_readable ||=
|
||||
case resource
|
||||
when RepositoryRow
|
||||
resource.repository.readable_by_user?(current_user)
|
||||
else
|
||||
resource.readable_by_user?(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_path
|
||||
case resource
|
||||
when Project
|
||||
|
@ -70,4 +97,13 @@ class SmartAnnotationsController < ApplicationController
|
|||
repository_repository_row_path(resource.repository, resource)
|
||||
end
|
||||
end
|
||||
|
||||
def repository_acronym(repository)
|
||||
words = repository.name.strip.split
|
||||
case words.size
|
||||
when 1 then words[0][0..2]
|
||||
when 2 then words[0][0..1] + words[1][0]
|
||||
else words[0..2].map(&:chr).join
|
||||
end.capitalize
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/* global I18n */
|
||||
|
||||
const SA_REGEX = /\[@([^~\]]+)~([0-9a-zA-Z]+)\]|\[#(.*?)~(rep_item|prj|exp|tsk)~([0-9a-zA-Z]+)\]/g;
|
||||
|
||||
const escapeHtml = (unsafe) => (
|
||||
unsafe.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
|
@ -6,6 +10,17 @@ const escapeHtml = (unsafe) => (
|
|||
.replaceAll("'", ''')
|
||||
);
|
||||
|
||||
const isInViewport = (element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const html = document.documentElement;
|
||||
|
||||
return (
|
||||
rect.top >= 0 && rect.left >= 0
|
||||
&& rect.bottom <= (window.innerHeight || html.clientHeight)
|
||||
&& rect.right <= (window.innerWidth || html.clientWidth)
|
||||
);
|
||||
};
|
||||
|
||||
const renderUserMention = (tag, userName) => {
|
||||
const safeUserName = escapeHtml(userName);
|
||||
|
||||
|
@ -20,37 +35,63 @@ const renderUserMention = (tag, userName) => {
|
|||
data-toggle="popover"
|
||||
data-content=""
|
||||
data-url="/sa/u?tag=${tag}"
|
||||
>${safeUserName}</a>`;
|
||||
><span class="sa-name"><span class="sci-loader-inline"></span></span></a>`;
|
||||
};
|
||||
|
||||
window.renderSmartAnnotations = (text) => (
|
||||
text.replace(/\[@([^~\]]+)~([0-9a-zA-Z]+)\]|\[#(.*?)~(rep_item|prj|exp|tsk)~([0-9a-zA-Z]+)\]/g, (match, userName, _userId, label, type) => {
|
||||
|
||||
text.replace(SA_REGEX, (match, userName, _userId, label, type) => {
|
||||
const tag = encodeURIComponent(match.slice(1, -1));
|
||||
|
||||
if (userName) {
|
||||
return renderUserMention(tag, userName);
|
||||
}
|
||||
|
||||
const safeLabel = escapeHtml(label);
|
||||
const safeType = escapeHtml(type);
|
||||
|
||||
switch (type) {
|
||||
case 'rep_item':
|
||||
return `<a class="sa-link record-info-link" href="/sa?tag=${tag}"><span class="sa-type">INV</span>${safeLabel}</a>`;
|
||||
return `<a class="sa-link record-info-link" href="/sa?tag=${tag}"><span class="sa-type"></span><span class="sa-name"><span class="sci-loader-inline"></span></span></a>`;
|
||||
default:
|
||||
return `<a class="sa-link" href="/sa?tag=${tag}" target="_blank"><span class="sa-type">${safeType}</span>${safeLabel}</a>`;
|
||||
return `<a class="sa-link" href="/sa?tag=${tag}" target="_blank"><span class="sa-type"></span><span class="sa-name"><span class="sci-loader-inline"></span></span></a>`;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
async function fetchSmartAnnotationData(element) {
|
||||
const response = await fetch(`${element.getAttribute('href') || element.getAttribute('data-url')}&data=true`);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
element.querySelector('.sa-name').innerHTML = data.name || `(${I18n.t('general.private')})`;
|
||||
|
||||
if (element.querySelector('.sa-type')) {
|
||||
element.querySelector('.sa-type').innerHTML = data.type;
|
||||
}
|
||||
}
|
||||
|
||||
window.renderElementSmartAnnotations = (element) => {
|
||||
if (!element.innerHTML.match(SA_REGEX)) return true;
|
||||
|
||||
element.innerHTML = window.renderSmartAnnotations(element.innerHTML);
|
||||
|
||||
// Schedule fetch of data for each annotation element when it scrolls into viewport
|
||||
element.querySelectorAll('.sa-link, .user-tooltip').forEach((el) => {
|
||||
if (isInViewport(el)) {
|
||||
fetchSmartAnnotationData(el);
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchFunction = () => {
|
||||
fetchSmartAnnotationData(el);
|
||||
window.removeEventListener('scroll', fetchFunction);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', fetchFunction);
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$(document).on('focus', '.user-tooltip', function () {
|
||||
// using legacy jQuery style, as we need it for tooltips anyway
|
||||
$(document).on('click', '.user-tooltip', function () {
|
||||
$.get($(this).data('url'), (data) => {
|
||||
const content = `<div class="user-name-popover-wrapper">
|
||||
<div class='col-xs-3'>
|
||||
|
@ -62,7 +103,7 @@ $(document).on('focus', '.user-tooltip', function () {
|
|||
<div class='col-xs-9 pl-3'>
|
||||
<div class='row'>
|
||||
<div class='col-xs-12 text-left font-bold'>
|
||||
${escapeHtml(data.full_name)}
|
||||
${escapeHtml(data.name)}
|
||||
</div>
|
||||
</div>
|
||||
<div class='row'>
|
||||
|
|
|
@ -20,6 +20,10 @@ module PermissionCheckableModel
|
|||
user_role_permissions.include?(permission)
|
||||
end
|
||||
|
||||
def readable_by_user?(user)
|
||||
permission_granted?(user, "::#{self.class.to_s.split('::').first}Permissions".constantize::READ)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_user_role_permissions(user)
|
||||
|
|
|
@ -23,7 +23,7 @@ Rails.application.routes.draw do
|
|||
|
||||
root 'dashboards#show'
|
||||
|
||||
get '/sa', to: 'smart_annotations#redirect'
|
||||
get '/sa', to: 'smart_annotations#show'
|
||||
get '/sa/u', to: 'smart_annotations#user'
|
||||
|
||||
resources :navigations, only: [] do
|
||||
|
|
Loading…
Add table
Reference in a new issue