Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snippet placeholder redesign #496

Merged
merged 9 commits into from
Nov 8, 2024
5 changes: 5 additions & 0 deletions .changeset/four-eagles-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lblod/ember-rdfa-editor-lblod-plugins': minor
---

Redesign snippet placeholder to have more intuitive UX
63 changes: 41 additions & 22 deletions addon/components/snippet-plugin/nodes/placeholder.gts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { on } from '@ember/modifier';
import IntlService from 'ember-intl/services/intl';
import t from 'ember-intl/helpers/t';
import AuAlert from '@appuniversum/ember-appuniversum/components/au-alert';
import { PlusTextIcon } from '@appuniversum/ember-appuniversum/components/icons/plus-text';
import AuIcon from '@appuniversum/ember-appuniversum/components/au-icon';
import AuButton from '@appuniversum/ember-appuniversum/components/au-button';
import { type EmberNodeArgs } from '@lblod/ember-rdfa-editor/utils/_private/ember-node';
import { service } from '@ember/service';
import IntlService from 'ember-intl/services/intl';
import { type SnippetPluginConfig } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/snippet-plugin';

interface Signature {
Args: Pick<EmberNodeArgs, 'node' | 'selectNode'>;
Args: Pick<EmberNodeArgs, 'node' | 'selectNode'> & {
insertSnippet: () => void;
};
}

export default class SnippetPluginPlaceholder extends Component<Signature> {
@service declare intl: IntlService;

get node() {
return this.args.node;
}
get listNames() {
return this.args.node.attrs.snippetListNames;
}
get config(): SnippetPluginConfig {
return this.node.attrs.config;
}
get isSingleList() {
return this.listNames.length === 1;
}
Expand All @@ -28,24 +39,32 @@ export default class SnippetPluginPlaceholder extends Component<Signature> {
return this.intl.t('snippet-plugin.placeholder.title-multiple');
}
}

<template>
<AuAlert
@title={{this.alertTitle}}
@skin='warning'
@icon={{PlusTextIcon}}
@size='small'
class='say-snippet-placeholder'
{{on 'click' @selectNode}}
>
{{#unless this.isSingleList}}
{{t 'snippet-plugin.placeholder.active-lists'}}
<ul>
{{#each this.listNames as |listName|}}
<li>{{listName}}</li>
{{/each}}
</ul>
{{/unless}}
{{t 'snippet-plugin.placeholder.text'}}
</AuAlert>
{{! template-lint-disable no-invalid-interactive }}
<div class='say-snippet-placeholder' {{on 'click' @selectNode}}>
<div class='say-snippet-placeholder__icon'>
<AuIcon @icon={{PlusTextIcon}} />
</div>
<div class='say-snippet-placeholder__content'>
<p class='say-snippet-placeholder__title'>{{this.alertTitle}}</p>
{{#unless this.isSingleList}}
<ul class='say-snippet-placeholder__list'>
{{#each this.listNames as |listName|}}
<li>{{listName}}</li>
{{/each}}
</ul>
{{/unless}}
{{#unless this.config.hidePlaceholderInsertButton}}
<AuButton
@skin='link'
class='say-snippet-placeholder__button'
{{on 'click' @insertSnippet}}
>
{{t 'snippet-plugin.placeholder.button'}}
</AuButton>
{{/unless}}
</div>
</div>
</template>
}
82 changes: 51 additions & 31 deletions addon/components/snippet-plugin/nodes/snippet.gts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { BinIcon } from '@appuniversum/ember-appuniversum/components/icons/bin';
import { AddIcon } from '@appuniversum/ember-appuniversum/components/icons/add';
import { type EmberNodeArgs } from '@lblod/ember-rdfa-editor/utils/_private/ember-node';
import {
NodeSelection,
type PNode,
ProseParser,
type Selection,
Expand All @@ -30,6 +31,8 @@ import { transactionCombinator } from '@lblod/ember-rdfa-editor/utils/transactio
import { recalculateNumbers } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/structure-plugin/recalculate-structure-numbers';
import { createSnippetPlaceholder } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/snippet-plugin/nodes/snippet-placeholder';
import { hasDecendant } from '@lblod/ember-rdfa-editor-lblod-plugins/utils/has-descendant';
import SnippetPlaceholder from '@lblod/ember-rdfa-editor-lblod-plugins/components/snippet-plugin/nodes/placeholder';
import { getSnippetListIdsFromNode } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/snippet-plugin/utils/rdfa-predicate';

interface ButtonSig {
Args: {
Expand Down Expand Up @@ -76,6 +79,13 @@ export default class SnippetNode extends Component<Signature> {
get node() {
return this.args.node;
}
get isPlaceholder() {
return this.node.content.size === 0;
}
get allowMultipleSnippets() {
return this.node.attrs.allowMultipleSnippets as boolean;
}

@action
closeModal() {
this.showModal = false;
Expand All @@ -85,10 +95,6 @@ export default class SnippetNode extends Component<Signature> {
this.showModal = true;
}

get allowMultipleSnippets() {
return this.node.attrs.allowMultipleSnippets as boolean;
}

@action
addFragment() {
if (this.allowMultipleSnippets) {
Expand Down Expand Up @@ -123,7 +129,7 @@ export default class SnippetNode extends Component<Signature> {
const node = createSnippetPlaceholder({
listProperties: {
placeholderId: this.node.attrs.placeholderId,
listIds: this.node.attrs.snippetListIds,
listIds: getSnippetListIdsFromNode(this.node),
names: this.node.attrs.snippetListNames,
importedResources: this.node.attrs.importedResources,
},
Expand Down Expand Up @@ -155,6 +161,12 @@ export default class SnippetNode extends Component<Signature> {
return this.controller.mainEditorState.selection;
}
get isActive(): boolean {
if (
this.selection instanceof NodeSelection &&
this.selection.node === this.node
) {
return true;
}
const ancestor = findAncestors(this.selection.$from, (node: PNode) => {
return hasOutgoingNamedNodeTriple(
node.attrs,
Expand Down Expand Up @@ -188,7 +200,7 @@ export default class SnippetNode extends Component<Signature> {
title,
listProperties: {
placeholderId: this.node.attrs.placeholderId,
listIds: this.node.attrs.snippetListIds,
listIds: getSnippetListIdsFromNode(this.node),
names: this.node.attrs.snippetListNames,
importedResources: this.node.attrs.importedResources,
},
Expand All @@ -199,40 +211,48 @@ export default class SnippetNode extends Component<Signature> {
}

<template>
<div class='say-snippet-card'>
<div class='say-snippet-title'>{{this.node.attrs.title}}</div>
<div class='say-snippet-content'>{{yield}}</div>
<div class='say-snippet-icons' contenteditable='false'>
<SnippetButton
@icon={{SynchronizeIcon}}
@helpText='snippet-plugin.snippet-node.change-fragment'
{{on 'click' this.editFragment}}
@isActive={{this.isActive}}
/>
<SnippetButton
@icon={{BinIcon}}
@helpText='snippet-plugin.snippet-node.remove-fragment'
{{on 'click' this.deleteFragment}}
@isActive={{this.isActive}}
class='say-snippet-remove-button'
/>
{{#if this.allowMultipleSnippets}}
{{#if this.isPlaceholder}}
<SnippetPlaceholder
@node={{@node}}
@selectNode={{@selectNode}}
@insertSnippet={{this.editFragment}}
/>
{{else}}
<div class='say-snippet-card'>
<div class='say-snippet-title'>{{this.node.attrs.title}}</div>
<div class='say-snippet-content'>{{yield}}</div>
<div class='say-snippet-icons' contenteditable='false'>
<SnippetButton
@icon={{AddIcon}}
@helpText='snippet-plugin.snippet-node.add-fragment'
{{on 'click' this.addFragment}}
@icon={{SynchronizeIcon}}
@helpText='snippet-plugin.snippet-node.change-fragment'
{{on 'click' this.editFragment}}
@isActive={{this.isActive}}
/>
{{/if}}
</div>
<SnippetButton
@icon={{BinIcon}}
@helpText='snippet-plugin.snippet-node.remove-fragment'
{{on 'click' this.deleteFragment}}
@isActive={{this.isActive}}
class='say-snippet-remove-button'
/>
{{#if this.allowMultipleSnippets}}
<SnippetButton
@icon={{AddIcon}}
@helpText='snippet-plugin.snippet-node.add-fragment'
{{on 'click' this.addFragment}}
@isActive={{this.isActive}}
/>
{{/if}}
</div>

</div>
</div>
{{/if}}
<SearchModal
@open={{this.showModal}}
@closeModal={{this.closeModal}}
@config={{this.node.attrs.config}}
@onInsert={{this.onInsert}}
@snippetListIds={{this.node.attrs.snippetListIds}}
@snippetListIds={{getSnippetListIdsFromNode this.node}}
/>
</template>
}
19 changes: 7 additions & 12 deletions addon/components/snippet-plugin/snippet-insert-rdfa.gts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import {
type SnippetPluginConfig,
} from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/snippet-plugin';
import { findParentNodeClosestToPos } from '@curvenote/prosemirror-utils';
import {
getAssignedSnippetListsIdsFromProperties,
getSnippetListIdsProperties,
} from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/snippet-plugin/utils/rdfa-predicate';
import { getSnippetListIdsFromNode } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/snippet-plugin/utils/rdfa-predicate';
import { ResolvedPNode } from '@lblod/ember-rdfa-editor/utils/_private/types';
import SnippetInsert from './snippet-insert';

Expand All @@ -25,13 +22,11 @@ interface Sig {
export default class SnippetInsertRdfaComponent extends Component<Sig> {
get listProperties(): SnippetListProperties | undefined {
const activeNode = this.args.node.value;
const activeNodeSnippetListIds = getSnippetListIdsProperties(activeNode);
const listIds = getSnippetListIdsFromNode(activeNode);

if (activeNodeSnippetListIds.length > 0) {
if (listIds.length > 0) {
return {
listIds: getAssignedSnippetListsIdsFromProperties(
activeNodeSnippetListIds,
),
listIds,
placeholderId: activeNode.attrs.placeholderId,
names: activeNode.attrs.snippetListNames,
importedResources: activeNode.attrs.importedResources,
Expand All @@ -49,11 +44,11 @@ export default class SnippetInsertRdfaComponent extends Component<Sig> {
isResourceNode(node),
);
while (parentNode) {
const properties = getSnippetListIdsProperties(parentNode.node);
const listIds = getSnippetListIdsFromNode(parentNode.node);

if (properties.length > 0) {
if (listIds.length > 0) {
return {
listIds: getAssignedSnippetListsIdsFromProperties(properties),
listIds,
placeholderId: parentNode.node.attrs.placeholderId,
names: parentNode.node.attrs.snippetListNames,
importedResources: parentNode.node.attrs.importedResources,
Expand Down
1 change: 1 addition & 0 deletions addon/plugins/snippet-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const DEFAULT_CONTENT_STRING = 'block+';
export type SnippetPluginConfig = {
endpoint: string;
allowedContent?: string;
hidePlaceholderInsertButton?: boolean;
};

interface SnippetArgs {
Expand Down
19 changes: 12 additions & 7 deletions addon/plugins/snippet-plugin/nodes/snippet-placeholder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
createEmberNodeView,
type EmberNodeConfig,
} from '@lblod/ember-rdfa-editor/utils/ember-node';
import SnippetPlaceholderComponent from '@lblod/ember-rdfa-editor-lblod-plugins/components/snippet-plugin/nodes/placeholder';
import SnippetComponent from '@lblod/ember-rdfa-editor-lblod-plugins/components/snippet-plugin/nodes/snippet';
import {
EXT,
RDF,
Expand All @@ -23,6 +23,7 @@ import {
type SnippetListProperties,
type ImportedResourceMap,
type SnippetList,
type SnippetPluginConfig,
} from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/snippet-plugin';
import { tripleForSnippetListId } from '@lblod/ember-rdfa-editor-lblod-plugins/plugins/snippet-plugin/utils/rdfa-predicate';
import { OutgoingTriple } from '@lblod/ember-rdfa-editor/core/rdfa-processor';
Expand Down Expand Up @@ -93,7 +94,7 @@ export function createSnippetPlaceholder({
});
}

const emberNodeConfig: EmberNodeConfig = {
const emberNodeConfig = (config: SnippetPluginConfig): EmberNodeConfig => ({
name: 'snippet_placeholder',
inline: false,
group: 'block',
Expand All @@ -107,16 +108,18 @@ const emberNodeConfig: EmberNodeConfig = {
snippetListNames: { default: [] },
importedResources: { default: {} },
allowMultipleSnippets: { default: false },
config: {
default: config,
},
},
component: SnippetPlaceholderComponent,
component: SnippetComponent,
serialize(node, editorState) {
const t = getTranslationFunction(editorState);
const listNames = node.attrs.snippetListNames as string[];
return renderRdfaAware({
renderable: node,
tag: 'div',
attrs: {
...node.attrs,
class: 'say-snippet-placeholder-node',
'data-list-names': listNames && JSON.stringify(listNames),
'data-imported-resources': JSON.stringify(node.attrs.importedResources),
Expand Down Expand Up @@ -169,7 +172,9 @@ const emberNodeConfig: EmberNodeConfig = {
},
},
],
};
});

export const snippetPlaceholder = createEmberNodeSpec(emberNodeConfig);
export const snippetPlaceholderView = createEmberNodeView(emberNodeConfig);
export const snippetPlaceholder = (config: SnippetPluginConfig) =>
createEmberNodeSpec(emberNodeConfig(config));
export const snippetPlaceholderView = (config: SnippetPluginConfig) =>
createEmberNodeView(emberNodeConfig(config));
Loading