Making EditLive! Content Behave More Like Word

This month I travelled around Australia and met with several long term Ephox clients and talked with them about their experiences with EditLive!, what they’d like to see in the product and what’s coming in EditLive!.

Their feedback was very valuable and it’s safe to say that the trip is likely to be the first of many very client focused trips.  In fact, if you’d like to share your own EditLive! experiences, please get in touch with us via the LiveWorks! mailing list, we’d love to hear from you.

One of the things that came up a few times was some inconsistencies between HTML and Word rendering that can cause confusion when users are importing content from Word. There are two simple things you can do to your web site’s CSS to help users out with this.

Firstly, default top and bottom margins.  Users experiencing this issue often complain that, when content is pasted from Word into EditLive! there’s “double spacing” between paragraphs.  This happens because the top and bottom margins on paragraphs in Word are set to 0, but in HTML it tends to be around 6 pixels or so.  The simple solution here is to override the HTML defaults in your CSS by adding something like this:

p{
        margin-top:0px;
        margin-bottom:0px;
}

It’s worth noting that you could also fix this issue by changing the top and bottom margins in your word processor’s default document template (normal.dot in Word) to match HTML, but it’s much easier to change this in your style sheet because it’s settings are centrally administered.

The second thing that often confuses people more used to creating content on their desktop rather than online is the nature of HTML tables.  This is because tables in desktop word processors have a single border, whereas HTML tables, by default actually have two borders.  You usually can’t see this because there’s no cell spacing specified.  However, if you look at the table below where I’ve set the cell spacing attribute to 2 you can clearly see the borders.

   
   

This is because each cell border is made up of two border lines - the border of the cell and the border of either the table or the neighbouring cell.

Fortunately CSS comes to the rescue again.  In order to make HTML tables behave just like tables in desktop word processors all you need to do is collapse the table borders.  This can be done by adding the following code to your site’s CSS:

table{
        border-collapse:collapse;
}

Which will make your table look more like this (note I’ve not changed the cell spacing here, just applied border-collapse):

   
   

And that’s it.  These two simple CSS changes can make a world of difference for your EditLive! users, especially when they’re importing content from desktop word processors.  Just a couple of final things to remember before you start applying this CSS everywhere:

  • Remember if you’ve got a CMS often the published CSS is different to the authoring CSS that EditLive! uses.  Make sure you make the required changes to both sets of styles.
  • You’ll want to test this CSS in a development/testing environment first to make sure it doesn’t have an unexpected impact on your web site’s layout.
  • If you’ve already got CSS specified for the “p” tag and/or the “table” tag just add these properties to what you already have.
  • If you add this CSS to the root element style (i.e. not a class style) like I have above, it should inherit across all the other classes you have in your CSS for tables and paragraphs.

Hopefully you’ll find this very useful and if you’ve got any questions just get in contact with us via the LiveWorks! mailing list.

Creating A “See Also” Panel

With the improved styles support in EditLive! 6.4 (grab the latest build from early access), and a little CSS, it's easy for users to make their content look great and better engage users. Let's look at a fairly simple example where users can add a set of links to related resources in their document. Currently, users would simply add a plain bullet list which is pretty ugly. For example:

See Also

That does the job, but with the improvements in EditLive! 6.4 and some fairly simple CSS we can make it look much more compelling. The first step is to group the heading and the list together in a group. EditLive! 6.4 provides a simple way to do that - just select them both and choose "Create Section" (the default configuration includes it in the Format menu). In terms of the HTML, this wraps a DIV element around the heading and the list which gives us some extra structure to work with but doesn't change the actual rendering.

That extra structure gives us a great place to add styles to control the see also panel from one place. The goal is that the user can simply select one item from the styles drop down and all of our formatting is applied. Let's start by adding some simple styles to the DIV itself:

div.SeeAlso {
    border: 1px solid #D4D4D4;
    width: 20em;
    margin: 0px 10px 0px 10px;
    float: right;
}

Those styles add a simple gray border, set the width of the DIV to 20em (ems are a relative sizing that's newly supported in 6.4 and is much better for accessibility than using px) and floats the DIV to the right so content can wrap around it. The result is on the right, titled "See Also (2)".

Now we want to make the heading and the list itself look better, but we don't want to make the user apply styles to them separately. Fortunately, CSS provides a descendant selector which fits this use case perfectly. We add the styles below which will automatically apply to the content inside the DIV:

div.SeeAlso h4 {
    margin: 0px;
    padding: 5px;
    font-size: 1em;
    background-image: url(largeTitleBar.png);
    color: #FFFFFF;
    background-color: #5E657B;
    background-repeat: repeat-x;
    background-position: center;
    background-attachment: scroll;
}

div.SeeAlso ul {
    margin: 0px;
    padding: 2px 5px 2px 35px;
    list-style-image: url(bluearrow.png);
}

div.SeeAlso li {
    margin: 0px;
    padding: 3px;
}

div.SeeAlso a {
    font-weight: bold;
    text-decoration: none;
    color: #2B3D72;
}

div.SeeAlso a:hover {
    text-decoration: none;
    text-decoration: underline;
    color: #8599D3;
}

See Also Panel With StylesWhile there's a fair bit of CSS there, it's all standard techniques that web designers are quite used to using for styling content. It also uses a couple of background images to provide nice gradients so that the final result looks like the image on the right. Here's the important elements that make it work well with the editor. Keep these in minds when you develop style sheets for your site:

  • Make it easy for users to apply by using a single class on the div element and then apply styles via descendant selectors whenever possible.
  • The class name you use will appear in the styles drop down, so make it self explanatory to help users know when to apply it.
  • The styles combo is context sensitive so it will show an accurate preview of how the style will look in the current context. So with the stylesheet above, the "Heading 4" entry would appear differently in the styles drop down if the current selection is within a "SeeAlso" div because the descendant style applies. Elsewhere, the default rendering of h4 elements would apply. This lets your users know what things will look like without having to try out each style. You could use this to provide different types of rendering for the header based on different heading levels and the user can pick the most appropriate one.

 

Customizing Built-In Commands

As you're probably aware, you can customize which menu items and toolbar buttons appear in EditLive! by editing your configuration file. However, most people don't realize they can also customize how those items appear while preserving the underlying functionality.

For example, if you wanted to replace the icons so they better matched your particular application's look and feel, you could do so by specifying the imageURL attribute on any or all of the item entries in your config file. So to change the "cut" menu item to use the image file "http://example.com/myCut.gif" the configuration file element would be:

<menuItem name="cut" imageURL="http://example.com/myCut.gif" />

Similarly the toolbarButton element would be:

<toolbarButton name="cut" imageURL="http://example.com/myCut.gif" />

When the user selected one of these the standard Cut routine would still be executed and the default text for the menu item and toolbar button tooltip would still be used. For best results, make sure the image you specify is 16×16 pixels and has a transparent background.

If you wanted to change the text or tooltip, just add a text="My New Cut Text" to the menuItem and toolbarButton element respectively. It's just the same as if you were using a custom menu item or custom toolbar button except that the behaviour is already implemented for you.

Deleting multiple rows or columns is now easier

We make small usability improvements to EditLive! all the time, and today I want to highlight our latest one.  In the 6.3 release we added a long-standing request for the ability to delete multiple rows or columns (previously we only deleted the first row or column in the selection).  What might not be obvious, however, is that you don't need to highlight the entire row or column to delete it.

For example, start by highlight some cells in the middle of the table like this:

From here, clicking the Delete Row button will remove both rows:

Or if you click Delete Column instead both selected columns will be removed:

The feature works even if you have only one cell in each row or column selected, I used a 2×2 square to simplify the example.  This change replicates another behavior from Microsoft Word; helping to make table manipulation more natural and ease the training requirements for non-technical users creating content in EditLive!.

Creating Bookmarks The Easy Way

Bookmarks (also known as Anchors) are a normal part of any web content.  As well as linking to an external document section, they can be used to navigate within sections of the current document.  We have a feature in EditLive! to generate these links easily, but the feature is so well integrated into our UI that I recently discovered even some of the Ephox employees don't know about it.

When creating bookmarks, most people follow a five step process:

  1. Find the text to bookmark
  2. Insert a bookmark at that location
  3. Find the place that will link to the bookmark
  4. Open the Hyperlink dialog
  5. Find the bookmark that has just been created and link to it

When building EditLive! we realized that the majority of these links will be to document headings, so we shortened it to a two step process:

  1. Open the Hyperlink dialog
  2. Find the heading you want to link to, and link to it

Any time you open the Hyperlink dialog, we find all heading tags in the document (H1 to H6) and list them under "Places in Document".  For example, the following HTML:

<h1>First Heading</h1>
<h2>Sub Heading</h2>

Looks like this in the Dialog:

Notice that spaces are removed from the bookmark address.  You don't have to worry about the dialog generating bookmarks you'll never use - they are only created when you actually link to them.  They're also inserted around the entire heading text so that it renders the heading with a blue underline and ensures the bookmark is not empty (which has caused problems on some integrations).

Combine this with easy linking to clipboard URLs and you have some cool tricks to improve your productivity.

Introducing Express Edit

EditLive! provides a rich, full featured and user friendly editing experience, but sometimes you need to roll a system out to users that may not have Java installed and that can sometimes cause deployment headaches. In the past the only option was to give up much of the functionality and ease of use that EditLive! provides and use a JavaScript based editor instead - great for users without Java but limiting and often frustrating for everyone else. So we've come up with a solution that gives you the best of both worlds and we call it Express Edit.

To get as many users up and running as fast as possible, Express Edit adds a JavaScript based editor to EditLive! with a subset of EditLive's functionality yet providing a quick and easy way for users to switch over to the full editor. The best part is that both the JavaScript and the full editor use the same configuration and provide nearly identical interfaces to the user. In fact, taking advantage of Express Edit only requires two minor changes to your existing EditLive! setup.

  1. Include the Express Edit JavaScript file. Do this immediately after you include the standard editlivejava.js file, for example:
    <script language="JavaScript" type="text/javascript" src="/editlivejava/editlivejava.js"></script>
    <script language="JavaScript" type="text/javascript" src="/editlivejava/expressEdit/expressEdit.js"></script>
  2. Instruct the instance of EditLive! to enable Express Edit.
    editlive.setExpressEdit("true");

We think Express Edit is a huge benefit for both users and system administrators. For users, the single configuration for both editors means that the interface is familiar whether they are using the JavaScript editor or the full editor so upgrading to the full editor is painless and just lets them keep working. For administrators and developers, having a single configuration file reduces maintenance costs and dramatically simplifies development and deployment - we've taken care of it all for you.

Since this functionality is so new, you'll need to be using one of the early access builds from version 6.2.7.73 and above. Also, on OS X Java is always installed and available so we always load the full editor. Otherwise, initially the JavaScript editor loads and the user can chose to switch over to the full editor if they desire. Once as user has switched to the full editor, it will load by default to give them the best experience possible by default in future.

Express Edit will be a part of the EditLive! Productivity Pack but feel free to try it out even if your license doesn't include the productivity pack. Over the next few weeks we'll look more in depth at Express Edit and what it can and can't do.

Driving Consistency With CSS

When you have multiple authors contributing to a site, it can be a challenge to ensure that the resulting site has a consistent look and feel across all your content. Fortunately, HTML was designed with exactly that problem in mind, allowing you to separate the content from the presentation using CSS. EditLive! automatically detects the CSS styles in your documents which allows your authors to not only see the document as it will be displayed, but also to apply any predefined styles you have created.

Like anything though, with a little bit of effort you can make it dramatically easier for your authors to use your styles like you intended without needing a lot of training. Here's the overall plan:

  1. Disable functionality that you don't want your authors to use.
  2. Hide CSS styles that your authors don't need.
  3. Design your CSS to work with semantic HTML.

Disabling Functionality

If you want your authors to use CSS for formatting, take away the other formatting options so they can't be used. So if you don't want your authors to apply their own fonts to content, remove the font menu. In EditLive! this is as simple as removing the toolbar buttons and menu items from the configuration file. You don't have to remove all the options, for instance you may want your authors to be able to emphasis content by making it bold so leave the bold function enabled. In most cases, I'd recommend disabling:

  • font face
  • font size
  • text color
  • highlight color
  • underline (underlined text is often confused for a hyperlink)
  • alignment

The CSS can still take advantage of all those formatting options, but users will only be using the predefined CSS styles which ensures the look of the site is consistent.

Hiding CSS Styles

Often a site layout has a range of styles that are only used for navigation components around the content like search boxes, menus and side bars. Authors don't need to apply these so they will only clutter up the styles menu and make it harder for authors to find what they need. To avoid this, either use two CSS files - one for the styles that are relevant to the actual content and one for the surrounding navigation. Include both stylesheets via link tags in your HTML pages, but only include the stylesheet that's relevant to the content in EditLive!'s configuration file.

Alternatively, you can specify that a CSS style shouldn't be displayed in EditLive! by adding the property: ephox-visible: false to the declaration. For example:

.myCSSClass {  display: inline;  color: blue;  ephox-visible: false;}

Designing CSS To Use Semantic HTML

To really make it easy for your authors to select the right CSS style, you should design your styles to be based around semantic attributes of the content rather than style. For example, if you wanted news headlines to appear as blue you could define the CSS style:

.blueText {  color: blue;}

This would appear in EditLive!'s styles menu as "blueText" and it could be applied to paragraphs or inline text. Instead, name the style "newsHeadline" to make what it is for obvious. Additionally, since newsHeadlines are always headings, specify the tag type to use. Specifying the tag type not only simplifies the style menu, it ensures that the style is applied to the right tag, making the site more consistent.

h2.newsHeading {  color: blue;}

If you want a style to only be used inline (to the selected text, not to paragraphs), specify the span tag:

span.reference {  font-style: italic;}

You can also use styles for tables, table rows and cells, lists and list items or pretty much any other HTML element. The more specific your CSS styles are, the more likely your authors will use them at the right time and the better your site will look.

Font Sizes In EditLive!

One of the biggest challenges with building a WYSIWYG HTML editor is handling the fact that HTML doesn't provide any guarantees for how things will be displayed. Different browsers and different platforms all render things slightly differently and nearly all browsers provide preferences that let users adjust the way pages display. While this can be frustrating if you're trying to make your site pixel-perfect, it's actually a really important feature as it allows users with limited vision to increase the font size for web pages or to choose a font that's easier for them to read. Fortunately, EditLive! provides a number of options to help your users create content that works well for everyone and fits into your site design.

Since the most common difference between browsers is the size of text, let's take a look at EditLive!'s font size options. If you haven't changed the default configuration file, the font size options are most likely 7pt, 8pt, 10pt, 12pt, 14pt, 18pt and 24pt. These are familiar font sizes for users and make them feel at home, but if the pt sizes were actually used in the HTML it would negate all the accessibility benefits of HTMLs variable font sizing. Instead, the sample configuration inserts relative font sizes that for the vast majority of users render at about those point sizes. Users who have adjusted their browser preferences will still get the benefits of the text being sized so they can read it easily. The configuration to achieve this is:

<toolbarComboBox name="Size">
  <comboBoxItem name="1" text="7pt"/>
  <comboBoxItem name="2" text="8pt"/>
  <comboBoxItem name="3" text="10pt"/>
  <comboBoxItem name="4" text="12pt"/>
  <comboBoxItem name="5" text="14pt"/>
  <comboBoxItem name="6" text="18pt"/>
  <comboBoxItem name="7" text="24pt"/>
</toolbarComboBox>

If you'd like your users to be aware that the font sizings are relative, you can easily change the way the options display just by changing the "text" attribute. For example:

<toolbarComboBox name="Size">
  <comboBoxItem name="1" text="Tiny"/>
  <comboBoxItem name="2" text="Rather Small"/>
  <comboBoxItem name="3" text="Normal"/>
  <comboBoxItem name="4" text="A Bit Bigger"/>
  <comboBoxItem name="5" text="Kinda Big"/>
  <comboBoxItem name="6" text="Large"/>
  <comboBoxItem name="7" text="Gigantic"/>
</toolbarComboBox>

If however, we want to change what gets inserted into the HTML when on of the options is selected, we change the "name" attribute. So to use specific pt sizes:

<toolbarComboBox name="Size">
  <comboBoxItem name="7pt" text="7pt"/>
  <comboBoxItem name="8pt" text="8pt"/>
  <comboBoxItem name="10pt" text="10pt"/>
  <comboBoxItem name="12pt" text="12pt"/>
  <comboBoxItem name="14pt" text="14pt"/>
  <comboBoxItem name="18pt" text="18pt"/>
  <comboBoxItem name="24pt" text="24pt"/>
</toolbarComboBox>

You can put whatever options your users need, including a mix of relative and absolute font sizes and you can always add extra items or remove items that you don't need.

Customizing The Insert Symbol Dialog

EditLive! provides a simple mechanism for customizing the insert symbol dialog to allow you to make it easier for your users to find the symbols they need or to add symbols that aren't provided by default. By default, EditLive! provides a pretty comprehensive set of symbols, but you can either replace those or add to them by specifying your own symbols in the configuration file.

To do this, simply edit the symbols element within the wysiwygEditor element in your configuration file. For example to have just the three symbols ©, ® and ™, the symbols element would be:

<symbols clearDialog="true">
  <symbol char="&#169;" />
  <symbol char="&#174;" />
  <symbol char="&#8482;" />
</symbols>

The clearDialog="true" attribute instructs EditLive! to replace the default with the specified symbols. If clearDialog is set to false, the specified elements will be added to the dialog along with the default symbols.

Also note how the symbols were specified as XML entities, it's important to note that the configuration file is an XML file so you need to use the numeric form of the entities and not the HTML specific named entities. Alternately, if your configuration file uses UTF-8, the symbols can specified as the characters themselves without encoding them as entities.

Automatically Linking URLs

If you're using EditLive! to edit HTML emails, it's common for users to simple type in a URL like http://www.ephox.com/ directly into the message and expect that it will automatically converted into a hyperlink. Out of the box EditLive! doesn't do this as it's generally better to use descriptive link text and only specify the URL in the actual link tag1. Fortunately, it's fairly simple to add this functionality to EditLive! using a plugin and it turns out to be a good example of how to monitor and react to the user's actions in the editor. If you'd prefer to skip the gory details and just grab the working plugin, it's available in ready-to-go form. We've previously looked at how to initialize a plugin using background loading so we'll start from there and build the actual linking functionality.

So to get started, in the last article we created the class below that handles the initialization.

public class Autolink implements EventListener {
  private ELJBean _bean;
  public Autolink(ELJBean bean) {
    _bean = bean;
    _bean.addEditorEventListener(this);
    if (_bean.isInitFinished()) {
      initializePlugin();
    }
  }
  public void raiseEvent(TextEvent e) {
    if (e.getActionCommand() == TextEvent.LOADING_COMPLETE) {
      initializePlugin();
    }
  }
}

Now we need to implement the actual initializePlugin method. We'll keep things modular by creating a second class to handle converting links and just instantiate it in initializePlugin():

private void initializePlugin() {
  new LinkConverter(_bean.getDocumentModifier(), _bean.getHTMLPane());
}

The LinkConverter works by listening for key strokes in the HTML pane and when the user types a character that could signify the end of a hyperlink (typically a space, enter or a bracket), it looks at the text just before the caret position and if it is a valid URL, uses the document modifier to apply a hyperlink to it. It's a very simple technique that uses a number of heuristics to make the user experience as pleasant as possible.

Firstly, we need to do some house keeping and register as a key listener:

public class LinkConverter extends KeyAdapter {
  private final JTextPane _pane;
  private final DocumentModifier _modifier;
  public LinkConverter(DocumentModifier modifier, JTextPane pane) {
    _modifier = modifier;
    _pane = pane;
    _pane.addKeyListener(this);
  }

Next we want to implement keyTyped and look for characters the user has typed that might indicate the end of a hyperlink. We use the heuristic that space, enter or a closing bracket might indicate the end of a hyperlink, this should cover the vast majority of cases even if it's not entirely comprehensive.

public void keyTyped(KeyEvent e) {
  if (isTriggerCharacter(e.getKeyChar())) {
    checkForUrlsToConvert();
  }
}
private boolean isTriggerCharacter(char keyChar) {
  return keyChar == ' ' ||    keyChar == '\n' ||    keyChar == '\r' ||    keyChar == ')';
}

The checkForUrlsToConvert method delegates most of the actual work but provides the structure for the algorithm, specifically:

  1. Get the text before the current caret position that could be a URL.
  2. Determine if the text is a valid URL.
  3. If it is, apply a hyperlink to it.
private void checkForUrlsToConvert() {
  try {
    String link = getMostLikelyLinkText();
    if (isUrl(link)) {
      int endOffset = _pane.getCaretPosition();
      applyHyperlink(endOffset - link.length(), endOffset, link);
    }
  } catch (Exception ex) {
    System.err.println("Failed to create URL from text.");
    ex.printStackTrace();
  }
}

Getting the text applies two more heuristics:

  • Links don't cross paragraph boundaries so we only need to search from the start of the current paragraph.
  • Valid links don't contain spaces or opening brackets so we only want the text after the last space or bracket.

The opening bracket may seem like an odd heuristic to apply, but it handles the case where the hyperlink is placed in brackets, eg: (http://www.ephox.com/). That's also why the closing bracket is included as a trigger character.

private String getMostLikelyLinkText() throws BadLocationException {
  int caretPos = _pane.getCaretPosition();
  HTMLDocument document = (HTMLDocument)_pane.getDocument();
  // Get the text from the start of the paragraph to the caret position.
  // Note that document.getText takes an offset and a length, not an end offset.
  Element paragraph = document.getParagraphElement(caretPos);
  String link = document.getText(paragraph.getStartOffset(), caretPos - paragraph.getStartOffset());
  // This is the second heuristic - the last space or bracket
  // indicates the start of the hyperlink.
  int startLink = Math.max(link.lastIndexOf(' '), link.lastIndexOf('('));
  if (startLink >= 0) {
    link = link.substring(startLink + 1);
  }
  return link;
}

Now that we've got the text of the most likely URL candidate, we need to check if it really is a URL, we do this with two steps:

  • Does it start with a valid protocol?
  • Can we parse it as a URI?
private boolean isUrl(String link) {
  if (link.startsWith("http://") || link.startsWith("ftp://") || link.startsWith("https://")) {
    try {
      new URI(link);
      return true;
    } catch (URISyntaxException e) {}
  }
  return false;
}

Finally, we need to actually apply the hyperlink. Most of the details of this are taken care of by the DocumentModifier class. In fact, by using the DocumentModifier the change will be tracked correctly by track changes, undo and redo will work and any formatting that has been applied to the text will be preserved. What we have to do is describe what attributes we want to add or remove and what range to add them in. The range is simple, and we don't need to remove any attributes, so let's look at what attributes we need to add.

The attributes to add are specified as an AttributeSet so we first create one of those. Since we want to add a hyperlink which is an inline tag, we need to add a HTML.Tag.A attribute. The value is another attribute set with the attributes of the link tag we're adding. Fortunately, it's harder to explain than it is to code:

private SimpleAttributeSet getAttributesToAdd(String href) {
  SimpleAttributeSet add = new SimpleAttributeSet();
  SimpleAttributeSet linkAttributes = new SimpleAttributeSet();
  add.addAttribute(HTML.Tag.A, linkAttributes);
  // We can add any attributes we need to the link by adding them
  // to the linkAttributes attributes set.
  linkAttributes.addAttribute(HTML.Attribute.HREF, href);
  return add;
}

Note that we use the constants HTML.Tag.A and HTML.Attribute.HREF instead of the strings "a" and "href", the constants are actually not strings and it won't work like you expect if you use strings.

Now that we've got all the components we need, applying the hyperlink is a simple matter of calling DocumentModifier.adjustCharacterAttributes:

private void applyHyperlink(int startOffset, int endOffset, String href)
throws BadLocationException {
  _modifier.adjustCharacterAttributes(startOffset, endOffset,
    getAttributesToAdd(href), Collections.EMPTY_LIST);
}

When you put all of that together, you have surprisingly effective automatic linking functionality that covers most of the potential use cases. There are obviously a few gaps, the most obvious of which are links in quotes and email addresses, but they use the same fundamental techniques. The final plugin is available for download as a complete, ready to go package with full source code and build scripts - in the future we'll look to build on it to support more use cases and make it more robust.

Finally, let's take a look at the key techniques that we used:

  • Safe initializing of plugins that load in the background.
  • Adding a KeyListener to the HTMLPane to monitor and react to user actions. Obviously any other type of swing listeners could be used when required.
  • Retrieving blocks of plain text content from the document with the getText() method. It's much faster and usually easier to retrieve a block of content from the document and work with it than to iterate over a range one character at a time.
  • Using the DocumentModifier to make changes to the document in a way that works with undo/redo, track changes and handles all the other little details that user's expect like preserving content formatting correctly.
  • Using nested attribute sets and the constants in javax.swing.text.html.HTML.Tag and javax.swing.text.html.HTML.Attribute to define inline tags and attributes.

1 - this is actually quite important for accessibility - having a screen reader speak a URL is much less pleasant than having it read a description of where the link actually goes