diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts
index 0a939baf4..d7f873ea5 100644
--- a/resources/js/wysiwyg/index.ts
+++ b/resources/js/wysiwyg/index.ts
@@ -45,11 +45,12 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st
 
     const editor = createEditor(config);
     editor.setRootElement(editArea);
+    const context: EditorUiContext = buildEditorUI(container, editArea, editWrap, editor, options);
 
     mergeRegister(
         registerRichText(editor),
         registerHistory(editor, createEmptyHistoryState(), 300),
-        registerShortcuts(editor),
+        registerShortcuts(context),
         registerTableResizer(editor, editWrap),
         registerTableSelectionHandler(editor),
         registerTaskListHandler(editor, editArea),
@@ -89,7 +90,6 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st
         console.log(editor.getEditorState().toJSON());
     };
 
-    const context: EditorUiContext = buildEditorUI(container, editArea, editWrap, editor, options);
     registerCommonNodeMutationListeners(context);
 
     return new SimpleWysiwygEditorInterface(editor);
diff --git a/resources/js/wysiwyg/services/shortcuts.ts b/resources/js/wysiwyg/services/shortcuts.ts
index 235c2788a..b17ec1bf7 100644
--- a/resources/js/wysiwyg/services/shortcuts.ts
+++ b/resources/js/wysiwyg/services/shortcuts.ts
@@ -1,12 +1,17 @@
-import {COMMAND_PRIORITY_HIGH, FORMAT_TEXT_COMMAND, KEY_ENTER_COMMAND, LexicalEditor} from "lexical";
+import {$getSelection, COMMAND_PRIORITY_HIGH, FORMAT_TEXT_COMMAND, KEY_ENTER_COMMAND, LexicalEditor} from "lexical";
 import {
     cycleSelectionCalloutFormats,
-    formatCodeBlock,
+    formatCodeBlock, insertOrUpdateLink,
     toggleSelectionAsBlockquote,
-    toggleSelectionAsHeading,
+    toggleSelectionAsHeading, toggleSelectionAsList,
     toggleSelectionAsParagraph
 } from "../utils/formats";
 import {HeadingTagType} from "@lexical/rich-text";
+import {EditorUiContext} from "../ui/framework/core";
+import {$getNodeFromSelection} from "../utils/selection";
+import {$isLinkNode, LinkNode} from "@lexical/link";
+import {$showLinkForm} from "../ui/defaults/forms/objects";
+import {showLinkSelector} from "../utils/links";
 
 function headerHandler(editor: LexicalEditor, tag: HeadingTagType): boolean {
     toggleSelectionAsHeading(editor, tag);
@@ -25,10 +30,9 @@ function toggleInlineCode(editor: LexicalEditor): boolean {
     return true;
 }
 
-type ShortcutAction = (editor: LexicalEditor) => boolean;
+type ShortcutAction = (editor: LexicalEditor, context: EditorUiContext) => boolean;
 
 const actionsByKeys: Record<string, ShortcutAction> = {
-    // Save draft
     'ctrl+s': () => {
         window.$events.emit('editor-save-draft');
         return true;
@@ -51,18 +55,35 @@ const actionsByKeys: Record<string, ShortcutAction> = {
     'ctrl+shift+e': toggleInlineCode,
     'ctrl+9': wrapFormatAction(cycleSelectionCalloutFormats),
 
-    // TODO Lists
-    // TODO Links
-    // TODO Link selector
+    'ctrl+o': wrapFormatAction((e) => toggleSelectionAsList(e, 'number')),
+    'ctrl+p': wrapFormatAction((e) => toggleSelectionAsList(e, 'bullet')),
+    'ctrl+k': (editor, context) => {
+        editor.getEditorState().read(() => {
+            const selectedLink = $getNodeFromSelection($getSelection(), $isLinkNode) as LinkNode | null;
+            $showLinkForm(selectedLink, context);
+        });
+        return true;
+    },
+    'ctrl+shift+k': (editor, context) => {
+        showLinkSelector(entity => {
+            insertOrUpdateLink(editor, {
+                text: entity.name,
+                title: entity.link,
+                target: '',
+                url: entity.link,
+            });
+        });
+        return true;
+    },
 };
 
-function createKeyDownListener(editor: LexicalEditor): (e: KeyboardEvent) => void {
+function createKeyDownListener(context: EditorUiContext): (e: KeyboardEvent) => void {
     return (event: KeyboardEvent) => {
         // TODO - Mac Cmd support
         const combo = `${event.ctrlKey ? 'ctrl+' : ''}${event.shiftKey ? 'shift+' : ''}${event.key}`.toLowerCase();
-        console.log(`pressed: ${combo}`);
+        // console.log(`pressed: ${combo}`);
         if (actionsByKeys[combo]) {
-            const handled = actionsByKeys[combo](editor);
+            const handled = actionsByKeys[combo](context.editor, context);
             if (handled) {
                 event.stopPropagation();
                 event.preventDefault();
@@ -78,11 +99,11 @@ function overrideDefaultCommands(editor: LexicalEditor) {
     }, COMMAND_PRIORITY_HIGH);
 }
 
-export function registerShortcuts(editor: LexicalEditor) {
-    const listener = createKeyDownListener(editor);
-    overrideDefaultCommands(editor);
+export function registerShortcuts(context: EditorUiContext) {
+    const listener = createKeyDownListener(context);
+    overrideDefaultCommands(context.editor);
 
-    return editor.registerRootListener((rootElement: null | HTMLElement, prevRootElement: null | HTMLElement) => {
+    return context.editor.registerRootListener((rootElement: null | HTMLElement, prevRootElement: null | HTMLElement) => {
         // add the listener to the current root element
         rootElement?.addEventListener('keydown', listener);
         // remove the listener from the old root element
diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md
index 75263c927..f05e79baa 100644
--- a/resources/js/wysiwyg/todo.md
+++ b/resources/js/wysiwyg/todo.md
@@ -2,7 +2,7 @@
 
 ## In progress
 
-- Keyboard shortcuts support
+// 
 
 ## Main Todo
 
@@ -13,6 +13,7 @@
 - Media resize support (like images)
 - Table caption text support
 - Table Cut/Copy/Paste column
+- Mac: Shortcut support via command.
 
 ## Secondary Todo
 
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/lists.ts b/resources/js/wysiwyg/ui/defaults/buttons/lists.ts
index 10500eb67..edec3ea00 100644
--- a/resources/js/wysiwyg/ui/defaults/buttons/lists.ts
+++ b/resources/js/wysiwyg/ui/defaults/buttons/lists.ts
@@ -1,11 +1,12 @@
-import {$isListNode, insertList, ListNode, ListType, removeList} from "@lexical/list";
+import {$isListNode, ListNode, ListType} from "@lexical/list";
 import {EditorButtonDefinition} from "../../framework/buttons";
 import {EditorUiContext} from "../../framework/core";
-import {$getSelection, BaseSelection, LexicalNode} from "lexical";
+import {BaseSelection, LexicalNode} from "lexical";
 import listBulletIcon from "@icons/editor/list-bullet.svg";
 import listNumberedIcon from "@icons/editor/list-numbered.svg";
 import listCheckIcon from "@icons/editor/list-check.svg";
 import {$selectionContainsNodeType} from "../../../utils/selection";
+import {toggleSelectionAsList} from "../../../utils/formats";
 
 
 function buildListButton(label: string, type: ListType, icon: string): EditorButtonDefinition {
@@ -13,14 +14,7 @@ function buildListButton(label: string, type: ListType, icon: string): EditorBut
         label,
         icon,
         action(context: EditorUiContext) {
-            context.editor.getEditorState().read(() => {
-                const selection = $getSelection();
-                if (this.isActive(selection, context)) {
-                    removeList(context.editor);
-                } else {
-                    insertList(context.editor, type);
-                }
-            });
+            toggleSelectionAsList(context.editor, type);
         },
         isActive(selection: BaseSelection|null): boolean {
             return $selectionContainsNodeType(selection, (node: LexicalNode | null | undefined): boolean => {
diff --git a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts
index 3494096a2..46556d3d1 100644
--- a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts
+++ b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts
@@ -2,11 +2,9 @@ import {EditorButtonDefinition} from "../../framework/buttons";
 import linkIcon from "@icons/editor/link.svg";
 import {EditorUiContext} from "../../framework/core";
 import {
-    $createNodeSelection,
     $createTextNode,
     $getRoot,
     $getSelection, $insertNodes,
-    $setSelection,
     BaseSelection,
     ElementNode
 } from "lexical";
@@ -17,7 +15,7 @@ import {$isImageNode, ImageNode} from "../../../nodes/image";
 import horizontalRuleIcon from "@icons/editor/horizontal-rule.svg";
 import {$createHorizontalRuleNode, $isHorizontalRuleNode} from "../../../nodes/horizontal-rule";
 import codeBlockIcon from "@icons/editor/code-block.svg";
-import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../../../nodes/code-block";
+import {$isCodeBlockNode} from "../../../nodes/code-block";
 import editIcon from "@icons/edit.svg";
 import diagramIcon from "@icons/editor/diagram.svg";
 import {$createDiagramNode, DiagramNode} from "../../../nodes/diagram";
@@ -32,35 +30,16 @@ import {
 } from "../../../utils/selection";
 import {$isDiagramNode, $openDrawingEditorForNode, showDiagramManagerForInsert} from "../../../utils/diagrams";
 import {$createLinkedImageNodeFromImageData, showImageManager} from "../../../utils/images";
-import {$showImageForm} from "../forms/objects";
+import {$showImageForm, $showLinkForm} from "../forms/objects";
 import {formatCodeBlock} from "../../../utils/formats";
 
 export const link: EditorButtonDefinition = {
     label: 'Insert/edit link',
     icon: linkIcon,
     action(context: EditorUiContext) {
-        const linkModal = context.manager.createModal('link');
         context.editor.getEditorState().read(() => {
-            const selection = $getSelection();
-            const selectedLink = $getNodeFromSelection(selection, $isLinkNode) as LinkNode | null;
-
-            let formDefaults = {};
-            if (selectedLink) {
-                formDefaults = {
-                    url: selectedLink.getURL(),
-                    text: selectedLink.getTextContent(),
-                    title: selectedLink.getTitle(),
-                    target: selectedLink.getTarget(),
-                }
-
-                context.editor.update(() => {
-                    const selection = $createNodeSelection();
-                    selection.add(selectedLink.getKey());
-                    $setSelection(selection);
-                });
-            }
-
-            linkModal.show(formDefaults);
+            const selectedLink = $getNodeFromSelection($getSelection(), $isLinkNode) as LinkNode | null;
+            $showLinkForm(selectedLink, context);
         });
     },
     isActive(selection: BaseSelection | null): boolean {
diff --git a/resources/js/wysiwyg/ui/defaults/forms/objects.ts b/resources/js/wysiwyg/ui/defaults/forms/objects.ts
index 2aefe5414..714d5f64b 100644
--- a/resources/js/wysiwyg/ui/defaults/forms/objects.ts
+++ b/resources/js/wysiwyg/ui/defaults/forms/objects.ts
@@ -5,9 +5,9 @@ import {
     EditorSelectFormFieldDefinition
 } from "../../framework/forms";
 import {EditorUiContext} from "../../framework/core";
-import {$createTextNode, $getSelection, $insertNodes} from "lexical";
+import {$createNodeSelection, $createTextNode, $getSelection, $insertNodes, $setSelection} from "lexical";
 import {$isImageNode, ImageNode} from "../../../nodes/image";
-import {$createLinkNode, $isLinkNode} from "@lexical/link";
+import {$createLinkNode, $isLinkNode, LinkNode} from "@lexical/link";
 import {$createMediaNodeFromHtml, $createMediaNodeFromSrc, $isMediaNode, MediaNode} from "../../../nodes/media";
 import {$insertNodeToNearestRoot} from "@lexical/utils";
 import {$getNodeFromSelection, getLastSelection} from "../../../utils/selection";
@@ -19,6 +19,7 @@ import searchImageIcon from "@icons/editor/image-search.svg";
 import searchIcon from "@icons/search.svg";
 import {showLinkSelector} from "../../../utils/links";
 import {LinkField} from "../../framework/blocks/link-field";
+import {insertOrUpdateLink} from "../../../utils/formats";
 
 export function $showImageForm(image: ImageNode, context: EditorUiContext) {
     const imageModal: EditorFormModal = context.manager.createModal('image');
@@ -96,37 +97,36 @@ export const image: EditorFormDefinition = {
     ],
 };
 
+export function $showLinkForm(link: LinkNode|null, context: EditorUiContext) {
+    const linkModal = context.manager.createModal('link');
+
+    let formDefaults = {};
+    if (link) {
+        formDefaults = {
+            url: link.getURL(),
+            text: link.getTextContent(),
+            title: link.getTitle(),
+            target: link.getTarget(),
+        }
+
+        context.editor.update(() => {
+            const selection = $createNodeSelection();
+            selection.add(link.getKey());
+            $setSelection(selection);
+        });
+    }
+
+    linkModal.show(formDefaults);
+}
+
 export const link: EditorFormDefinition = {
     submitText: 'Apply',
     async action(formData, context: EditorUiContext) {
-        context.editor.update(() => {
-
-            const url = formData.get('url')?.toString() || '';
-            const title = formData.get('title')?.toString() || ''
-            const target = formData.get('target')?.toString() || '';
-            const text = formData.get('text')?.toString() || '';
-
-            const selection = $getSelection();
-            let link = $getNodeFromSelection(selection, $isLinkNode);
-            if ($isLinkNode(link)) {
-                link.setURL(url);
-                link.setTarget(target);
-                link.setTitle(title);
-            } else {
-                link = $createLinkNode(url, {
-                    title: title,
-                    target: target,
-                });
-
-                $insertNodes([link]);
-            }
-
-            if ($isLinkNode(link)) {
-                for (const child of link.getChildren()) {
-                    child.remove(true);
-                }
-                link.append($createTextNode(text));
-            }
+        insertOrUpdateLink(context.editor, {
+            url: formData.get('url')?.toString() || '',
+            title: formData.get('title')?.toString() || '',
+            target: formData.get('target')?.toString() || '',
+            text: formData.get('text')?.toString() || '',
         });
         return true;
     },
diff --git a/resources/js/wysiwyg/utils/formats.ts b/resources/js/wysiwyg/utils/formats.ts
index 340be393d..97038f07b 100644
--- a/resources/js/wysiwyg/utils/formats.ts
+++ b/resources/js/wysiwyg/utils/formats.ts
@@ -1,9 +1,9 @@
 import {$isQuoteNode, HeadingNode, HeadingTagType} from "@lexical/rich-text";
-import {$getSelection, LexicalEditor, LexicalNode} from "lexical";
+import {$createTextNode, $getSelection, $insertNodes, LexicalEditor, LexicalNode} from "lexical";
 import {
     $getBlockElementNodesInSelection,
     $getNodeFromSelection,
-    $insertNewBlockNodeAtSelection,
+    $insertNewBlockNodeAtSelection, $selectionContainsNodeType,
     $toggleSelectionBlockNodeType,
     getLastSelection
 } from "./selection";
@@ -12,6 +12,9 @@ import {$createCustomParagraphNode, $isCustomParagraphNode} from "../nodes/custo
 import {$createCustomQuoteNode} from "../nodes/custom-quote";
 import {$createCodeBlockNode, $isCodeBlockNode, $openCodeEditorForNode, CodeBlockNode} from "../nodes/code-block";
 import {$createCalloutNode, $isCalloutNode, CalloutCategory} from "../nodes/callout";
+import {insertList, ListNode, ListType, removeList} from "@lexical/list";
+import {$isCustomListNode} from "../nodes/custom-list";
+import {$createLinkNode, $isLinkNode} from "@lexical/link";
 
 const $isHeaderNodeOfTag = (node: LexicalNode | null | undefined, tag: HeadingTagType) => {
     return $isCustomHeadingNode(node) && (node as HeadingNode).getTag() === tag;
@@ -38,6 +41,21 @@ export function toggleSelectionAsBlockquote(editor: LexicalEditor) {
     });
 }
 
+export function toggleSelectionAsList(editor: LexicalEditor, type: ListType) {
+    editor.getEditorState().read(() => {
+        const selection = $getSelection();
+        const listSelected = $selectionContainsNodeType(selection, (node: LexicalNode | null | undefined): boolean => {
+            return $isCustomListNode(node) && (node as ListNode).getListType() === type;
+        });
+
+        if (listSelected) {
+            removeList(editor);
+        } else {
+            insertList(editor, type);
+        }
+    });
+}
+
 export function formatCodeBlock(editor: LexicalEditor) {
     editor.getEditorState().read(() => {
         const selection = $getSelection();
@@ -85,4 +103,30 @@ export function cycleSelectionCalloutFormats(editor: LexicalEditor) {
             }
         }
     });
+}
+
+export function insertOrUpdateLink(editor: LexicalEditor, linkDetails: {text: string, title: string, target: string, url: string}) {
+    editor.update(() => {
+        const selection = $getSelection();
+        let link = $getNodeFromSelection(selection, $isLinkNode);
+        if ($isLinkNode(link)) {
+            link.setURL(linkDetails.url);
+            link.setTarget(linkDetails.target);
+            link.setTitle(linkDetails.title);
+        } else {
+            link = $createLinkNode(linkDetails.url, {
+                title: linkDetails.title,
+                target: linkDetails.target,
+            });
+
+            $insertNodes([link]);
+        }
+
+        if ($isLinkNode(link)) {
+            for (const child of link.getChildren()) {
+                child.remove(true);
+            }
+            link.append($createTextNode(linkDetails.text));
+        }
+    });
 }
\ No newline at end of file