|
|
@@ -1,3 +1,5 @@
|
|
|
+/* global $, userID, postData */
|
|
|
+/* eslint-env browser */
|
|
|
'use strict';
|
|
|
|
|
|
(function() {
|
|
|
@@ -13,13 +15,15 @@
|
|
|
const entry = root.find('.global-assistant-entry');
|
|
|
const projectId = root.data('project-id');
|
|
|
const storageKey = 'global-assistant-history-v2-' + projectId + '-' + userID;
|
|
|
- const entryPositionKey = 'global-assistant-entry-position-' + userID;
|
|
|
+ const entryPositionKey = 'global-assistant-entry-position-v2-' + userID;
|
|
|
let history = loadHistory();
|
|
|
let conversationId = getConversationId();
|
|
|
let sending = false;
|
|
|
let entryPosition = loadEntryPosition();
|
|
|
let entryDrag = null;
|
|
|
let suppressEntryClick = false;
|
|
|
+ let suppressClickTimer = null;
|
|
|
+ let entryExpanded = false;
|
|
|
|
|
|
applyEntryPosition();
|
|
|
|
|
|
@@ -30,7 +34,7 @@
|
|
|
return;
|
|
|
}
|
|
|
panel.addClass('show').attr('aria-hidden', 'false');
|
|
|
- $(this).hide();
|
|
|
+ $(this).attr('aria-expanded', 'true').hide();
|
|
|
input.trigger('focus');
|
|
|
scrollToBottom();
|
|
|
});
|
|
|
@@ -38,42 +42,69 @@
|
|
|
entry.on('pointerdown', function(event) {
|
|
|
const pointer = event.originalEvent;
|
|
|
if (pointer.pointerType === 'mouse' && pointer.button !== 0) return;
|
|
|
+ if (entryDrag) return;
|
|
|
+ const rect = this.getBoundingClientRect();
|
|
|
entryDrag = {
|
|
|
pointerId: pointer.pointerId,
|
|
|
startX: pointer.clientX,
|
|
|
startY: pointer.clientY,
|
|
|
- positionX: entryPosition.x,
|
|
|
- positionY: entryPosition.y,
|
|
|
+ grabRatio: rect.width ? (pointer.clientX - rect.left) / rect.width : 0.5,
|
|
|
+ grabY: pointer.clientY - rect.top,
|
|
|
+ originalPosition: Object.assign({}, entryPosition),
|
|
|
moved: false,
|
|
|
};
|
|
|
+ clearTimeout(suppressClickTimer);
|
|
|
suppressEntryClick = false;
|
|
|
this.setPointerCapture(pointer.pointerId);
|
|
|
- entry.addClass('dragging');
|
|
|
event.preventDefault();
|
|
|
});
|
|
|
|
|
|
entry.on('pointermove', function(event) {
|
|
|
const pointer = event.originalEvent;
|
|
|
if (!entryDrag || pointer.pointerId !== entryDrag.pointerId) return;
|
|
|
- const offsetX = entryDrag.startX - pointer.clientX;
|
|
|
- const offsetY = entryDrag.startY - pointer.clientY;
|
|
|
- if (Math.abs(offsetX) > 3 || Math.abs(offsetY) > 3) entryDrag.moved = true;
|
|
|
+ const offsetX = pointer.clientX - entryDrag.startX;
|
|
|
+ const offsetY = pointer.clientY - entryDrag.startY;
|
|
|
+ if (!entryDrag.moved && Math.abs(offsetX) <= 4 && Math.abs(offsetY) <= 4) return;
|
|
|
+ entryDrag.moved = true;
|
|
|
+ entryExpanded = false;
|
|
|
+ entry.addClass('dragging');
|
|
|
+ const width = 92;
|
|
|
entryPosition = clampEntryPosition({
|
|
|
- x: entryDrag.positionX + offsetX,
|
|
|
- y: entryDrag.positionY + offsetY,
|
|
|
+ right: window.innerWidth - pointer.clientX - width * (1 - entryDrag.grabRatio),
|
|
|
+ top: pointer.clientY - entryDrag.grabY,
|
|
|
+ docked: false,
|
|
|
});
|
|
|
applyEntryPosition();
|
|
|
event.preventDefault();
|
|
|
});
|
|
|
|
|
|
- entry.on('pointerup pointercancel', function(event) {
|
|
|
+ entry.on('pointerup pointercancel lostpointercapture', function(event) {
|
|
|
const pointer = event.originalEvent;
|
|
|
if (!entryDrag || pointer.pointerId !== entryDrag.pointerId) return;
|
|
|
- suppressEntryClick = event.type === 'pointerup' && entryDrag.moved;
|
|
|
- if (this.hasPointerCapture(pointer.pointerId)) this.releasePointerCapture(pointer.pointerId);
|
|
|
+ const drag = entryDrag;
|
|
|
entryDrag = null;
|
|
|
+ suppressEntryClick = event.type === 'pointerup' && drag.moved;
|
|
|
+ if (event.type !== 'pointerup') entryPosition = drag.originalPosition;
|
|
|
+ else if (drag.moved) entryPosition.docked = entryPosition.right <= 24;
|
|
|
+ if (drag.moved || event.type !== 'pointerup') entryExpanded = false;
|
|
|
+ entryPosition = clampEntryPosition(entryPosition);
|
|
|
entry.removeClass('dragging');
|
|
|
+ applyEntryPosition();
|
|
|
+ if (this.hasPointerCapture(pointer.pointerId)) this.releasePointerCapture(pointer.pointerId);
|
|
|
saveEntryPosition();
|
|
|
+ suppressClickTimer = setTimeout(function() { suppressEntryClick = false; }, 0);
|
|
|
+ });
|
|
|
+
|
|
|
+ entry.on('pointerenter focusin', function(event) {
|
|
|
+ if (!entryPosition.docked || entryDrag || (event.originalEvent && event.originalEvent.pointerType === 'touch')) return;
|
|
|
+ entryExpanded = true;
|
|
|
+ applyEntryPosition();
|
|
|
+ });
|
|
|
+
|
|
|
+ entry.on('pointerleave focusout', function() {
|
|
|
+ if (entryDrag) return;
|
|
|
+ entryExpanded = false;
|
|
|
+ applyEntryPosition();
|
|
|
});
|
|
|
|
|
|
$(window).on('resize.globalAssistant', function() {
|
|
|
@@ -84,7 +115,9 @@
|
|
|
|
|
|
root.find('.global-assistant-close').on('click', function() {
|
|
|
panel.removeClass('show').attr('aria-hidden', 'true');
|
|
|
- root.find('.global-assistant-entry').show();
|
|
|
+ entryExpanded = false;
|
|
|
+ entry.attr('aria-expanded', 'false').show();
|
|
|
+ applyEntryPosition();
|
|
|
});
|
|
|
|
|
|
root.find('.global-assistant-clear').on('click', function() {
|
|
|
@@ -282,11 +315,13 @@
|
|
|
}
|
|
|
|
|
|
function loadEntryPosition() {
|
|
|
+ const initial = { right: 0, top: window.innerHeight - 44 - 24, docked: true };
|
|
|
try {
|
|
|
const value = JSON.parse(localStorage.getItem(entryPositionKey) || '{}');
|
|
|
- return clampEntryPosition({ x: Number(value.x) || 0, y: Number(value.y) || 0 });
|
|
|
+ if (!value || typeof value.docked !== 'boolean') return clampEntryPosition(initial);
|
|
|
+ return clampEntryPosition(value);
|
|
|
} catch (err) {
|
|
|
- return { x: 0, y: 0 };
|
|
|
+ return clampEntryPosition(initial);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -299,17 +334,19 @@
|
|
|
}
|
|
|
|
|
|
function applyEntryPosition() {
|
|
|
- entry.css('transform', `translate(${-entryPosition.x}px, ${-entryPosition.y}px)`);
|
|
|
+ entry.toggleClass('docked', entryPosition.docked)
|
|
|
+ .toggleClass('expanded', entryPosition.docked && entryExpanded)
|
|
|
+ .css({ right: entryPosition.right, top: entryPosition.top, transform: 'none' });
|
|
|
}
|
|
|
|
|
|
function clampEntryPosition(position) {
|
|
|
- const panelWidth = parseFloat(panel.css('width')) || 420;
|
|
|
- const panelHeight = Math.min(650, Math.max(0, window.innerHeight - 48));
|
|
|
- const maxX = Math.max(0, panelWidth - entry.outerWidth());
|
|
|
- const maxY = Math.max(0, panelHeight - entry.outerHeight());
|
|
|
+ const docked = position.docked === true;
|
|
|
+ const maxRight = Math.max(0, window.innerWidth - 92 - 8);
|
|
|
+ const maxTop = Math.max(0, window.innerHeight - 44 - 8);
|
|
|
return {
|
|
|
- x: Math.min(maxX, Math.max(0, Number(position.x) || 0)),
|
|
|
- y: Math.min(maxY, Math.max(0, Number(position.y) || 0)),
|
|
|
+ right: docked ? 0 : Math.min(maxRight, Math.max(Math.min(8, maxRight), Number(position.right) || 0)),
|
|
|
+ top: Math.min(maxTop, Math.max(Math.min(8, maxTop), Number(position.top) || 0)),
|
|
|
+ docked,
|
|
|
};
|
|
|
}
|
|
|
});
|