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

TEMP #3051

Closed
wants to merge 1 commit into from
Closed

TEMP #3051

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23,480 changes: 23,480 additions & 0 deletions src/components.d.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { LimelProsemirrorAdapterCustomEvent } from '@limetech/lime-elements';
import { Component, h, State } from '@stencil/core';
import {
LimeObjectSelectorPlugin,
LimeObjectSelectorPluginProps,
LimeObjectSelectorPluginTrigger,
} from '../plugins/limeobject-selector/types';
import { ListItem } from 'src/components/list/list-item.types';
/**
* Searcher example
*
* Try typing an exclamation mark
*/
@Component({
tag: 'limel-example-prosemirror-adapter-with-searcher-plugin',
shadow: true,
})
export class ProsemirrorAdapterSearcherPluginExample {
@State()
private text: string = '';

private plugin: LimeObjectSelectorPlugin;
private exclamationItems: Array<ListItem<string>> = [
{ text: 'Google', value: 'https://www.google.com' },
];
private atItems: Array<ListItem<string>> = [
{ text: 'Facebook', value: 'https://www.facebook.com' },
];

public componentWillLoad() {
const props: LimeObjectSelectorPluginProps = {
trigger: '!',
searcher: (trigger: LimeObjectSelectorPluginTrigger) => {
if (trigger === '!') {
return this.getSearcher(this.exclamationItems);
} else if (trigger === '@') {
return this.getSearcher(this.atItems);
}
},
onChange: this.handleSearcherChange,
};

this.plugin = new LimeObjectSelectorPlugin(props);
}

public render() {
return [
<limel-prosemirror-adapter
onChange={this.handleChange}
plugins={[this.plugin]}
/>,
<limel-example-value value={this.text} />,
];
}

private getSearcher(items: Array<ListItem<string>>) {
return (query: string): Promise<Array<ListItem<string>>> => {
return new Promise((resolve) => {
if (query === '') {
return resolve(items);
}

const filteredItems = items.filter((item) => {
return item.text
.toLowerCase()
.includes(query.toLowerCase());
});

return resolve(filteredItems);
});
};
}

private handleChange = (
event: LimelProsemirrorAdapterCustomEvent<string>,
): void => {
event.stopPropagation();

this.text = event.detail;
};

private handleSearcherChange = (value: string): void => {
event.stopPropagation();

this.text = value;
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { PluginKey } from 'prosemirror-state';

export class EditorPlugin<T = any> {
private key: PluginKey = undefined;

constructor(
private props: T,
private name: string,
) {
this.key = new PluginKey(name);
}

public getPluginKey(): PluginKey {
return this.key;
}

public getName(): string {
return this.name;
}

public getProps(): T {
return this.props;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.picker {
position: relative;
max-width: 200px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Component, h, Prop } from '@stencil/core';
import { LimelPickerCustomEvent } from 'src/components';
import { ListItem } from 'src/components/list/list-item.types';
import { Searcher } from 'src/components/picker/searcher.types';
import { PickerValue } from 'src/components/picker/value.types';

/**
* @private
*/
@Component({
tag: 'limel-limeobject-selector-picker',
shadow: true,
styleUrl: 'limeobject-selector-picker.scss',
})
export class LimeobjectSelectorPicker {
/**
* Currently selected value or values. Where the value can be an object.
*/
@Prop()
public value: ListItem<PickerValue> | Array<ListItem<PickerValue>>;

/**
* A search function that takes a search-string as an argument,
* and returns a promise that will eventually be resolved with
* an array of `ListItem`:s.
*
* See the docs for the type `Searcher` for type information on
* the searcher function itself.
*/
@Prop()
public searcher: Searcher;

@Prop()
public offset: {
top: string;
left: string;
}

Check failure on line 37 in src/components/text-editor/prosemirror-adapter/plugins/limeobject-selector/limeobject-selector-picker.tsx

View workflow job for this annotation

GitHub Actions / Lint

Insert `;`

Check failure on line 37 in src/components/text-editor/prosemirror-adapter/plugins/limeobject-selector/limeobject-selector-picker.tsx

View workflow job for this annotation

GitHub Actions / Lint

Missing semicolon

@Prop()
public onChange: (
event: LimelPickerCustomEvent<ListItem<PickerValue>>,
) => void;

public render() {
return (
<div class="picker" style={

Check failure on line 46 in src/components/text-editor/prosemirror-adapter/plugins/limeobject-selector/limeobject-selector-picker.tsx

View workflow job for this annotation

GitHub Actions / Lint

Replace `·class="picker"·style={` with `⏎················class="picker"`
{

Check failure on line 47 in src/components/text-editor/prosemirror-adapter/plugins/limeobject-selector/limeobject-selector-picker.tsx

View workflow job for this annotation

GitHub Actions / Lint

Insert `style={`
top: this.offset.top,
left: this.offset.left,
}

Check failure on line 50 in src/components/text-editor/prosemirror-adapter/plugins/limeobject-selector/limeobject-selector-picker.tsx

View workflow job for this annotation

GitHub Actions / Lint

Insert `}`
}>

Check failure on line 51 in src/components/text-editor/prosemirror-adapter/plugins/limeobject-selector/limeobject-selector-picker.tsx

View workflow job for this annotation

GitHub Actions / Lint

Delete `}`
<limel-picker
searcher={this.searcher}
value={this.value}
onChange={this.onChange}
></limel-picker>
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { EditorView } from 'prosemirror-view';
import {
Plugin,
PluginView,
EditorState,
Transaction,
} from 'prosemirror-state';
import { Searcher } from 'src/components/picker/searcher.types';
import { EditorPlugins } from '../plugins';
import {
LimeObjectSelectorPlugin,
LimeObjectSelectorPluginTrigger,
} from './types';
import { PickerValue } from 'src/components/picker/value.types';
import { ListItem } from 'src/components/list/list-item.types';

export type pluginFactory<T extends EditorPlugins> = (plugin: T) => Plugin;

/**
* The limeobject selector plugin lets you search for limeobjects and insert them into the editor
*
* @param plugin The plugin to use

Check warning on line 22 in src/components/text-editor/prosemirror-adapter/plugins/limeobject-selector/limeobject-selector.ts

View workflow job for this annotation

GitHub Actions / Lint

tsdoc-param-tag-missing-hyphen: The @param block should be followed by a parameter name and then a hyphen
* @returns The Prosemirror plugin
*/
export const limeobjectSelectorPlugin: pluginFactory<
LimeObjectSelectorPlugin
> = (plugin: LimeObjectSelectorPlugin): Plugin => {
const { searcher } = plugin.getProps();
const key = plugin.getPluginKey();

return new Plugin({
view: () => {
return new LimeobjectSelector(searcher);
},
key: key,
});
};

class LimeobjectSelector implements PluginView {
private container: ParentNode;
private picker: HTMLLimelLimeobjectSelectorPickerElement;
private state: 'closed' | 'opening' | 'open' = 'closed';
private currentTrigger: LimeObjectSelectorPluginTrigger | null = null;

constructor(
private searcher: (
trigger: LimeObjectSelectorPluginTrigger,
) => Searcher,
) {}

public update(view: EditorView, lastState: EditorState): void {
this.setContainer(view);
const state = view.state;
let deleteTriggerTr: Transaction;

switch (this.state) {
case 'closed':
this.currentTrigger = this.getTriggerChar(state, lastState);

if (!this.currentTrigger) {
return;
}

deleteTriggerTr = state.tr.delete(
state.selection.from - 1,
state.selection.from,
);

this.state = 'opening';

view.dispatch(deleteTriggerTr);

break;
case 'opening':
this.openPicker(view, this.currentTrigger);
this.state = 'open';

break;
case 'open':
this.closePicker();
this.state = 'closed';

break;
default:
return;
}
}

private setContainer(view: EditorView): void {
if (this.container) {
return;
}

this.container = view.dom.parentNode;
}

private getTriggerChar(
state: EditorState,
lastState: EditorState,
): LimeObjectSelectorPluginTrigger | null {
if (state && lastState.doc.eq(state.doc)) {
return;
}

if (!state.selection.empty || !lastState.selection.empty) {
return;
}

if (state.selection.from !== lastState.selection.from + 1) {
return;
}

const lastChar = state.doc.textBetween(
state.selection.from - 1,
state.selection.from,
'',
);

if (!this.isTriggerChar(lastChar)) {
return null;
}

return lastChar;
}

private isTriggerChar(
char: string,
): char is LimeObjectSelectorPluginTrigger {
return char === '!' || char === '@';
}

private openPicker(
view: EditorView,
trigger: LimeObjectSelectorPluginTrigger,
): void {
this.picker = document.createElement(
'limel-limeobject-selector-picker',
);

const start = view.coordsAtPos(view.state.selection.from);
const end = view.coordsAtPos(view.state.selection.to);
const top =
start.top -
view.dom.parentElement.getBoundingClientRect().height -
(view.dom.parentElement.nextSibling as any).getBoundingClientRect()
.height;
const left =
// eslint-disable-next-line no-magic-numbers
Math.max((start.left + end.left) / 2 - 100, 0);

this.picker.offset = {
top: `${top}px`,
left: `${left}px`,
};

this.picker.onChange = (event: CustomEvent<ListItem<PickerValue>>) => {
const href = event.detail.value;
const title = event.detail.text;

const linkMark = view.state.schema.marks.link.create({
href: href,
title: title,
});

const textNode = view.state.schema.text(title);

const tr = view.state.tr
.replaceSelectionWith(textNode)
.addMark(
view.state.selection.from,
view.state.selection.from + textNode.nodeSize,
linkMark,
);

this.closePicker();
this.state = 'closed';

view.dispatch(tr);
};

this.picker.searcher = this.searcher(trigger);

this.container.appendChild(this.picker);
}

private closePicker(): void {
this.picker.remove();
}

public destroy(): void {
this.closePicker();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Searcher } from 'src/components/picker/searcher.types';
import { EditorPlugin } from '../editor-plugin';

export class LimeObjectSelectorPlugin extends EditorPlugin<LimeObjectSelectorPluginProps> {
constructor(props: LimeObjectSelectorPluginProps) {
super(props, 'limeobjectSelector');
}
}

export interface LimeObjectSelectorPluginProps {
trigger: LimeObjectSelectorPluginTrigger;
onChange: (value: string) => void;
searcher: (trigger: LimeObjectSelectorPluginTrigger) => Searcher;
}

export interface LimeObjectSelectorPluginState {
query: string;
}

export type LimeObjectSelectorPluginTrigger = '!' | '@';
Loading
Loading