├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc └── source │ ├── conf.py │ └── index.rst ├── mf2util.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── authorship ├── h-card_with_u-url_equal_to_self.html ├── h-card_with_u-url_equal_to_u-uid_equal_to_self.html ├── h-card_with_u-url_that_is_also_rel-me.html ├── h-entry_with_p-author_h-card.html ├── h-entry_with_rel-author.html ├── h-entry_with_u-author.html ├── h-feed_with_p-author_h-card.html ├── h-feed_with_u-author.html └── no_h-card.html ├── interpret ├── article_naive_datetime.json ├── article_no_p-name.json ├── article_non_ascii_content.json ├── article_two_published_dates.json ├── follow.json ├── hwc-event.json ├── location_h-adr.json ├── location_h-card.json ├── location_h-geo.json ├── location_top_level.json ├── note_with_comment_and_like.json ├── relative_paths.json ├── reply_h-cite.json ├── reply_invite.json ├── reply_rsvp.json ├── reply_u-in-reply-to.json └── unusual_properties.json ├── posttype ├── hcard_no_name.json ├── hcard_org.json ├── only_html_content.json └── tantek_photo.json ├── test_authorship.py ├── test_classify_comments.py ├── test_datetime.py ├── test_interpret.py ├── test_is_name_a_title.py ├── test_post_type_discovery.py └── test_representative_hcard.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .cache 3 | local 4 | mf2util.egg-info 5 | TAGS 6 | .eggs 7 | build 8 | dist 9 | .vscode 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | install: 8 | - pip install --upgrade setuptools 9 | - pip install pytest 10 | - pip install mf2py 11 | script: 12 | pytest tests 13 | sudo: false 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Simplifed BSD License 2 | 3 | Copyright (c) 2014, Kyle Mahan 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are 8 | met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the 16 | distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Microformats2 Utilities 2 | ======================= 3 | 4 | [](https://travis-ci.org/kylewm/mf2util) 6 | [](https://readthedocs.org/projects/mf2util/?badge=latest) 8 | 9 | Microformats2 provides an extremely flexible way to mark up HTML 10 | documents, so that human-centric data is machine-discoverable. This 11 | utility can be used to interpret a microformatted post or event, for 12 | display as a [comment](http://indiewebcamp.com/comments-presentation) or 13 | [reply-context](http://indiewebcamp.com/reply-context). 14 | 15 | The library itself has no dependencies, but it won't do you much good 16 | without an mf2 parser. I use and recommend 17 | [mf2py](https://github.com/tommorris/mf2py). 18 | 19 | Compatibility: Python 2.6, 2.7, 3.3+ 20 | 21 | License: [Simplified BSD](http://opensource.org/licenses/BSD-2-Clause) 22 | 23 | Installation 24 | ------------ 25 | 26 | I've done my best to create appropriate unit tests for this library, but 27 | it is very much alpha software at this point. 28 | 29 | Install via pip 30 | 31 | pip install mf2util 32 | 33 | or add as a submodule to your own project. 34 | 35 | I've used pytest for running unit tests (These are also run 36 | automatically by Travis-CI) 37 | 38 | pip install pytest 39 | python -m pytest 40 | 41 | Quick Start 42 | ----------- 43 | 44 | For received webmentions, use the method `mf2util.interpret_comment`. 45 | This will return a dictionary with the fields necessary to display the 46 | comment. For example: 47 | 48 | ```python 49 | import mf2py 50 | import mf2util 51 | 52 | # source_url = source_url of incoming webmention 53 | # target_url = target_url of incoming webmention 54 | 55 | parsed = mf2py.Parser(url=source_url).to_dict() 56 | comment = mf2util.interpret_comment(parsed, source_url, [target_url]) 57 | 58 | # result 59 | { 60 | 'type': 'entry', 61 | 'name': 'Re: How to make toast', 62 | 'content': '
This solved my problem, thanks!
', 63 | 'url': 'http://facebook.com/posts/0123456789', 64 | 'published': datetime.datetime(2014, 11, 24, 13, 24) 65 | 'author': { 66 | 'name': 'John Doe', 67 | 'url': 'http://facebook.com/john.doe', 68 | 'photo': 'http://img.facebook.com/johndoe-profile-picture.jpg' 69 | }, 70 | 'comment_type': ['reply'] 71 | } 72 | ``` 73 | 74 | When display reply-context, you may not know the precise type of the 75 | source document. Use the method `mf2util.interpret` to interpret the 76 | document, it will figure out the document's primary h- type and return 77 | the appropriate fields for display. Currently supports h-entry and 78 | h-event style documents. 79 | 80 | ```python 81 | import mf2py 82 | import mf2util 83 | 84 | # reply_to_url = url being replied to 85 | 86 | parsed = mf2py.Parser(url=rely_to_url).to_dict() 87 | entry = mf2util.interpret(parsed, reply_to_url) 88 | 89 | # result 90 | { 91 | 'type': 'event', 92 | 'name': 'Homebrew Website Club', 93 | 'start': datetime.datetime(2014, 5, 7, 18, 30), 94 | 'end': datetime.datetime(2014, 5, 7, 19, 30), 95 | 'content': 'Exchange information, swap ideas, talk shop, help work on a project ...
' 96 | } 97 | ``` 98 | 99 | For most users, these two methods alone may be sufficient. 100 | 101 | Comments 102 | -------- 103 | 104 | When processing an incoming webmention, you can use the 105 | `mf2util.classify_comment` method to classify it as a reply, like, or 106 | repost (or a combination thereof). The method returns a list of zero or 107 | more strings (one of 'like', 'repost', or 'reply'). 108 | 109 | ### Usage 110 | 111 | ```python 112 | import mf2py 113 | import mf2util 114 | 115 | # receive webmention from source_url to target_url 116 | target_url = 'http://my-domain.com/2014/04/12/1' 117 | alternate_url = 'http://doma.in/V4ls' 118 | parsed = mf2py.Parser(url=source_url) 119 | mentions = mf2util.classify_comment(parsed, [target_url, alternative_url]) 120 | ``` 121 | 122 | Datetimes 123 | --------- 124 | 125 | The `mf2util.parse_datetime` function is useful for parsing microformats2 126 | dates and datetimes. It can be used as a microformats-specific 127 | alternative to larger, more general libraries like python-dateutil. 128 | 129 | The definition for microformats2 dt-\* properties are fairly lenient. 130 | This module will convert a mf2 date string into either a datetime.date 131 | or datetime.datetime object. Datetimes will be naive unless a timezone 132 | is specified. 133 | 134 | Timezones are specified as fixed offsets from UTC. 135 | 136 | ### Usage 137 | 138 | ```python 139 | import mf2py 140 | import mf2util 141 | 142 | parsed = mf2py.Parser(=…) 143 | publishedstr = parsed.to_dict()['items'][0]['properties']['published'][0] 144 | published = mf2util.parse_datetime(published) # --> datetime.datetime 145 | ``` 146 | 147 | Authorship 148 | ---------- 149 | 150 | Use `mf2py.find_author` to determine an h-event's author name, url, and 151 | photo. Uses the [authorship 152 | algorithm](https://indiewebcamp.com/authorship) described on the 153 | IndieWebCamp wiki. 154 | 155 | Contributing 156 | ------------ 157 | 158 | If you find a bug or deficiency, feel free to file an issue, pull 159 | request, or just message me in the \#indiewebcamp channel on freenode. 160 | 161 | Changes 162 | ------- 163 | 164 | All notable changes to this project will be documented here. 165 | 166 | ### 0.5.2 - 2023-01-15 167 | 168 | - Bugfix: post-type-discovery should only return org if name and org properties are present. Thanks @snarfed! 169 | 170 | ### 0.5.1 - 2018-11-04 171 | 172 | - Add `follow` to `post_type_discovery()`. 173 | 174 | ### 0.5.0 - 2016-10-27 175 | 176 | - Fully implement location parsing based on https://indieweb.org/location#How_to_determine_the_location_of_a_microformat 177 | thanks to @snarfed 178 | 179 | ### 0.4.3 - 2016-08-20 180 | 181 | - representative_hcard now includes h-cards that are properties of 182 | other h-* entities, thanks to @angelogladding 183 | 184 | ### 0.4.2 - 2016-05-09 185 | 186 | - Added properties "dt-deleted", "u-logo", "u-featured" 187 | 188 | ### 0.4.1 - 2016-05-04 189 | 190 | - Minor bugfix: interpret was passing parameters in the wrong order 191 | when parsing nested reply contexts and comments, which meant (in 192 | practice) `want_json` was always false, and dates were included as 193 | strings rather than datetimes. 194 | 195 | ### 0.4.0 - 2016-04-23 196 | #### Added 197 | 198 | - Update authorship implementation (`find_author`) to support fetching 199 | a separate page to find the author's h-card. 200 | - Added a new optional parameter to all `interpret_*` methods called 201 | `fetch_mf2_func`. A good value for this is `lambda url: mf2py.parse(url=url)` 202 | 203 | ### 0.3.3 - 2016-04-07 204 | #### Changed 205 | 206 | - minor bugfixes to prevent throwing errors on bad mf2 input 207 | - when a value (e.g. "name") is expected to be simple and we get a 208 | dict instead 209 | - when a e-* value has "html" but not "value" 210 | 211 | ### 0.3.2 - 2016-03-01 212 | #### Changed 213 | 214 | - `interpret_feed` now skips rel=syndication when parsing syndication 215 | values for individual entries. This value should be empty for feeds, 216 | but if it isn't, it will almost always be wrong. 217 | 218 | ### 0.3.1 - 2016-02-17 219 | #### Changed 220 | 221 | - Added "poster" to the recognized URL properties of a video tag. 222 | 223 | ### 0.3.0 - 2016-02-17 224 | #### Changed 225 | 226 | - Added `base_href` parameter to all interpret methods. Now when 227 | content is normalized, it will take into account the base tag if 228 | it's given. 229 | - Added `audio`, `video`, and `source` tags to the list of tags that 230 | might contain URL attributes. 231 | 232 | ### 0.2.12 - 2016-02-15 233 | #### Added 234 | 235 | - Added "photo" to common URL properties. 236 | 237 | ### 0.2.11 - 2016-01-02 238 | #### Changed 239 | 240 | - `is_name_a_title` accepts bytestrings now, no longer throws an error 241 | if the input is not unicode. 242 | 243 | ### 0.2.10 - 2015-11-27 244 | #### Added 245 | 246 | - `representative_hcard()` implementation of 247 | http://microformats.org/wiki/representative-h-card-parsing. Search 248 | all h-cards on a page and find the one that represents the page's 249 | author/owner. 250 | 251 | ### 0.2.9 - 2015-10-28 252 | #### Changed 253 | 254 | - Guard against mf2 required fields being None to make it a little 255 | easier for third parties (in this case Bridgy) to write unit tests. 256 | 257 | ### 0.2.8 - 2015-10-28 258 | #### Added 259 | 260 | - `post_type_discovery()` implementation that takes an h-event or 261 | h-entry and returns a string defining the post type (e.g. "article", 262 | "note", "like", etc.) 263 | 264 | #### Changed 265 | 266 | - Consolidated modules into one flat file for simplicity 267 | - Renamed `parse_dt` to `parse_datetime` (old name still works for 268 | backcompat) 269 | - In python 3, use builtin timezone implementation instead of 270 | mf2util's custom implementation 271 | 272 | ### 0.2.7 - 2015-10-05 273 | #### Added 274 | 275 | - add parsing for comment, like, and repost h-cites nested inside an 276 | h-entry 277 | 278 | ### 0.2.6 - 2015-09-24 279 | #### Added 280 | 281 | - added property content-plain to preserve the e-content value 282 | 283 | ### 0.2.5 - 2015-09-14 284 | #### Changed 285 | 286 | - minor bugfix: interpret should pass want_json recursively when 287 | fetching reply contexts. 288 | 289 | ### 0.2.4 - 2015-09-14 290 | 291 | #### Added 292 | 293 | - interpret methods now have an optional want_json argument. If true, 294 | result will be pure json with no Python-only objects (i.e. datetimes) 295 | 296 | ### 0.2.3 - 2015-08-27 297 | 298 | #### Added 299 | 300 | - parse simple location name and url from events and entries \#\#\# 301 | Changed 302 | - accept complex-valued "url" properties and fallback to their "value" 303 | 304 | ### 0.2.1 - 2015-06-08 305 | 306 | #### Changed 307 | 308 | - more lenient parsing of content as either e-content or p-content 309 | 310 | ### 0.2.0 - 2015-06-08 311 | 312 | #### Added 313 | 314 | - parse nested h-cite comments as entries under the top-level entry 315 | - check for bookmark-of \#\#\# Changed 316 | - in-reply-to, repost-of, like-of all parse into a list of objects now 317 | instead of a list of urls 318 | 319 | ### 0.1.9 - 2015-04-01 320 | 321 | #### Added 322 | 323 | - Parse event invitations as type ['invite', 'reply']. 324 | - Parse the list of invitees. 325 | 326 | ### 0.1.5 - 2015-02-18 327 | 328 | #### Added 329 | 330 | - in-reply-to, like-of, and repost-of properties added to the 331 | interpret\_entry result. 332 | 333 | ### 0.1.4 - 2015-01-27 334 | 335 | #### Changed 336 | 337 | - Authorship algorithm was incorrectly using the first h-entry on a 338 | page, even when parsing an h-feed that has many. 339 | 340 | ### 0.1.3 - 2014-12-14 341 | 342 | #### Changed 343 | 344 | - RSVP replies are now classified as type 'rsvp' instead of 'reply' 345 | 346 | ### 0.1.2 - 2014-09-20 347 | 348 | #### Added 349 | 350 | - Utility methods for interpreting h-feeds that contain one or more 351 | entries. 352 | 353 | #### Changed 354 | 355 | - Handle parsing errors more gracefully. 356 | - Distinguish between explicit h-entry titles and auto-generated 357 | p-names (junk) when determining whether a post has a title 358 | 359 | ### 0.1.1 - 2014-06-21 360 | 361 | #### Added 362 | 363 | - Include "syndication" attribute for including syndication URLs (e.g. 364 | for de-duplicating received comments) 365 | - Convert URL attributes from relative paths to absolute URLs for 366 | displaying foreign content. 367 | 368 | ### 0.1.0 - 2014-05-11 369 | 370 | #### Added 371 | 372 | - Migrated code from Red Wind for reasoning about raw microformats2 373 | data into this library. 374 | - Methods for interpreting h-entry, h-event, and received comments (to 375 | decide whether they are replies, likes, reposts, etc.) 376 | - No-dependency method for parsing datetimes described in 377 | [http://microformats.org/wiki/value-class-pattern#Date_and_time_parsing](http://microformats.org/wiki/value-class-pattern#Date_and_time_parsing) 378 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # mf2util documentation build configuration file, created by 4 | # sphinx-quickstart on Mon May 12 08:02:11 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | sys.path.insert(0, os.path.abspath('../..')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.ifconfig', 35 | 'sphinx.ext.viewcode', 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix of source filenames. 42 | source_suffix = '.rst' 43 | 44 | # The encoding of source files. 45 | #source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = u'mf2util' 52 | copyright = u'2014, Kyle Mahan' 53 | 54 | # The version info for the project you're documenting, acts as replacement for 55 | # |version| and |release|, also used in various other places throughout the 56 | # built documents. 57 | # 58 | # The short X.Y version. 59 | version = '0.1.0' 60 | # The full version, including alpha/beta/rc tags. 61 | release = '0.1.0' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | #language = None 66 | 67 | # There are two options for replacing |today|: either, you set today to some 68 | # non-false value, then it is used: 69 | #today = '' 70 | # Else, today_fmt is used as the format for a strftime call. 71 | #today_fmt = '%B %d, %Y' 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | exclude_patterns = [] 76 | 77 | # The reST default role (used for this markup: `text`) to use for all 78 | # documents. 79 | #default_role = None 80 | 81 | # If true, '()' will be appended to :func: etc. cross-reference text. 82 | #add_function_parentheses = True 83 | 84 | # If true, the current module name will be prepended to all description 85 | # unit titles (such as .. function::). 86 | #add_module_names = True 87 | 88 | # If true, sectionauthor and moduleauthor directives will be shown in the 89 | # output. They are ignored by default. 90 | #show_authors = False 91 | 92 | # The name of the Pygments (syntax highlighting) style to use. 93 | pygments_style = 'sphinx' 94 | 95 | # A list of ignored prefixes for module index sorting. 96 | #modindex_common_prefix = [] 97 | 98 | # If true, keep warnings as "system message" paragraphs in the built documents. 99 | #keep_warnings = False 100 | 101 | 102 | # -- Options for HTML output ---------------------------------------------- 103 | 104 | # The theme to use for HTML and HTML Help pages. See the documentation for 105 | # a list of builtin themes. 106 | html_theme = 'default' 107 | 108 | # Theme options are theme-specific and customize the look and feel of a theme 109 | # further. For a list of options available for each theme, see the 110 | # documentation. 111 | #html_theme_options = {} 112 | 113 | # Add any paths that contain custom themes here, relative to this directory. 114 | #html_theme_path = [] 115 | 116 | # The name for this set of Sphinx documents. If None, it defaults to 117 | # "John Doe
19 |John Doe
20 |John Doe
21 |Nothing to see here. Move along.
10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/interpret/article_naive_datetime.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "properties": { 5 | "name": [ 6 | "\nMarkup For People Focused Mobile Communication \n" 7 | ], 8 | "published": [ 9 | "2014-04-30T12:11" 10 | ], 11 | "uid": [ 12 | "http://tantek.com/2014/120/b1/markup-people-focused-mobile-communication" 13 | ], 14 | "url": [ 15 | "http://tantek.com/2014/120/b1/markup-people-focused-mobile-communication" 16 | ], 17 | "updated": [ 18 | "2014-04-30T12:11" 19 | ], 20 | "content": [ 21 | { 22 | "html": "\n\nAll functionality on web pages and applications starts with markup. The previous post in this series, URLs For People Focused Mobile Communication, documented the various URL schemes for launching the communication apps shown in the mockups, as well as results of testing them on mobile devices. Those tests used minimal markup.\n
\n\nThis post documents and explains that markup, building up element by element from a simple hyperlink to the structure implied by this mockup:\n
\n\n
\nOr if you want, you may jump directly to the complete markup example.\n
\n\nA hyperlink provides a way for the user to navigate to other web pages. Using a URL scheme for a communication app, a hyperlink can start a message, resume a conversation, or start an audio/video call. Here's a simple hyperlink that uses the first URL scheme documented in the previous post, sms:
\n
<a href=\"sms:user@example.com\">txt message</a>\n
\n\nLive example: txt message\n
\n\nActivating that live example likely won't do much, as user@example.com
does not belong to anyone. Example.com
is a domain registered purely for the purpose of examples like this one. To make this hyperlink work, you'd have to use a registered AppleID email address, which would send a txt on iOS, and fallback to email via phone provider on Android.\n
\nI use the link text \"txt message\" to indicate its user-centered function: the action of creating a txt message, from one human to another.\n
\n\nContrast that with the mockup above (which I \"built\" using an iOS7 home screen folder), which uses the label \"Messages\", the name of the application it launches. \n
\n\nThis deliberate change from \"Messages\" (application) to \"txt message\" (action) reflects the larger purpose of this exercise: people-focused rather than app-focused communication. Subsequent labels follow a similar approach.\n
\n\nA simple text hyperlink is functional, yet does not provide the immediate association and recognition conveyed by the Messages icon in the mockup. There are two methods of providing an image hyperlink:\n
\n<img>
element inside the hyperlinkbackground-image
\nThe question of when to use markup for an image and when to use CSS is usually easily answered by the question: is the image meaningful content (like a photograph) or purely decorative (like a flourish)? Or by asking, is any meaning lost if the image is dropped?\n
\n\nThe Messages image is neither content nor decorative. It's a button, and it's also a standard iOS user interface element, which means it does convey meaning to those users, above and beyond any text label. Here's the minimum markup for an image hyperlink, with the link text moved into the alt attribute as a fallback:\n
\n<a href=\"sms:user@example.com\">\n <img src=\"ios7-messages-icon.png\" \n alt=\"txt message\"/>\n</a>\n
\n\n\nThere is a third option, as implied by the mockup, and that is to use both an image and a text label. That's a simple matter of moving the alt text outside the image:\n
\n<a href=\"sms:user@example.com\">\n <img src=\"ios7-messages-icon.png\" \n alt=\"\"/>\n txt message\n</a>\n
\n\nLive example:
\n\ntxt message\n
\nThe alt
attribute is left deliberately empty since putting anything there would not add to the usability of the link, and could in fact detract from it.\n
\nUnlike the mockup, the link text is next to (instead of underneath) the image, and is blue & underlined. These are all presentational aspects and will be addressed in the next post on CSS for People Focused Mobile Communication.\n
\n\nThe mockup also shows multiple communication buttons in a list laid out as a grid. We can assign meaning to the order of the buttons - the site owner's preferred order of communication methods. Thus we use an ordered list to convey that their order is significant. Here's a few image+text links wrapped in list items inside an ordered list:\n
\n<ol>\n <li><a href=\"sms:user@example.com\">\n <img src=\"ios7-messages-icon.png\" \n alt=\"\"/>\n txt message\n </a></li>\n <li><a href=\"fb-messenger://user-thread/4\">\n <img src=\"fb-messenger-icon.png\" \n alt=\"\"/>\n <abbr title=\"Facebook\">FB</abbr> message\n </a></li>\n <li><a href=\"aim:goim?screenname=tantekc&message=hi\">\n <img src=\"aim-icon.png\" \n alt=\"\"/>\n AIM chat\n </a></li>\n</ol>\n
\n\nNote the use of an <abbr>
element to abbreviate \"Facebook\" just to \"FB\" to shorten the overall \"FB message\" link text.\n
\nLive example: \n
\n\nJust as in the previous URLs post, the FB message link uses Zuck's ID, and the AIM chat link uses the same nickname I've had in the sidebar for a while.\n
\n\n\nThe mockup labels the entire grid \"Contact\" (also deliberately chosen as an action, rather than the \"Contacts\" application). This makes sense as a heading, and in the context of a home page, a second level heading:\n
\n\n<h2>Contact</h2>\n
\n\nNo need for a separate live example - the subheads above are all <h2>
elements. As is this one:
\nCombining the Contact heading with the previous ordered list, and adding the remaining buttons:\n
\n\n<h2>Contact</h2>\n<ol>\n <li><a href=\"sms:user@example.com\">\n <img src=\"ios7-messages-icon.png\" \n alt=\"\"/>\n txt message\n </a></li>\n <li><a href=\"fb-messenger://user-thread/4\">\n <img src=\"fb-messenger-icon.png\" \n alt=\"\"/>\n <abbr title=\"Facebook\">FB</abbr> message\n </a></li>\n <li><a href=\"aim:goim?screenname=tantekc&message=hi\">\n <img src=\"aim-icon.png\" \n alt=\"\"/>\n AIM chat\n </a></li>\n <li><a href=\"facetime:user@example.com\">\n <img src=\"facetime-icon.png\" \n alt=\"\"/>\n FaceTime call\n </a></li>\n <li><a href=\"skype:echo123?call\">\n <img src=\"skype-icon.png\" \n alt=\"\"/>\n Skype call\n </a></li>\n <li><a href=\"https://mobile.twitter.com/t/messages\">\n <img src=\"twitter-dm-icon.png\" \n alt=\"\"/>\n Twitter DM\n </a></li>\n</ol>\n
\n\n\nIn this final code example I've highlighted (using orange bold tags), the key pieces you need to change to your own identifiers on each service.\n
\n\n\nLive example once more, including heading:\n
\n\n\nI dropped the Google Hangouts icon since that application lacks support for any URL schemes (as noted in the previous post). Also I've re-ordered a bit from the mockup, having found that I prefer FaceTime over Skype. Pick your own from among the documented URL schemes, and order them to your preference.\n
\n\n\nAll the essential structure is there, yet it clearly needs some CSS. There's plenty to fix from inconsistent image sizes (all but the Messages & FaceTime icons are from Apple's iTunes store web pages), to blue underlined link text. And there's plenty to clean up to approach the look of the mockup: from the clustered center-aligned image+text button layout, to the grid layout of the buttons, to white text on the gray rounded corner ordered list background.
\n\nThat's all for the next post in this series.\n
\n\n", 23 | "value": "\n\nAll functionality on web pages and applications starts with markup. The previous post in this series, URLs For People Focused Mobile Communication, documented the various URL schemes for launching the communication apps shown in the mockups, as well as results of testing them on mobile devices. Those tests used minimal markup.\n\n\nThis post documents and explains that markup, building up element by element from a simple hyperlink to the structure implied by this mockup:\n\n\n\n\nOr if you want, you may jump directly to the complete markup example.\n\nA hyperlink\n\nA hyperlink provides a way for the user to navigate to other web pages. Using a URL scheme for a communication app, a hyperlink can start a message, resume a conversation, or start an audio/video call. Here's a simple hyperlink that uses the first URL scheme documented in the previous post, sms:\n\ntxt message\n\n\nLive example: txt message\n\n\nActivating that live example likely won't do much, as user@example.com does not belong to anyone. Example.com is a domain registered purely for the purpose of examples like this one. To make this hyperlink work, you'd have to use a registered AppleID email address, which would send a txt on iOS, and fallback to email via phone provider on Android.\n\nAction labels not app names\n\nI use the link text \"txt message\" to indicate its user-centered function: the action of creating a txt message, from one human to another.\n\n\nContrast that with the mockup above (which I \"built\" using an iOS7 home screen folder), which uses the label \"Messages\", the name of the application it launches. \n\n\nThis deliberate change from \"Messages\" (application) to \"txt message\" (action) reflects the larger purpose of this exercise: people-focused rather than app-focused communication. Subsequent labels follow a similar approach.\n\nAn image hyperlink\n\nA simple text hyperlink is functional, yet does not provide the immediate association and recognition conveyed by the Messages icon in the mockup. There are two methods of providing an image hyperlink:\n\n\nAnGive me crayons and I will draw a rocketship. Fact.
\n", 60 | "value": "\nEgg, Brooklyn\n\n\n\nGive me crayons and I will draw a rocketship. Fact.\n" 61 | } 62 | ], 63 | "name": [ 64 | "Ben Werdm\u00fcller\n\n\n\n\u00a0\n\n\n\n\n2014-09-19T14:16:45+00:00\n\n\n\n\nEgg, Brooklyn\n\n\n\nGive me crayons and I will draw a rocketship. Fact.\n\n\n\n\n\n\n\n 1 star\n 1 comment\n\n\n\n\n\n\n\n\n\n\n\n\n\nsorry to break it to you, that is a squid.\nKyle Mahan,\n Sep 20 2014\n on kylewm.com\n\n\n\n\n\n\n\n\n\n\nDavid Walker\n liked this post\n \nSep 20 2014 on facebook.com\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n $(document).ready(function () {\n\n $('#extrafield').html('');\n\n })\n \n\n\n\n Also on:\n flickr facebook" 65 | ], 66 | "photo": [ 67 | "Egg, Brooklyn", 68 | "http://werd.io/file/541c3accbed7ded9797e59bb/thumb.jpg" 69 | ], 70 | "published": [ 71 | "2014-09-19T14:16:45+0000" 72 | ], 73 | "syndication": [ 74 | "https://www.flickr.com/photos/benwerd/15264900606/", 75 | "https://facebook.com/10100928029673259" 76 | ], 77 | "url": [ 78 | "http://werd.io/2014/egg-brooklyn" 79 | ] 80 | }, 81 | "type": [ 82 | "h-entry" 83 | ] 84 | } 85 | ], 86 | "rels": { 87 | "apple-touch-icon": [ 88 | "http://werd.io/gfx/logos/apple-icon-57x57.png", 89 | "http://werd.io/gfx/logos/apple-icon-72x72.png", 90 | "http://werd.io/gfx/logos/apple-icon-114x114.png", 91 | "http://werd.io/gfx/logos/apple-icon-144x144.png", 92 | "http://werd.io/file/538d0a4cbed7de5111a1ad31/thumb.jpg" 93 | ], 94 | "apple-touch-icon-precomposed": [ 95 | "http://werd.io/file/538d0a4cbed7de5111a1ad31/thumb.jpg" 96 | ], 97 | "author": [ 98 | "http://werd.io/humans.txt" 99 | ], 100 | "authorization_endpoint": [ 101 | "https://indieauth.com/auth" 102 | ], 103 | "feed": [ 104 | "http://werd.io/content/all" 105 | ], 106 | "http://webmention.org/": [ 107 | "http://werd.io/webmention/" 108 | ], 109 | "hub": [ 110 | "http://benwerd.superfeedr.com/" 111 | ], 112 | "icon": [ 113 | "http://werd.io/file/538d0a4cbed7de5111a1ad31/thumb.jpg" 114 | ], 115 | "micropub": [ 116 | "http://werd.io/micropub/endpoint" 117 | ], 118 | "openid.delegate": [ 119 | "http://werd.io/" 120 | ], 121 | "openid.server": [ 122 | "https://indieauth.com/openid" 123 | ], 124 | "permalink": [ 125 | "http://werd.io/2014/egg-brooklyn" 126 | ], 127 | "schema.DC": [ 128 | "http://purl.org/dc/elements/1.1/" 129 | ], 130 | "shortcut": [ 131 | "http://werd.io/file/538d0a4cbed7de5111a1ad31/thumb.jpg" 132 | ], 133 | "stylesheet": [ 134 | "http://werd.io/external/bootstrap/assets/css/bootstrap.css", 135 | "http://werd.io/external/font-awesome/css/font-awesome.min.css", 136 | "http://werd.io/external/bootstrap/assets/css/bootstrap-responsive.css", 137 | "http://werd.io/css/default.css", 138 | "http://cdn.leafletjs.com/leaflet-0.5/leaflet.css", 139 | "http://werd.io/styles/site/", 140 | "http://werd.io/Themes/Cherwell/css/default.css", 141 | "http://werd.io/external/mediaelement/build/mediaelementplayer.css", 142 | "http://werd.io/external/summernote/dist/summernote.css", 143 | "http://werd.io/external/mention/recommended-styles.css" 144 | ], 145 | "syndication": [ 146 | "https://www.flickr.com/photos/benwerd/15264900606/", 147 | "https://facebook.com/10100928029673259" 148 | ], 149 | "token_endpoint": [ 150 | "http://werd.io/indieauth/token" 151 | ], 152 | "webmention": [ 153 | "http://werd.io/webmention/" 154 | ] 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /tests/interpret/article_non_ascii_content.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "type": [ 5 | "h-entry" 6 | ], 7 | "properties": { 8 | "name": [ 9 | "foo" 10 | ], 11 | "content": [ 12 | "Поч" 13 | ] 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tests/interpret/article_two_published_dates.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "properties": { 5 | "name": [ 6 | "Test Article with Two Published Dates" 7 | ], 8 | "published": [ 9 | "2014-04-30T12:11:00-0800", 10 | "2014-04-30T12:11:00-0800" 11 | ], 12 | "content": [ 13 | { 14 | "html": "", 15 | "value": "" 16 | } 17 | ], 18 | "author": [ 19 | { 20 | "value": "", 21 | "properties": { 22 | "name": [ 23 | "Aaron Parecki" 24 | ], 25 | "url": [ 26 | "http://aaronparecki.com/" 27 | ] 28 | }, 29 | "type": [ 30 | "h-card" 31 | ] 32 | } 33 | ] 34 | }, 35 | "type": [ 36 | "h-entry", 37 | "h-as-article" 38 | ] 39 | } 40 | ], 41 | "rels": { 42 | }, 43 | "alternates": [ 44 | { 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /tests/interpret/follow.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "properties": { 5 | "name": [ 6 | "I follow thee" 7 | ], 8 | "published": [ 9 | "2014-05-05T10:10:53-07:00" 10 | ], 11 | "author": [ 12 | { 13 | "value": "Ryan Barrett", 14 | "properties": { 15 | "name": [ 16 | "Ryan Barrett" 17 | ], 18 | "photo": [ 19 | "https://secure.gravatar.com/avatar/947b5f3f323da0ef785b6f02d9c265d6?s=96&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&r=G" 20 | ], 21 | "url": [ 22 | "http://snarfed.org/" 23 | ] 24 | }, 25 | "type": [ 26 | "h-card" 27 | ] 28 | } 29 | ], 30 | "follow-of": [ 31 | "http://other/person" 32 | ], 33 | "url": [ 34 | "https://snarfed.org/2014-05-05_follow" 35 | ], 36 | "content": [ 37 | { 38 | "html": "I follow thee", 39 | "value": "I follow thee" 40 | } 41 | ] 42 | }, 43 | "type": [ 44 | "h-entry" 45 | ] 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /tests/interpret/hwc-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "alternates": [ 3 | { 4 | "type": "application/rss+xml", 5 | "url": "http://werd.io/2014/homebrew-website-club-4?_t=rss", 6 | "rel": "feed" 7 | }, 8 | { 9 | "type": "application/rss+xml", 10 | "url": "http://werd.io/content/all?_t=rss", 11 | "rel": "feed" 12 | } 13 | ], 14 | "items": [ 15 | { 16 | "type": [ 17 | "h-card" 18 | ], 19 | "properties": { 20 | "photo": [ 21 | "http://werd.io/file/52be39babed7deb701668dd8" 22 | ], 23 | "name": [ 24 | "Ben Werdm\u00fcller" 25 | ], 26 | "url": [ 27 | "http://werd.io/profile/benwerd", 28 | "http://werd.io/profile/benwerd" 29 | ] 30 | } 31 | }, 32 | { 33 | "type": [ 34 | "h-event" 35 | ], 36 | "properties": { 37 | "published": [ 38 | "2014-05-05T16:34:30+00:00" 39 | ], 40 | "end": [ 41 | "2014-05-07T19:30:00+00:00" 42 | ], 43 | "start": [ 44 | "2014-05-07T18:30:00+00:00" 45 | ], 46 | "content": [ 47 | { 48 | "value": "\n \n \n Homebrew Website Club\n \n \n \n Discuss progress; meet up; make new friends. \n \n Location: Mozilla SF, 1st floor, 2 Harrison st. (at Embarcadero), San Francisco, CA \n \n \n Time: May 7th, 6:30pm\n \n \n Ends: May 7th, 7:30pm\n \n \n\n \nAre you building your own website? Indie reader? Personal publishing web app? Or some other digital magic-cloud proxy? If so, come on by and join a gathering of people with like-minded interests. Bring your friends that want to start a personal web site. Exchange information, swap ideas, talk shop, help work on a project ...\n\nSee the Homebrew Website Club Newsletter Volume 1 Issue 1 for a description of the first meeting.\n\nOriginally posted on indiewebcamp.com. There's also a companion event at Mozilla Portland.\n\nHere's the Facebook event, if you prefer.\n ", 49 | "html": "\n\n Discuss progress; meet up; make new friends.
\n\n Location: Mozilla SF, 1st floor, 2 Harrison st. (at Embarcadero), San Francisco, CA \n
\n\n Time: \n
\n\n Ends: \n
\nAre you building your own website? Indie reader? Personal publishing web app? Or some other digital magic-cloud proxy? If so, come on by and join a gathering of people with like-minded interests. Bring your friends that want to start a personal web site. Exchange information, swap ideas, talk shop, help work on a project ...
\n\nSee the Homebrew Website Club Newsletter Volume 1 Issue 1 for a description of the first meeting.
\n\nOriginally posted on indiewebcamp.com. There's also a companion event at Mozilla Portland.
\n\n\nbig thing missing from my #indieweb experience is being able to see other people\u2019s comments before replying. tough problem to solve.
\n \n ", 74 | "value": "\n big thing missing from my #indieweb experience is being able to see other people\u2019s comments before replying. tough problem to solve.\n \n " 75 | } 76 | ], 77 | "like": [ 78 | { 79 | "properties": { 80 | "author": [ 81 | { 82 | "properties": { 83 | "name": [ 84 | "" 85 | ], 86 | "photo": [ 87 | "https://kylewm.com/imageproxy?url=https%3A%2F%2Ftwitter.com%2Fbenwerd%2Fprofile_image%3Fsize%3Doriginal&size=48&sig=fde7ce5635f5ea132a2545ff5c7d3d33" 88 | ], 89 | "url": [ 90 | "https://twitter.com/benwerd" 91 | ] 92 | }, 93 | "type": [ 94 | "h-card" 95 | ], 96 | "value": "" 97 | } 98 | ], 99 | "name": [ 100 | "" 101 | ], 102 | "url": [ 103 | "https://twitter.com/kylewmahan/status/651186266701107200" 104 | ] 105 | }, 106 | "type": [ 107 | "h-cite" 108 | ], 109 | "value": "https://twitter.com/kylewmahan/status/651186266701107200" 110 | } 111 | ], 112 | "name": [ 113 | "big thing missing from my #indieweb experience is being able to see other people\u2019s comments before replying. tough problem to solve." 114 | ], 115 | "published": [ 116 | "2015-10-05T17:04:35-07:00" 117 | ], 118 | "shortlink": [ 119 | "https://kylewm.com/n/4d_1" 120 | ], 121 | "syndication": [ 122 | "https://twitter.com/kylewmahan/status/651186266701107200" 123 | ], 124 | "uid": [ 125 | "https://kylewm.com/2015/10/big-thing-missing-from-my-indieweb-experience-is" 126 | ], 127 | "url": [ 128 | "https://kylewm.com/2015/10/big-thing-missing-from-my-indieweb-experience-is", 129 | "https://kylewm.com/n/4d_1" 130 | ] 131 | }, 132 | "type": [ 133 | "h-entry" 134 | ] 135 | } 136 | ], 137 | "rel-urls": { 138 | "https://indieauth.com/auth": { 139 | "rels": [ 140 | "authorization_endpoint" 141 | ], 142 | "text": "" 143 | }, 144 | "https://indieauth.com/openid": { 145 | "rels": [ 146 | "openid.server" 147 | ], 148 | "text": "" 149 | }, 150 | "https://keybase.io/kylewm/key.asc": { 151 | "rels": [ 152 | "pgpkey" 153 | ], 154 | "text": "", 155 | "type": "application/pgp-keys" 156 | }, 157 | "https://kylewm.com": { 158 | "rels": [ 159 | "openid.delegate" 160 | ], 161 | "text": "" 162 | }, 163 | "https://kylewm.com/_themes/boxy/style.css?version=2015-06-25": { 164 | "rels": [ 165 | "stylesheet" 166 | ], 167 | "text": "" 168 | }, 169 | "https://kylewm.com/everything": { 170 | "rels": [ 171 | "feed" 172 | ], 173 | "text": "", 174 | "type": "text/html" 175 | }, 176 | "https://kylewm.com/foaf.rdf": { 177 | "rels": [ 178 | "meta" 179 | ], 180 | "text": "", 181 | "title": "Contact", 182 | "type": "application/rdf+xml" 183 | }, 184 | "https://kylewm.com/imageproxy?url=%2Fstatic%2Fimg%2Fusers%2Fkyle.jpg&size=114&sig=b57d1f32eb45988e4b1e7f5a53afd072": { 185 | "rels": [ 186 | "apple-touch-icon" 187 | ], 188 | "text": "" 189 | }, 190 | "https://kylewm.com/imageproxy?url=%2Fstatic%2Fimg%2Fusers%2Fkyle.jpg&size=152&sig=cb27d9fb6b285da683bb869ba974ee53": { 191 | "rels": [ 192 | "apple-touch-icon" 193 | ], 194 | "text": "" 195 | }, 196 | "https://kylewm.com/imageproxy?url=%2Fstatic%2Fimg%2Fusers%2Fkyle.jpg&size=60&sig=deebbb906749f01b98a4291e7b2cff7d": { 197 | "rels": [ 198 | "apple-touch-icon" 199 | ], 200 | "text": "" 201 | }, 202 | "https://kylewm.com/imageproxy?url=%2Fstatic%2Fimg%2Fusers%2Fkyle.jpg&size=76&sig=7606f9576a5cdbfeac9fe773b19d5bf1": { 203 | "rels": [ 204 | "apple-touch-icon" 205 | ], 206 | "text": "" 207 | }, 208 | "https://kylewm.com/micropub": { 209 | "rels": [ 210 | "micropub" 211 | ], 212 | "text": "" 213 | }, 214 | "https://kylewm.com/static/img/users/kyle.jpg": { 215 | "rels": [ 216 | "shortcut", 217 | "icon", 218 | "apple-touch-icon" 219 | ], 220 | "text": "" 221 | }, 222 | "https://kylewm.com/static/pygments.css": { 223 | "rels": [ 224 | "stylesheet" 225 | ], 226 | "text": "" 227 | }, 228 | "https://kylewm.com/token": { 229 | "rels": [ 230 | "token_endpoint" 231 | ], 232 | "text": "" 233 | }, 234 | "https://kylewm.com/webmention": { 235 | "rels": [ 236 | "webmention" 237 | ], 238 | "text": "" 239 | }, 240 | "https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css": { 241 | "rels": [ 242 | "stylesheet" 243 | ], 244 | "text": "" 245 | }, 246 | "https://twitter.com/kylewmahan/status/651186266701107200": { 247 | "rels": [ 248 | "syndication" 249 | ], 250 | "text": "" 251 | }, 252 | "https://webmention.io/webmention?forward=https://kylewm.com/webmention": { 253 | "rels": [ 254 | "pingback" 255 | ], 256 | "text": "" 257 | }, 258 | "ni:///sha-256;LXQj307VecrQ7BPxkMhuI-rM14CktmXjy16DjI0MMAE?ct=application/x-x509-user-cert": { 259 | "rels": [ 260 | "me" 261 | ], 262 | "text": "" 263 | } 264 | }, 265 | "rels": { 266 | "apple-touch-icon": [ 267 | "https://kylewm.com/static/img/users/kyle.jpg", 268 | "https://kylewm.com/imageproxy?url=%2Fstatic%2Fimg%2Fusers%2Fkyle.jpg&size=60&sig=deebbb906749f01b98a4291e7b2cff7d", 269 | "https://kylewm.com/imageproxy?url=%2Fstatic%2Fimg%2Fusers%2Fkyle.jpg&size=76&sig=7606f9576a5cdbfeac9fe773b19d5bf1", 270 | "https://kylewm.com/imageproxy?url=%2Fstatic%2Fimg%2Fusers%2Fkyle.jpg&size=114&sig=b57d1f32eb45988e4b1e7f5a53afd072", 271 | "https://kylewm.com/imageproxy?url=%2Fstatic%2Fimg%2Fusers%2Fkyle.jpg&size=152&sig=cb27d9fb6b285da683bb869ba974ee53" 272 | ], 273 | "authorization_endpoint": [ 274 | "https://indieauth.com/auth" 275 | ], 276 | "feed": [ 277 | "https://kylewm.com/everything" 278 | ], 279 | "icon": [ 280 | "https://kylewm.com/static/img/users/kyle.jpg" 281 | ], 282 | "me": [ 283 | "ni:///sha-256;LXQj307VecrQ7BPxkMhuI-rM14CktmXjy16DjI0MMAE?ct=application/x-x509-user-cert" 284 | ], 285 | "meta": [ 286 | "https://kylewm.com/foaf.rdf" 287 | ], 288 | "micropub": [ 289 | "https://kylewm.com/micropub" 290 | ], 291 | "openid.delegate": [ 292 | "https://kylewm.com" 293 | ], 294 | "openid.server": [ 295 | "https://indieauth.com/openid" 296 | ], 297 | "pgpkey": [ 298 | "https://keybase.io/kylewm/key.asc" 299 | ], 300 | "pingback": [ 301 | "https://webmention.io/webmention?forward=https://kylewm.com/webmention" 302 | ], 303 | "shortcut": [ 304 | "https://kylewm.com/static/img/users/kyle.jpg" 305 | ], 306 | "stylesheet": [ 307 | "https://kylewm.com/_themes/boxy/style.css?version=2015-06-25", 308 | "https://kylewm.com/static/pygments.css", 309 | "https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" 310 | ], 311 | "syndication": [ 312 | "https://twitter.com/kylewmahan/status/651186266701107200" 313 | ], 314 | "token_endpoint": [ 315 | "https://kylewm.com/token" 316 | ], 317 | "webmention": [ 318 | "https://kylewm.com/webmention" 319 | ] 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /tests/interpret/relative_paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "rels": {}, 3 | "alternates": [], 4 | "items": [{ 5 | "type": ["h-entry"], 6 | "properties": { 7 | "name": ["Example title"], 8 | "content": [{ 9 | "value": "This is an example document", 10 | "html": "This is anRSVPs yes to
\nRSVPs yes to
\n", 75 | "value": "RSVPs yes to Homebrew Website Club\n" 76 | } 77 | ], 78 | "rsvp": [ 79 | "yes" 80 | ] 81 | }, 82 | "type": [ 83 | "h-entry", 84 | "h-as-article" 85 | ] 86 | } 87 | ], 88 | "rels": { 89 | "nofollow": [ 90 | "https://snarfed.org/2014-05-05_homebrew-website-club-3#respond" 91 | ], 92 | "generator": [ 93 | "http://wordpress.org/" 94 | ], 95 | "prev": [ 96 | "https://snarfed.org/2014-05-05_9325" 97 | ], 98 | "stylesheet": [ 99 | "https://snarfed.org/w/wp-content/plugins/nextgen-gallery/products/photocrati_nextgen/modules/nextgen_gallery_display/static/nextgen_gallery_related_images.css?ver=3.9.1", 100 | "https://snarfed.org/w/wp-content/plugins/jetpack/modules/subscriptions/subscriptions.css?ver=3.9.1", 101 | "https://snarfed.org/w/wp-content/plugins/jetpack/modules/widgets/widgets.css?ver=20121003", 102 | "https://snarfed.org/w/wp-content/plugins/jetpack/modules/carousel/jetpack-carousel.css?ver=20120629", 103 | "https://snarfed.org/w/wp-content/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.css?ver=2012-09-21", 104 | "https://snarfed.org/w/wp-includes/js/mediaelement/mediaelementplayer.min.css?ver=2.13.0", 105 | "https://snarfed.org/w/wp-includes/js/mediaelement/wp-mediaelement.css?ver=3.9.1", 106 | "https://snarfed.org/w/wp-content/themes/snarfed-ryu/style.css?ver=3.9.1", 107 | "https://fonts.googleapis.com/css?family=Lato:100,300,400,700,900,100italic,300italic,400italic,700italic,900italic", 108 | "https://fonts.googleapis.com/css?family=Playfair+Display:400,700,900,400italic,700italic,900italic&subset=latin,latin-ext" 109 | ], 110 | "canonical": [ 111 | "https://snarfed.org/2014-05-05_homebrew-website-club-3" 112 | ], 113 | "tag": [ 114 | "https://snarfed.org/category/indieweb_rsvp" 115 | ], 116 | "webmention": [ 117 | "https://snarfed.org/w/?webmention=endpoint" 118 | ], 119 | "shortcut": [ 120 | "https://snarfed.org/ryan_profile_square_thumb.jpg" 121 | ], 122 | "openid2.local_id": [ 123 | "http://www.google.com/profiles/heaven" 124 | ], 125 | "pingback": [ 126 | "https://snarfed.org/w/xmlrpc.php" 127 | ], 128 | "http://webmention.org/": [ 129 | "https://snarfed.org/w/?webmention=endpoint" 130 | ], 131 | "apple-touch-icon-precomposed": [ 132 | "https://snarfed.org/ryan_profile_square_thumb.jpg" 133 | ], 134 | "shortlink": [ 135 | "http://wp.me/p3EDAq-2qr" 136 | ], 137 | "author": [ 138 | "http://snarfed.org/" 139 | ], 140 | "publisher": [ 141 | "https://plus.google.com/103651231634018158746" 142 | ], 143 | "key": [ 144 | "https://snarfed.org/pubkey.txt" 145 | ], 146 | "designer": [ 147 | "http://theme.wordpress.com/" 148 | ], 149 | "icon": [ 150 | "https://snarfed.org/ryan_profile_square_thumb.jpg" 151 | ], 152 | "bookmark": [ 153 | "https://snarfed.org/2014-05-05_homebrew-website-club-3" 154 | ], 155 | "category": [ 156 | "https://snarfed.org/category/indieweb_rsvp" 157 | ], 158 | "home": [ 159 | "https://snarfed.org/", 160 | "https://snarfed.org/" 161 | ], 162 | "me": [ 163 | "mailto:public@ryanb.org", 164 | "https://twitter.com/schnarfed", 165 | "https://www.facebook.com/snarfed.org", 166 | "https://plus.google.com/+RyanBarrett", 167 | "https://github.com/snarfed" 168 | ], 169 | "openid2.provider": [ 170 | "https://www.google.com/accounts/o8/ud?source=profiles" 171 | ], 172 | "profile": [ 173 | "http://gmpg.org/xfn/11" 174 | ], 175 | "next": [ 176 | "https://snarfed.org/2014-05-05_i-still-think-automattic-is-undervalued-at-1-16bn-httprecode-net20140505wordpress-parent-automattic-has-raised-160-million-now-valued-at-1-16-billion-post-money" 177 | ] 178 | }, 179 | "alternates": [ 180 | { 181 | "url": "https://snarfed.org/feed", 182 | "type": "application/rss+xml" 183 | }, 184 | { 185 | "url": "https://snarfed.org/comments/feed", 186 | "type": "application/rss+xml" 187 | }, 188 | { 189 | "url": "https://snarfed.org/2014-05-05_homebrew-website-club-3/feed", 190 | "type": "application/rss+xml" 191 | } 192 | ] 193 | } -------------------------------------------------------------------------------- /tests/interpret/reply_u-in-reply-to.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "children": [ 5 | { 6 | "value": "\n\nI'm Ryan Barrett.\nI live, work, and play in\n San FranciscoCalifornia.\nI code, write, and post pictures here.\n\n\n\n\npublic@ryanb.org\npublic PGP key\n\n\n\n \n \n Home\n \n \n \n \n Search\n \n \n \n \n Archives\n \n \n \n \n Twitter\n \n \n \n \n Facebook\n \n \n \n \n Google+\n \n \n \n \n GitHub\n \n \n \n \n RSS Feed\n \n \n\n", 7 | "properties": { 8 | "name": [ 9 | "Ryan Barrett" 10 | ], 11 | "photo": [ 12 | "https://snarfed.org/ryan_profile_square_thumb.jpg" 13 | ], 14 | "url": [ 15 | "https://snarfed.org/" 16 | ], 17 | "key": [ 18 | "https://snarfed.org/pubkey.txt" 19 | ], 20 | "region": [ 21 | "California" 22 | ], 23 | "locality": [ 24 | "San Francisco" 25 | ], 26 | "email": [ 27 | "mailto:public@ryanb.org" 28 | ] 29 | }, 30 | "type": [ 31 | "h-card" 32 | ] 33 | } 34 | ], 35 | "properties": { 36 | "name": [ 37 | "Re: Display likes in a facepile", 38 | "Re: Display likes in a facepile" 39 | ], 40 | "published": [ 41 | "2014-03-09T22:48:22-07:00" 42 | ], 43 | "author": [ 44 | { 45 | "value": " Ryan Barrett", 46 | "properties": { 47 | "name": [ 48 | "Ryan Barrett" 49 | ], 50 | "photo": [ 51 | "https://secure.gravatar.com/avatar/947b5f3f323da0ef785b6f02d9c265d6?s=96&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&r=G" 52 | ], 53 | "url": [ 54 | "http://snarfed.org/" 55 | ] 56 | }, 57 | "type": [ 58 | "h-card" 59 | ] 60 | } 61 | ], 62 | "in-reply-to": [ 63 | "https://willnorris.com/2014/03/display-likes-in-a-facepile" 64 | ], 65 | "url": [ 66 | "https://snarfed.org/2014-03-09_re-display-likes-in-a-facepile" 67 | ], 68 | "content": [ 69 | { 70 | "html": "\n\t\t\toh man, so cool! thanks for doing this. can\u2019t wait to try it myself!
\n\n\n
oh man, so cool! thanks for doing this. can\u2019t wait to try it myself!
\n\n\n", 75 | "value": "oh man, so cool! thanks for doing this. can\u2019t wait to try it myself!\n\n\n" 76 | } 77 | ] 78 | }, 79 | "type": [ 80 | "h-entry", 81 | "h-as-article" 82 | ] 83 | } 84 | ], 85 | "rels": { 86 | "nofollow": [ 87 | "https://snarfed.org/2014-03-09_re-display-likes-in-a-facepile#respond" 88 | ], 89 | "generator": [ 90 | "http://wordpress.org/" 91 | ], 92 | "prev": [ 93 | "https://snarfed.org/2014-03-09_re-oh-in-duboce-park-theyre-entering-the-medical-tricorder-s" 94 | ], 95 | "stylesheet": [ 96 | "https://snarfed.org/w/wp-content/plugins/nextgen-gallery/products/photocrati_nextgen/modules/nextgen_gallery_display/static/nextgen_gallery_related_images.css?ver=3.9.1", 97 | "https://snarfed.org/w/wp-content/plugins/jetpack/modules/subscriptions/subscriptions.css?ver=3.9.1", 98 | "https://snarfed.org/w/wp-content/plugins/jetpack/modules/widgets/widgets.css?ver=20121003", 99 | "https://snarfed.org/w/wp-content/plugins/jetpack/modules/carousel/jetpack-carousel.css?ver=20120629", 100 | "https://snarfed.org/w/wp-content/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery.css?ver=2012-09-21", 101 | "https://snarfed.org/w/wp-includes/js/mediaelement/mediaelementplayer.min.css?ver=2.13.0", 102 | "https://snarfed.org/w/wp-includes/js/mediaelement/wp-mediaelement.css?ver=3.9.1", 103 | "https://snarfed.org/w/wp-content/themes/snarfed-ryu/style.css?ver=3.9.1", 104 | "https://fonts.googleapis.com/css?family=Lato:100,300,400,700,900,100italic,300italic,400italic,700italic,900italic", 105 | "https://fonts.googleapis.com/css?family=Playfair+Display:400,700,900,400italic,700italic,900italic&subset=latin,latin-ext" 106 | ], 107 | "canonical": [ 108 | "https://snarfed.org/2014-03-09_re-display-likes-in-a-facepile" 109 | ], 110 | "tag": [ 111 | "https://snarfed.org/category/indieweb" 112 | ], 113 | "webmention": [ 114 | "https://snarfed.org/w/?webmention=endpoint" 115 | ], 116 | "shortcut": [ 117 | "https://snarfed.org/ryan_profile_square_thumb.jpg" 118 | ], 119 | "openid2.local_id": [ 120 | "http://www.google.com/profiles/heaven" 121 | ], 122 | "pingback": [ 123 | "https://snarfed.org/w/xmlrpc.php" 124 | ], 125 | "http://webmention.org/": [ 126 | "https://snarfed.org/w/?webmention=endpoint" 127 | ], 128 | "apple-touch-icon-precomposed": [ 129 | "https://snarfed.org/ryan_profile_square_thumb.jpg" 130 | ], 131 | "shortlink": [ 132 | "http://wp.me/p3EDAq-2ak" 133 | ], 134 | "author": [ 135 | "http://snarfed.org/" 136 | ], 137 | "publisher": [ 138 | "https://plus.google.com/103651231634018158746" 139 | ], 140 | "key": [ 141 | "https://snarfed.org/pubkey.txt" 142 | ], 143 | "designer": [ 144 | "http://theme.wordpress.com/" 145 | ], 146 | "icon": [ 147 | "https://snarfed.org/ryan_profile_square_thumb.jpg" 148 | ], 149 | "bookmark": [ 150 | "https://snarfed.org/2014-03-09_re-display-likes-in-a-facepile" 151 | ], 152 | "category": [ 153 | "https://snarfed.org/category/indieweb" 154 | ], 155 | "home": [ 156 | "https://snarfed.org/", 157 | "https://snarfed.org/" 158 | ], 159 | "me": [ 160 | "mailto:public@ryanb.org", 161 | "https://twitter.com/schnarfed", 162 | "https://www.facebook.com/snarfed.org", 163 | "https://plus.google.com/+RyanBarrett", 164 | "https://github.com/snarfed" 165 | ], 166 | "openid2.provider": [ 167 | "https://www.google.com/accounts/o8/ud?source=profiles" 168 | ], 169 | "profile": [ 170 | "http://gmpg.org/xfn/11" 171 | ], 172 | "next": [ 173 | "https://snarfed.org/2014-03-10_re-joining-the-indie-web-my-motivation" 174 | ] 175 | }, 176 | "alternates": [ 177 | { 178 | "url": "https://snarfed.org/feed", 179 | "type": "application/rss+xml" 180 | }, 181 | { 182 | "url": "https://snarfed.org/comments/feed", 183 | "type": "application/rss+xml" 184 | }, 185 | { 186 | "url": "https://snarfed.org/2014-03-09_re-display-likes-in-a-facepile/feed", 187 | "type": "application/rss+xml" 188 | } 189 | ] 190 | } -------------------------------------------------------------------------------- /tests/interpret/unusual_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "properties": { 5 | "name": [ 6 | { 7 | "properties": { 8 | "name": ["This is wrong"] 9 | }, 10 | "value": "Rocky Raccoon", 11 | "type": ["h-card"] 12 | }, 13 | "This is also wrong" 14 | ], 15 | "url": [ 16 | { 17 | "properties": { 18 | "url": ["This is probably wrong"] 19 | }, 20 | "value": "https://foo.bar/", 21 | "type": ["h-event"] 22 | }, 23 | "This is wrong too" 24 | ], 25 | "uid": ["https://foo.bar/"] 26 | }, 27 | "type": [ 28 | "h-entry" 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /tests/posttype/hcard_no_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "type": [ 5 | "h-card" 6 | ], 7 | "properties": { 8 | "url": [ 9 | "https://tmichellemoore.com/" 10 | ], 11 | "uid": [ 12 | "https://tmichellemoore.com/" 13 | ], 14 | "photo": [ 15 | "https://tmichellemoore.com/pic.jpg" 16 | ] 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tests/posttype/hcard_org.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "type": [ 5 | "h-card" 6 | ], 7 | "properties": { 8 | "url": [ 9 | "https://tmichellemoore.com/" 10 | ], 11 | "uid": [ 12 | "https://tmichellemoore.com/" 13 | ], 14 | "name": [ 15 | "Foo Foundation" 16 | ], 17 | "org": [ 18 | "Foo Foundation" 19 | ] 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tests/posttype/only_html_content.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "properties": { 5 | "content": [ 6 | { 7 | "html": "some