mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 14:43:08 +03:00
Added support for selecting posts
refs https://github.com/TryGhost/Team/issues/2906 Adds a way to select posts using CMD, shift and CMD+A. And adds a placeholder context menu. Behind the making it rain feature flag.
This commit is contained in:
parent
aa5272ffb9
commit
8c046740f0
@ -562,3 +562,5 @@ add|ember-template-lint|require-input-label|10|12|10|12|8c3c0ea315ff4da828363989
|
||||
add|ember-template-lint|no-action|465|46|465|46|f2f0f3f512f141fdd821333c873f5052813bb491|1677974400000|1688342400000|1693526400000|app/components/gh-portal-links.hbs
|
||||
add|ember-template-lint|no-action|271|58|271|58|5124558b018d5e90a3d203fd54c5e4ca8e9b0548|1680566400000|1690934400000|1696118400000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-action|289|68|289|68|eaa96ff81a7c4b4743ca191655c017bd90549e96|1680566400000|1690934400000|1696118400000|app/components/modal-portal-settings.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|1|103|1|103|f5a46b2538fbf79a40f2683ff1151ca60e0fa0ca|1680652800000|1691020800000|1696204800000|app/components/gh-context-menu.hbs
|
||||
add|ember-template-lint|no-invalid-interactive|5|53|5|53|9647ef6afba919b2af04fe551b0fdf0fb63be849|1680652800000|1691020800000|1696204800000|app/components/gh-context-menu.hbs
|
||||
|
6
ghost/admin/app/components/gh-context-menu.hbs
Normal file
6
ghost/admin/app/components/gh-context-menu.hbs
Normal file
@ -0,0 +1,6 @@
|
||||
<div role="menu" class="gh-context-menu-container" {{did-insert this.setup}} data-open={{this.isOpen}} {{on "click" this.stopClicks}}>
|
||||
<div class="gh-context-menu" style={{this.style}}>
|
||||
{{yield this this.selectionList}}
|
||||
</div>
|
||||
<div role="none" class="gh-context-menu-overlay" {{on "click" this.close}} {{on "contextmenu" this.onContextMenuOutside}}></div>
|
||||
</div>
|
77
ghost/admin/app/components/gh-context-menu.js
Normal file
77
ghost/admin/app/components/gh-context-menu.js
Normal file
@ -0,0 +1,77 @@
|
||||
import Component from '@glimmer/component';
|
||||
import SelectionList from '../utils/selection-list';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class GhContextMenu extends Component {
|
||||
@service dropdown;
|
||||
|
||||
@tracked isOpen = false;
|
||||
@tracked left = 0;
|
||||
@tracked top = 0;
|
||||
@tracked selectionList = new SelectionList();
|
||||
|
||||
get name() {
|
||||
return this.args.name;
|
||||
}
|
||||
|
||||
get style() {
|
||||
return `left: ${this.left}px; top: ${this.top}px;`;
|
||||
}
|
||||
|
||||
@action
|
||||
setup() {
|
||||
const dropdownService = this.dropdown;
|
||||
dropdownService.on('close', this, this.close);
|
||||
dropdownService.on('toggle', this, this.toggle);
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
const dropdownService = this.dropdown;
|
||||
dropdownService.off('close', this, this.close);
|
||||
dropdownService.off('toggle', this, this.toggle);
|
||||
}
|
||||
|
||||
@action
|
||||
open() {
|
||||
this.isOpen = true;
|
||||
}
|
||||
|
||||
@action
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
}
|
||||
|
||||
@action
|
||||
onContextMenuOutside(event) {
|
||||
this.close();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
// Called by the dropdown service when the context menu should open
|
||||
@action
|
||||
toggle(options) {
|
||||
const targetDropdownName = options.target;
|
||||
if (this.name === targetDropdownName) {
|
||||
if (options.left !== undefined) {
|
||||
this.left = options.left;
|
||||
this.top = options.top;
|
||||
}
|
||||
if (options.selectionList) {
|
||||
this.selectionList = options.selectionList;
|
||||
}
|
||||
|
||||
this.open();
|
||||
} else if (this.isOpen) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
stopClicks(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
3
ghost/admin/app/components/multi-list/item.hbs
Normal file
3
ghost/admin/app/components/multi-list/item.hbs
Normal file
@ -0,0 +1,3 @@
|
||||
<div role="menuitem" {{on "click" this.onClick capture=true}} data-selected={{this.isSelected}} {{on "contextmenu" this.onContextMenu}} ...attributes>
|
||||
{{yield}}
|
||||
</div>
|
71
ghost/admin/app/components/multi-list/item.js
Normal file
71
ghost/admin/app/components/multi-list/item.js
Normal file
@ -0,0 +1,71 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
|
||||
function clearTextSelection() {
|
||||
if (window.getSelection) {
|
||||
if (window.getSelection().empty) { // Chrome
|
||||
window.getSelection().empty();
|
||||
} else if (window.getSelection().removeAllRanges) { // Firefox
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
} else if (document.selection) { // IE?
|
||||
document.selection.empty();
|
||||
}
|
||||
}
|
||||
|
||||
export default class ItemComponent extends Component {
|
||||
@service dropdown;
|
||||
|
||||
get selectionList() {
|
||||
return this.args.model;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.args.id;
|
||||
}
|
||||
|
||||
get isSelected() {
|
||||
return this.selectionList.isSelected(this.id);
|
||||
}
|
||||
|
||||
@action
|
||||
onClick(event) {
|
||||
const shiftKey = event.shiftKey;
|
||||
const ctrlKey = event.ctrlKey || event.metaKey;
|
||||
|
||||
if (ctrlKey) {
|
||||
this.selectionList.toggleItem(this.id);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
clearTextSelection();
|
||||
} else if (shiftKey) {
|
||||
try {
|
||||
this.selectionList.shiftItem(this.id);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
clearTextSelection();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onContextMenu(event) {
|
||||
let x = event.clientX;
|
||||
let y = event.clientY;
|
||||
|
||||
if (this.isSelected) {
|
||||
this.dropdown.toggleDropdown('context-menu', this, {left: x, top: y, selectionList: this.selectionList});
|
||||
} else {
|
||||
const selectionList = this.selectionList.cloneEmpty();
|
||||
selectionList.toggleItem(this.id);
|
||||
this.dropdown.toggleDropdown('context-menu', this, {left: x, top: y, selectionList});
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
10
ghost/admin/app/components/multi-list/list.hbs
Normal file
10
ghost/admin/app/components/multi-list/list.hbs
Normal file
@ -0,0 +1,10 @@
|
||||
<div data-ctrl={{this.actionKeyPressed}} {{did-insert this.setup}} ...attributes>
|
||||
{{yield
|
||||
(hash
|
||||
item=(
|
||||
component "multi-list/item"
|
||||
model=@model
|
||||
)
|
||||
)
|
||||
}}
|
||||
</div>
|
84
ghost/admin/app/components/multi-list/list.js
Normal file
84
ghost/admin/app/components/multi-list/list.js
Normal file
@ -0,0 +1,84 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class ListComponent extends Component {
|
||||
@tracked ctrlPressed = false;
|
||||
@tracked metaPressed = false;
|
||||
@tracked shiftPressed = false;
|
||||
|
||||
get selectionList() {
|
||||
return this.args.model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Required for shift behaviour
|
||||
*/
|
||||
get allIds() {
|
||||
return this.args.all.map(a => a.id);
|
||||
}
|
||||
|
||||
get actionKeyPressed() {
|
||||
return this.ctrlPressed || this.metaPressed || this.shiftPressed;
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
window.removeEventListener('keydown', this.onKeyDow, {passive: true});
|
||||
window.removeEventListener('keyup', this.onKeyUp, {passive: true});
|
||||
window.removeEventListener('click', this.onWindowClicked, {passive: true});
|
||||
}
|
||||
|
||||
@action
|
||||
setup() {
|
||||
window.addEventListener('keydown', this.onKeyDown, {passive: false});
|
||||
window.addEventListener('keyup', this.onKeyUp, {passive: true});
|
||||
window.addEventListener('click', this.onWindowClicked, {passive: true});
|
||||
}
|
||||
|
||||
@action
|
||||
onWindowClicked(event) {
|
||||
// Clear selection if no ctrl/meta key is pressed
|
||||
if (!event.metaKey && !event.ctrlKey) {
|
||||
this.selectionList.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onKeyDown(event) {
|
||||
if (event.key === 'Control') {
|
||||
this.ctrlPressed = true;
|
||||
}
|
||||
if (event.key === 'Meta') {
|
||||
this.metaPressed = true;
|
||||
}
|
||||
if (event.key === 'Shift') {
|
||||
this.shiftPressed = true;
|
||||
}
|
||||
|
||||
if ((event.ctrlKey || event.metaKey) && !event.shiftKey) {
|
||||
if (event.key === 'a') {
|
||||
this.selectionList.selectAll();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
this.selectionList.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onKeyUp(event) {
|
||||
if (event.key === 'Control') {
|
||||
this.ctrlPressed = false;
|
||||
}
|
||||
if (event.key === 'Meta') {
|
||||
this.metaPressed = false;
|
||||
}
|
||||
if (event.key === 'Shift') {
|
||||
this.shiftPressed = false;
|
||||
}
|
||||
}
|
||||
}
|
51
ghost/admin/app/components/posts-list/list.hbs
Normal file
51
ghost/admin/app/components/posts-list/list.hbs
Normal file
@ -0,0 +1,51 @@
|
||||
<MultiList::List @model={{@list}} class="posts-list gh-list {{unless @model "no-posts"}} feature-memberAttribution" as |list| >
|
||||
{{#each @model as |post|}}
|
||||
<list.item @id={{post.id}} class="gh-posts-list-item-group">
|
||||
<PostsList::ListItem
|
||||
@post={{post}}
|
||||
data-test-post-id={{post.id}}
|
||||
/>
|
||||
</list.item>
|
||||
{{/each}}
|
||||
</MultiList::List>
|
||||
|
||||
{{!-- The currently selected item or items are passed to the context menu --}}
|
||||
<GhContextMenu
|
||||
@name="context-menu"
|
||||
as |menu selectionList|
|
||||
>
|
||||
<ul class="gh-posts-context-menu dropdown-menu dropdown-triangle-top-left">
|
||||
{{#if selectionList.isSingle}}
|
||||
<li>
|
||||
<button class="mr2" type="button" disabled {{on "click" menu.close}}>
|
||||
<span>Duplicate</span>
|
||||
</button>
|
||||
</li>
|
||||
{{/if}}
|
||||
<li>
|
||||
<button class="mr2" type="button" disabled {{on "click" menu.close}}>
|
||||
<span>Unpublish</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="mr2" type="button" disabled {{on "click" menu.close}}>
|
||||
<span>Feature</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="mr2" type="button" disabled {{on "click" menu.close}}>
|
||||
<span>Add tag...</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="mr2" type="button" disabled {{on "click" menu.close}}>
|
||||
<span>Post access...</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="mr2" type="button" {{on "click" (fn this.deletePosts menu)}}>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</GhContextMenu>
|
12
ghost/admin/app/components/posts-list/list.js
Normal file
12
ghost/admin/app/components/posts-list/list.js
Normal file
@ -0,0 +1,12 @@
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
export default class PostsList extends Component {
|
||||
get list() {
|
||||
return this.args.list;
|
||||
}
|
||||
|
||||
deletePosts(menu) {
|
||||
alert('Deleting posts not yet supported.');
|
||||
menu.close();
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import Controller from '@ember/controller';
|
||||
import SelectionList from 'ghost-admin/utils/selection-list';
|
||||
import {DEFAULT_QUERY_PARAMS} from 'ghost-admin/helpers/reset-query-params';
|
||||
import {action} from '@ember/object';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
@ -63,6 +64,7 @@ export default class PostsController extends Controller {
|
||||
@tracked author = null;
|
||||
@tracked tag = null;
|
||||
@tracked order = null;
|
||||
@tracked selectionList = new SelectionList(this.postsInfinityModel);
|
||||
|
||||
availableTypes = TYPES;
|
||||
availableVisibilities = VISIBILITIES;
|
||||
|
@ -22,7 +22,7 @@ export default class DropdownService extends Service.extend(Evented, BodyEventLi
|
||||
}
|
||||
|
||||
@action
|
||||
toggleDropdown(dropdownName, dropdownButton) {
|
||||
this.trigger('toggle', {target: dropdownName, button: dropdownButton});
|
||||
toggleDropdown(dropdownName, dropdownButton, options = {}) {
|
||||
this.trigger('toggle', {target: dropdownName, button: dropdownButton, ...options});
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,10 @@
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.dropdown-menu li > button:disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.dropdown-menu svg {
|
||||
margin-right: 10px;
|
||||
height: 14px;
|
||||
@ -314,3 +318,32 @@
|
||||
line-height: 1em;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
/**
|
||||
Post context menu
|
||||
*/
|
||||
|
||||
.gh-context-menu-container {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Disable user interaction with a div that covers the full view. We don't use a javascript based approach here, because you want to close a context menu even when you click a link on the page. With js, we cannot ignore or block that link click without complex event listeners. */
|
||||
.gh-context-menu-overlay {
|
||||
position: fixed;
|
||||
content: '';
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.gh-context-menu-container[data-open] {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.gh-context-menu {
|
||||
position: fixed;
|
||||
max-width: 200px;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
@ -153,6 +153,55 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* START Temporary styles to move post list to use flex instead of tables */
|
||||
.gh-posts-list-item-group {
|
||||
padding: 0 var(--main-layout-content-sidepadding);
|
||||
margin: 0 calc(var(--main-layout-content-sidepadding) * -1 + 10px);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.gh-posts-list-item-group .gh-list-row {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gh-posts-list-item-group .gh-list-row .gh-list-data {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.gh-posts-list-item-group .gh-list-row .gh-list-data:first-child {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.posts-list[data-ctrl] {
|
||||
cursor: default !important; /* Hide pointer */
|
||||
}
|
||||
|
||||
.posts-list[data-ctrl] .gh-posts-list-item-group * {
|
||||
cursor: default !important; /* Hide pointer */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.posts-list .gh-posts-list-item-group:hover {
|
||||
background: #fafafb;
|
||||
}
|
||||
|
||||
.posts-list[data-ctrl] .gh-posts-list-item-group:hover {
|
||||
background: #f3f3f9;
|
||||
}
|
||||
|
||||
.gh-posts-list-item-group[data-selected] {
|
||||
background: #eae5ff !important;
|
||||
}
|
||||
|
||||
.gh-posts-list-item-group .gh-posts-list-item:hover .gh-list-data {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* END Temporary styles to move post list to use flex instead of tables */
|
||||
|
||||
.gh-posts-list-item {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -29,32 +29,39 @@
|
||||
</GhCanvasHeader>
|
||||
|
||||
<section class="view-container content-list">
|
||||
<ol class="posts-list gh-list {{unless this.postsInfinityModel "no-posts"}} feature-memberAttribution">
|
||||
{{#if (feature "makingItRain")}}
|
||||
<PostsList::List
|
||||
@model={{this.postsInfinityModel}}
|
||||
@list={{this.selectionList}}
|
||||
/>
|
||||
{{else}}
|
||||
<ol class="posts-list gh-list {{unless this.postsInfinityModel "no-posts"}} feature-memberAttribution">
|
||||
|
||||
{{#each this.postsInfinityModel as |post|}}
|
||||
<PostsList::ListItem
|
||||
@post={{post}}
|
||||
data-test-post-id={{post.id}}
|
||||
/>
|
||||
{{else}}
|
||||
<li class="no-posts-box" data-test-no-posts-box>
|
||||
<div class="no-posts">
|
||||
{{#if this.showingAll}}
|
||||
{{svg-jar "posts-placeholder" class="gh-posts-placeholder"}}
|
||||
<h4>Start creating content.</h4>
|
||||
<LinkTo @route="editor.new" @model="post" class="gh-btn gh-btn-green" data-test-link="write-a-new-post">
|
||||
<span>Write a new post</span>
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<h4>No posts match the current filter</h4>
|
||||
<LinkTo @route="posts" @query={{hash type=null author=null tag=null}} class="gh-btn" data-test-link="show-all">
|
||||
<span>Show all posts</span>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
{{#each this.postsInfinityModel as |post|}}
|
||||
<PostsList::ListItem
|
||||
@post={{post}}
|
||||
data-test-post-id={{post.id}}
|
||||
/>
|
||||
{{else}}
|
||||
<li class="no-posts-box" data-test-no-posts-box>
|
||||
<div class="no-posts">
|
||||
{{#if this.showingAll}}
|
||||
{{svg-jar "posts-placeholder" class="gh-posts-placeholder"}}
|
||||
<h4>Start creating content.</h4>
|
||||
<LinkTo @route="editor.new" @model="post" class="gh-btn gh-btn-green" data-test-link="write-a-new-post">
|
||||
<span>Write a new post</span>
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<h4>No posts match the current filter</h4>
|
||||
<LinkTo @route="posts" @query={{hash type=null author=null tag=null}} class="gh-btn" data-test-link="show-all">
|
||||
<span>Show all posts</span>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
{{/if}}
|
||||
|
||||
<GhInfinityLoader
|
||||
@infinityModel={{this.postsInfinityModel}}
|
||||
|
132
ghost/admin/app/utils/selection-list.js
Normal file
132
ghost/admin/app/utils/selection-list.js
Normal file
@ -0,0 +1,132 @@
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class SelectionList {
|
||||
@tracked selectedIds = new Set();
|
||||
@tracked inverted = false;
|
||||
@tracked lastSelectedId = null;
|
||||
@tracked lastShiftSelectionGroup = new Set();
|
||||
|
||||
infinityModel;
|
||||
|
||||
constructor(infinityModel) {
|
||||
this.infinityModel = infinityModel ?? {content: []};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty copy
|
||||
*/
|
||||
cloneEmpty() {
|
||||
return new SelectionList(this.infinityModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of models that are already loaded in memory.
|
||||
* Keep in mind that when using CMD + A, we don't have all items in memory!
|
||||
*/
|
||||
get availableModels() {
|
||||
const arr = [];
|
||||
for (const item of this.infinityModel.content) {
|
||||
if (this.isSelected(item.id)) {
|
||||
arr.push(item);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
get isSingle() {
|
||||
return this.selectedIds.size === 1 && !this.inverted;
|
||||
}
|
||||
|
||||
isSelected(id) {
|
||||
if (this.inverted) {
|
||||
return !this.selectedIds.has(id);
|
||||
}
|
||||
return this.selectedIds.has(id);
|
||||
}
|
||||
|
||||
toggleItem(id) {
|
||||
this.lastShiftSelectionGroup = new Set();
|
||||
this.lastSelectedId = id;
|
||||
|
||||
if (this.selectedIds.has(id)) {
|
||||
this.selectedIds.delete(id);
|
||||
} else {
|
||||
this.selectedIds.add(id);
|
||||
}
|
||||
|
||||
// Force update
|
||||
// eslint-disable-next-line no-self-assign
|
||||
this.selectedIds = this.selectedIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all items between the last selection or the first one if none
|
||||
*/
|
||||
shiftItem(id) {
|
||||
// Unselect last selected items
|
||||
for (const item of this.lastShiftSelectionGroup) {
|
||||
if (this.inverted) {
|
||||
this.selectedIds.add(item);
|
||||
} else {
|
||||
this.selectedIds.delete(item);
|
||||
}
|
||||
}
|
||||
this.lastShiftSelectionGroup = new Set();
|
||||
|
||||
// todo
|
||||
let running = false;
|
||||
|
||||
if (this.lastSelectedId === null) {
|
||||
running = true;
|
||||
}
|
||||
|
||||
for (const item of this.infinityModel.content) {
|
||||
// Exlusing the last selected item
|
||||
if (item.id === this.lastSelectedId || item.id === id) {
|
||||
if (!running) {
|
||||
running = true;
|
||||
|
||||
// Skip last selected on its own
|
||||
if (item.id === this.lastSelectedId) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Still include id
|
||||
if (item.id === id) {
|
||||
this.lastShiftSelectionGroup.add(item.id);
|
||||
|
||||
if (this.inverted) {
|
||||
this.selectedIds.delete(item.id);
|
||||
} else {
|
||||
this.selectedIds.add(item.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (running) {
|
||||
this.lastShiftSelectionGroup.add(item.id);
|
||||
if (this.inverted) {
|
||||
this.selectedIds.delete(item.id);
|
||||
} else {
|
||||
this.selectedIds.add(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force update
|
||||
// eslint-disable-next-line no-self-assign
|
||||
this.selectedIds = this.selectedIds;
|
||||
}
|
||||
|
||||
selectAll() {
|
||||
this.selectedIds = new Set();
|
||||
this.inverted = !this.inverted;
|
||||
}
|
||||
|
||||
clearSelection() {
|
||||
this.selectedIds = new Set();
|
||||
this.inverted = false;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user