Tips for effectively utilizing hyperlinks in Medium Editor

Recently, I've been exploring the amazing capabilities of a tool called Medium Editor. However, I've come across an issue where I can't seem to get links to function properly.

To illustrate the problem simply, here is some HTML/JS code that showcases the issue:

HTML:

<html>
<head>
  <script src="//cdn.jsdelivr.net/medium-editor/latest/js/medium-editor.min.js"></script>
  <link rel="stylesheet" href="//cdn.jsdelivr.net/medium-editor/latest/css/medium-editor.min.css" type="text/css" media="screen" charset="utf-8">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/medium-editor/latest/css/themes/beagle.min.css" type="text/css">
</head>
<body>
  <div class='editable'>
    Hello world.  <a href="http://www.google.com">link</a>
  </div>
</body>
</html>

Javascript:

var editor = new MediumEditor('.editable');

This example highlights the issue mentioned above (using the provided code).

  • When hovering over the link, a popup appears.
  • Clicking on the link does not redirect you to the specified URL.
  • By clicking on the popup, a form emerges allowing you to modify the link.

It seems logical that clicking on the link should direct the user to the target location specified by its href. Having users right-click and open in a new tab/window is not ideal for usability.

I have a feeling that there might be a simple configuration setting that I am missing, perhaps related to the Anchor Preview Options or the Anchor Form Options. Unfortunately, I haven't been able to identify it yet.

In my application, I am not utilizing jQuery, but I am incorporating angularjs. If there isn't a direct solution within Medium Editor, I am open to using vanilla JS or leveraging angularjs features as alternatives.

Answer №2

medium-editor utilizes the intrinsic browser functionality for contenteditable elements as its foundation. By initializing medium-editor, it automatically assigns the contenteditable=true attribute to the specified element(s).

Due to this attribute, which transforms the text into an editable format resembling WYSIWYG text, standard hyperlink navigation within the text is no longer supported by the browser. This behavior is not imposed by medium-editor but rather occurs naturally when making the text editable.

medium-editor comes with predefined extensions for managing links:

  • anchor extension
    • enables addition/removal of links
  • anchor-preview extension
    • displays a tooltip upon link hover
    • allows link href editing through anchor extension on clicking the tooltip

The primary objective of the editor seems to be misconstrued here. The purpose of the editor is to facilitate text editing, and in order to modify links, users must access them without triggering immediate navigation - essentially entering 'edit' mode.

However, the resulting HTML post-editing remains valid, and transferring it to an element lacking the contenteditable=true attribute ensures normal functioning - denoting 'publish' mode.

This concept aligns with platforms like Microsoft Word or Google Docs where links require intentional actions for navigation while editing, contrasting with automatic redirection once published.

An actionable suggestion can be enhancing the existing anchor-preview extension with additional features in its hovering tooltip (e.g., Edit Link | Remove Link | Navigate to URL).

tldr;

Clickable links are disabled during text editing using browser's inherent WYSIWYG support (contenteditable). Post-editing, links will function normally outside 'edit' mode. Enhancements to medium-editor's anchor-preview extension could be beneficial.

Answer №3

After gathering inspiration from @Valijon's suggestions in the comments, I successfully implemented the solution by utilizing the code snippet below:

var iElement = angular.element(mediumEditorElement);

iElement.on('click', function(event) {
  if (
      event.target && event.target.tagName == 'A' &&
      event.target.href && !event.defaultPrevented) {
    $window.open(event.target.href, '_blank');
  }
});

It seems that the key lies in the fact that the editor allows the event to propagate to its ancestor elements, hence why I could simply listen for the click on the top-level editor element.

In this context, $window refers to Angular's $window service -- In case you are not using angularjs, window can be used as an alternative. I opted for angular.element to streamline the event listener registration process, but feel free to implement it using traditional methods (or with your chosen JS framework).

Answer №4

When I initially asked the question, all I wanted was a behavior similar to Google Docs while in "edit" mode (as explained by Nate Mielnik). Unfortunately, the Medium Editor team decided not to include this feature as part of the core medium editor. However, they mentioned that they would welcome it as an extension if someone were to add it.

So, taking their suggestion, I went ahead and implemented the functionality as an extension myself. You can find it as part of MediumTools. Please note that the project is still in its early stages and there are areas that require improvement such as styling and code minification. We welcome Pull Requests for enhancements.

The code structure is outlined below:

var ClassName = {
  INNER: 'medium-editor-toolbar-anchor-preview-inner',
  INNER_CHANGE: 'medium-editor-toolbar-anchor-preview-inner-change',
  INNER_REMOVE: 'medium-editor-toolbar-anchor-preview-inner-remove'
}

var AnchorPreview = MediumEditor.extensions.anchorPreview;
GdocMediumAnchorPreview = MediumEditor.Extension.extend.call(
  AnchorPreview, {

    /** @override */
    getTemplate: function () {
      return '<div class="medium-editor-toolbar-anchor-preview">' +
        '  <a class="' + ClassName.INNER + '"></a>' +
        '  -' +
        '  <a class="' + ClassName.INNER_CHANGE + '">Change</a>' +
        '  |' +
        '  <a class="' + ClassName.INNER_REMOVE + '">Remove</a>' +
        '</div>';
    },

    /** @override */
    createPreview: function () {
      var el = this.document.createElement('div');

      el.id = 'medium-editor-anchor-preview-' + this.getEditorId();
      el.className = 'medium-editor-anchor-preview';
      el.innerHTML = this.getTemplate();

      var targetBlank =
          this.getEditorOption('targetBlank') ||
          this.getEditorOption('gdocAnchorTargetBlank');
      if (targetBlank) {
        el.querySelector('.' + ClassName.INNER).target = '_blank';
      }

      var changeEl = el.querySelector('.' + ClassName.INNER_CHANGE);
      this.on(changeEl, 'click', this.handleClick.bind(this));

      var unlinkEl = el.querySelector('.' + ClassName.INNER_REMOVE);
      this.on(unlinkEl, 'click', this.handleUnlink.bind(this));

      return el;
    },

    /** Unlink the currently active anchor. */
    handleUnlink: function() {
      var activeAnchor = this.activeAnchor;
      if (activeAnchor) {
        this.activeAnchor.outerHTML = this.activeAnchor.innerHTML;
        this.hidePreview();
      }
    }
  });

To explain further, I utilized medium's prototypical inheritance approach to "subclass" the built-in AnchorPreview extension. I customized the getTemplate method to incorporate additional links into the markup. Subsequently, I made modifications based on the base implementation of getPreview, attaching new actions to each link accordingly. Finally, I included a method to handle unlinking when the "Remove" action is triggered. This unlink method could be optimized using contenteditable features for better browser integration, but I have yet to explore that option (which could make a valuable contribution via a Pull Request).

1Currently, this is the only part of the project, but hopefully, that will change in the future. . .

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Angular: Promise updating array but integer remains static

At the moment, I have a factory and a controller set up. The factory is responsible for updating with items retrieved from an endpoint along with the total number of pages of data. While my data array seems to be working properly, the pageCount (an integ ...

Assistance Required for Making a Delicious Cookie

Within my interface, there are two buttons displayed - one is labeled yes while the other is called no. <input type="button" name="yes" onclick="button()"> <input type="button" name="no"> In order to enhance user experience, I am looking to i ...

Attempted to incorporate a text input into the header of every column within a table using Jquery

Struggling to have a text input appear below the header span as desired, encountering some unexpected behavior - The feature seems to not be functioning properly. I previously implemented this in C# and now need to transition it to clientside due to speci ...

Tips for selecting React component props based on a specific condition

Here is a React component that I have: <SweetAlert show={this.props.message} success title={this.props.message} onConfirm={this.props.handleCloseAlert}> </SweetAlert> Upon using this component, I receive the following alert ...

How can I extract a substring from a URL and then save it to the clipboard?

Hi there! I'm working on a project for my school and could really use your expertise. I need help extracting a substring from a URL, like this one: URL = https://www.example.com/blah/blah&code=12432 The substring is: 12432 I also want to display ...

In Vue, you can dynamically load a JavaScript file containing a JavaScript object during runtime

I'm in the process of developing a no-code application with Vue. I have come across an issue where I cannot add functions to a JSON file that I want to import at runtime. As a workaround, I decided to use a JavaScript or TypeScript file to store the J ...

Is it possible to display data on a webpage without using dynamic content, or do I need to rely on JavaScript

Imagine a scenario where I have a single-page website and I want to optimize the loading time by only displaying content below the fold when the user clicks on a link to access it. However, I don't want users with disabled JavaScript to miss out on th ...

Setting default zoom level for Google Map in HTML

Initially, the map for the second city is hidden. When the second button is clicked, the map for the first city will be hidden and the map for the second city will be displayed. However, in this case, the map for the second city appears zoomed out. How can ...

Having issues with IE8 and SmoothGallery script errors

I'm currently utilizing the SmoothGallery plugin created by Jon Designs to enhance the website of one of my clients. However, there seems to be a minor issue in Internet Explorer 8 when attempting to navigate to the next image. Interestingly enough, a ...

Challenges with the dropdown menu navigation bar

I am struggling with my dropdown menu and sign up/sign in buttons as they are not aligning properly. I have tried various coding methods but haven't been able to fix the issue. Can someone provide me with suggestions on how to rectify this problem? c ...

Using JavaScript to compare arrays in order to display only the distinct outcome

Just starting out with JavaScript array matching! I've got two arrays, both with 11 elements each: txtfilename=['txt1','txt6','txt6','txt6','txt7','txt7','txt8','txt9',& ...

Initializing a resource in AngularJS by populating it with a string retrieved from an $http request

One issue I encountered with my angular app is the need to initialise some resources with a string value. For example: angular.module('client.core.resources') .factory('AuthenticationResource', ['$resource','ConstantV ...

What is the reason that a peerDependency cannot be imported or required, even if it is already included in the parent module?

When attempting to utilize npm's peerDependencies, I'm encountering issues that do not align with the expected behavior. What could be causing this discrepancy? The scenario is as follows: I have two modules, mod and plugin, both of which rely o ...

Retrieving information from a JSON web service can easily be done using just JavaScript and jQuery

I recently downloaded a sample application from the following URL: . I am pleased to report that the part I have implemented is functioning flawlessly: <script src="scripts/jquery-1.3.2.debug.js" type="text/javascript"></script> <script src ...

Change the height of textarea dynamically using jQuery

I am trying to create a comment box similar to Facebook's, where it resizes as text fills it using Expanding Text Areas Made Elegant This is how my view looks: <div class='expandingArea'> <pre><span></span></ ...

Incorrect date formatting is being displayed

vr_date :Date alert(this.vr_date ) // Result Displays Thu Feb 07 2019 00:00:00 GMT+0400 var json = JSON.stringify(this.vr_date); alert(json); // Outcome Reveals 2019-02-06T20:00:00.000Z with incorrect date The date output shows 06 instead of 07 on my ...

Struggling with filtering an array fetched from an API using VueJS

Currently, I am working on a Nativescript-Vue app and facing some challenges that I need help with. The Scenario I have data coming in from an API. Here is the structure of the data I receive: { "count": 9, "results": [ { "id": 1, "c ...

Communicating data transfer between two Node.js servers through the use of the Node Serial Port technology

How can I send the message "Hello world" from one nodejs server to another using node-serialport? I have confirmed that the radios connecting the two servers are properly connected as they are displaying buffer information after running my current code. ...

What is the best way to determine if the form has been submitted?

I am working on a React form and need to determine when the form has been successfully submitted in order to trigger another request in a separate form. const formRef = React.useRef<HTMLFormElement>(null); useEffect(() => { if (formRef &a ...

Changing the variable value upon clicking

When I click on an li, my goal is to retrieve the value of the data-currency attribute and store it in a var. This is the code I am using: $(function(){ var $currency = "€"; $('.pricingTable__dropdown-li').click(function(){ var dat ...