/*************************************/
/*  Provides link decoration services.

    To add new linkicon associations (i.e., to assign a new or existing icon to
    a new category of links), modify one of the arrays in the latter part of
    this file (either Links.targetLinkTypes, for assigning a linkicon to links
    by domain / URL pattern, or Links.fileLinkTypes, for assigning a linkicon to
    links by file extension).

    NOTE: this implementation is obsolete as of 2022-02-21.
    It is provided as a historical reference, and inspiration for sites which
    have lightweight link-icon needs.

    links.js has been superseded by a data-attribute+CSS approach done at compile-time.
    While more complex, this has several advantages:
    it is much more scalable, minimizes runtime overhead, rules can be more powerful,
    and the rules can be tested to detect conflicts or failures to match.

    For the history & design rationale, see <https://gwern.net/design-graveyard#link-icon-css-regexps>.
    For the current implementation, see </static/build/LinkIcon.hs>, configured by
    </static/build/Config/LinkIcon.hs>.
    To see the current suite of link-icons (which doubles as the integration/end-to-end test),
    see <https://gwern.net/lorem-links#link-icons>.
 */
Links = {
    /**********************/
    /*  Core functionality.
     */

    /*  Applies suitable linkicon classes to all links within the given
        container.
     */
    //  Called by: rewrite.js
    decorateLinksWithin: (doc) => {
        /*  Track which graphical linkicons we’re making use of in this
            container (so we can add them to the style block later).
         */
        let newLinkIcons = [ ];

        doc.querySelectorAll("a").forEach(link => {
            if (link.classList.contains("icon-not"))
                return;

            let iconInfo = Links.linkIconInfo(link);
            if (   iconInfo.type
                && iconInfo.icon) {
                link.dataset.linkIconType = iconInfo.type;
                link.dataset.linkIcon = iconInfo.icon;

                if (iconInfo.type == "svg")
                    newLinkIcons.push(iconInfo.icon);
            }
        });

        /*  If we’re using any graphical linkicons in this container, add them
            to the style block now.
         */
        if (newLinkIcons > [])
            Links.addGraphicalLinkIcons(newLinkIcons);
    },

    /*  All the graphical linkicons used on the page so far. Each entry in this
        array is a linkicon name. (See the entries of type ‘svg’ in the
        Links.fileLinkTypes and Links.targetLinkTypes arrays, below, for what
        values these linkicon names can have.
     */
    graphicalLinkIcons: [ ],

    /*  The style block that maps SVG linkicon names (which are associated with
        link elements by means of the `data-link-icon` attribute) with SVG icon
        file URLs, by means of a CSS variable (`--link-icon-url`) scoped to
        links with the icon name as the value of their `data-link-icon`
        attribute. In other words, the style block contains a series of lines
        such as the following (the example is for an SVG linkicon with the name
        ‘worddoc’):

        a[data-link-icon='worddoc'] { --link-icon-url: url('/static/img/icon/worddoc.svg'); }

        And thus for every SVG linkicon used on the page.

        (Note that this would not be necessary if the CSS3 `attr()` function
         were usable for all properties (i.e. https://caniuse.com/css3-attr );
         in such a case we could do for SVG linkicons what we already do for
         textual linkicons, i.e. refer directly to the `data-link-icon`
         attribute in the CSS. Alas, currently there is no browser support for
         this feature...)
     */
    graphicalLinkIconsStyleBlock: null,

    /*  Adds the given graphical linkicons to the style block. The newLinkIcons
        argument is an array of SVG linkicon names. (This function also injects
        the style block, if not already present.)
     */
    //  Called by: Links.decorateLinksWithin
    addGraphicalLinkIcons: (newLinkIcons) => {
        /*  Only do anything to the style block if we have actually added any
            linkicons (i.e. do a union of the existing linkicon array with the
            new addition, and check for a changed count); otherwise, no need
            to touch the style block and cause unnecessary re-rendering.
         */
        let oldCount = Links.graphicalLinkIcons.length;
        Links.graphicalLinkIcons = [...new Set([...(Links.graphicalLinkIcons), ...newLinkIcons])];
        if (Links.graphicalLinkIcons.length == oldCount)
            return;

        //  Inject the style block, if need be.
        if (Links.graphicalLinkIconsStyleBlock == null) {
            document.body.insertAdjacentHTML("beforeend", `<style id="graphical-link-icons"></style>`);
            Links.graphicalLinkIconsStyleBlock = document.querySelector("#graphical-link-icons");
        }

        /*  Generate the CSS for all the linkicons, and inject it into the style
            block.
         */
        Links.graphicalLinkIconsStyleBlock.innerHTML = Links.graphicalLinkIcons.map(icon =>
            `a[data-link-icon='${icon}'] { --link-icon-url: url('/static/img/icon/${icon}.svg'); }`
        ).join("\n");
    },

    /*  Returns an info object that specifies linkicon and linkicon type for
        the given link. The info object has fields ‘type’ and ‘icon’, both of
        which have string values. (See the Links.fileLinkTypes and
        Links.targetLinkTypes arrays for details on what the values of these
        fields can be, for what sorts of links.)

        Note that this function first checks whether the link is matched by one
        of the defined target type patterns (see the Links.targetLinkTypes
        array). Only if the link does not match any of those patterns does this
        function then check whether the link is to one of the defined file types
        (see the Links.fileLinkTypes array). (See explanation of the gwern.net
        linkicon philosophy at https://gwern.net/lorem#link-icons for more
        information on linkicon precedence.)
     */
    //  Called by: Links.decorateLinksWithin
    linkIconInfo: (link) => {
        let linkTargetType = Links.linkTargetType(link);
        if (linkTargetType)
            return linkTargetType;

        let linkFileType = Links.linkFileType(link);
        if (linkFileType)
            return linkFileType;

        return { };
    },

    /*********************************************/
    /*  Icons for links to certain sorts of files.
     */

    /*  Returns true iff the given link points to a file with the given
        extension (if `ext` is a string) or any of the given extensions (if
        `ext` is an array of strings). (In other words, returns true if the
        link’s pathname ends with a period followed by the given string / one
        of the given strings.)
     */
    //  Called by: Links.linkFileType
    //  Called by: some of the functions in Links.specialTargetTestFunctions
    linkFileExtensionMatches: (link, ext) => {
        return typeof ext == "string"
               ? link.pathname.toLowerCase().endsWith(`.${ext}`)
               : link.pathname.toLowerCase().endsWithAnyOf(ext.map(xt => `.${xt}`));
    },

    /*  Returns an info object that specifies link icon and link icon type for
        the given link, if the link is a link to one of the recognized file
        types. (Returns null otherwise.) The info object has fields ‘type’ and
        ‘icon’, both of which have string values. (See the Links.fileLinkTypes
        array for details on what the values of these fields can be.)
     */
    //  Called by: Links.linkIconInfo
    linkFileType: (link) => {
        /*  Various defined file types, classified by file extensions.
         */
        for (fileTypeDefinition of Links.fileLinkTypes) {
            [ fileType, iconType, extensions ] = fileTypeDefinition;
            if (Links.linkFileExtensionMatches(link, extensions.split(" ")))
                return { icon: fileType, type: iconType };
        }

        /*  PDF links, decorated by server code with a ‘link-pdf’ class.
         */
        if (link.classList.contains("link-pdf"))
            return { icon: "pdf", type: "svg" };

        /*  PHP files, but only if hosted locally (remote links with the ‘.php’
            extension are overwhelmingly likely to be dynamic web pages, rather
            than statically served PHP files).
         */
        if (   link.hostname == location.hostname
            && Links.linkFileExtensionMatches(link, "php"))
            return { icon: "code", type: "svg" };

        /*  HTML documents, but only if hosted locally _and_ located in the
            /static/ directory (remote links ending in ‘.html’ will be web pages
            all but a vanishingly tiny percentage of the time; likewise, local
            URLs ending in ‘.html’ that are outside the /static/ path are simply
            local pages.
         */
        if (   link.hostname == location.hostname
            && link.pathname.startsWith("/static/")
            && Links.linkFileExtensionMatches(link, "html"))
            return { icon: "code", type: "svg" };

        /*  Google Docs.
         */
        if (link.hostname == "docs.google.com")
            return { icon: "worddoc", type: "svg" };

        /*  Imgur links (except links to the Imgur front page itself).
         */
        if (   link.hostname == "imgur.com"
            && link.pathname > "")
            return { icon: "image", type: "svg" };

        return null;
    },

    /******************************************/
    /*  Icons for links to specific sites, etc.
     */

    /*  Returns true iff the given link matches the given pattern by hostname.

        Note that the hostname we classify links by is not necessarily hostname
        of the URL, per se, but the hostname of the site from which the content
        has been sourced (these two things will differ in the case of locally
        archived pages from other sites).

        The `pattern` argument may be a string or a RegExp. If a string, then
        an exact string match is checked for. If a RegExp, then a full pattern
        match is checked for (and capture groups, etc., are ignored).
     */
    //  Called by: Links.linkTargetType
    //  Called by: some of the functions in Links.specialTargetTestFunctions
    linkHostnameMatchesPattern: (link, pattern) => {
        //  Get the true hostname of the link’s content.
        let hostname = link.hostname;
        if (   link.hostname == location.hostname
            && link.pathname.startsWith("/doc/www/"))
            hostname = /\/doc\/www\/([^\/]+?)(\/.+)?$/.exec(link.pathname)[1];

        //  Hostname matched by regular expression.
        if (   pattern instanceof RegExp
            && pattern.test(hostname) == true)
            return true;

        //  Hostname exactly matched by string.
        if (hostname == pattern)
            return true;

        return false;
    },

    /*  Returns an info object that specifies link icon and link icon type for
        the given link, if the link matches one of the defined URL pattersn.
        (Returns null otherwise.) The info object has fields ‘type’ and ‘icon’,
        both of which have string values. (See the Links.fileLinkTypes array for
        details on what the values of these fields can be.)
     */
    //  Called by: Links.linkIconInfo
    linkTargetType: (link) => {
        for (targetTypeDefinition of Links.targetLinkTypes) {
            [ icon, type, urlPattern ] = targetTypeDefinition;

            if (   typeof urlPattern == "string"
                && urlPattern.startsWith("$")) {
                let testFunction = Links.specialTargetTestFunctions[urlPattern.substr(1)];
                if (   testFunction
                    && testFunction(link))
                    return { icon: icon, type: type };
            } else if (Links.linkHostnameMatchesPattern(link, urlPattern)) {
                return { icon: icon, type: type };
            }
        }

        return null;
    }
};

/************************************/
/*  LINKED FILE TYPE ICON DEFINITIONS

    Each row of this array defines or specifies an icon, specifies how the icon
    should be rendered, and lists a set of file extensions, to links to which
    the icon should be applied.

    Links are checked against each row’s file extension group in sequence; when
    a  match is found, the icon specified by that row is assigned to the link
    (and no more rows are checked). (If a match is not found in this array, then
    no linkicon data attributes are applied to the link.)

    Fields in each row are:

        [ ICON_NAME, ICON_TYPE, FILE_EXTENSIONS ]

    (All fields are required for each entry.)

    See comment on the Links.targetLinkTypes array, below, for information about
    the ICON_NAME and ICON_TYPE fields.

    FILE EXTENSIONS field values are strings, containing a space-separated list
    of file extensions (without the dot, i.e. `txt` and not `.txt`). A link
    whose pathname ends with a dot followed by one of the listed file extensions
    will be assigned the named icon. (File extensions should always be given in
    lowercase.)
 */
Links.fileLinkTypes = [
    //  Textfiles.
    [ "txt",            "svg",      "opml txt xml json jsonl md"            ],

    //  Code, scripts, etc.
    [ "code",           "svg",      "css hs js conf sh r patch diff"        ],

    //  Word (& compatible) documents.
    [ "worddoc",        "svg",      "doc docx"                              ],

    //  Excel (& compatible) documents.
    [ "spreadsheet",    "svg",      "xls xlsx ods"                          ],

    //  CSV files.
    [ "csv",            "svg",      "csv"                                   ],

    //  Images.
    [ "image",          "svg",      "gif bmp ico jpg jpeg png svg xcf"      ],

    //  Archive files.
    [ "audio",          "svg",      "mp3 wav flac ogg rm"                   ],

    //  Video files.
    [ "file-video",     "svg",      "swf mp4 mkv webm"                      ],

    //  Archive files.
    [ "archive",        "svg",      "tar zip xz img bin pkl onnx pt"        ],

    //  Miscellaneous files (for which there is no specific icon).
    [ "misc",           "svg",      "ebt mdb mht ttf"                       ],

    //  EPUB files.
    [ "EPUB",           "text,sans,quad",       "epub"                      ],
];

/*******************************/
/*  LINK TARGET ICON DEFINITIONS

    Each row of this array defines or specifies an icon, specifies how the icon
    should be rendered, and provides a link target matching pattern that assigns
    the icon to a specific category of links (usually, links to websites/pages
    on a certain domain).

    Links are checked against each row’s matching pattern in sequence; when a
    match is found, the icon specified by that row is assigned to the link (and
    no more rows are checked). (If a match is not found in this array, icon
    assignment falls back to the file type array (Links.fileLinkTypes).)

    Fields in each row are:

        [ ICON_NAME, ICON_TYPE, URL_PATTERN ]

    (All fields are required for each entry.)

    ICON NAMES are strings; they specify the characters of a text icon, or the
    name of an SVG icon.

    ICON TYPES are comma-separated lists of type tags. These specify how the
    icon should be rendered. Available type tags include the following:

        text, bold, italic, quad, sans, overline, svg

    One of either ‘text’ or ‘svg’ must be present, otherwise the icon will not
    be rendered.

    (Note that not all type tag combinations are valid; for instance, an icon
     type of "text,svg" is invalid, as an icon may be a text icon or an SVG
     icon, but not both. Specifying an invalid combination of type tags results
     in undefined behavior.)

    URL PATTERNS may be one of three things:

    1. A string specifying a domain name (in which case exact string match
       against the link’s hostname is checked for).

    2. A RegExp (which is tested for full-pattern match against the link’s
       hostname; capture groups, etc., are ignored).

    3. A key into the Links.specialTargetTestFunctions dictionary (designated by
       a string starting with ‘$’; the actual key is the string with the ‘$’
       prefix omitted), in which case the link is passed to the specified
       function, which returns true or false.

    The first way (string) is preferred if a single, exact domain can be
    specified, because string comparison is faster than either regular
    expression parsing and matching or (most likely) a function call.
 */
Links.targetLinkTypes = [
    /***********************************************/
    /*  High-priority URL patterns get listed first.
     */

    //  DeepMind; match articles or anchors about DM too.
    //  primary user: deepmind.com
    [ "deepmind",           "svg",                  "$DEEPMIND"                         ],

    /*  Microsoft: I don’t think
        https://en.wikipedia.org/wiki/File:Microsoft_logo_(2012).svg
        is all that recognizable, so make a logotype more like
        https://en.wikipedia.org/wiki/File:Microsoft_logo_(1987).svg :
        an italic sans "MS".
     */
    [ "MS",             "text,sans,italic",         "$MICROSOFT"                        ],

    //  Nvidia: https://en.wikipedia.org/wiki/Nvidia#cite_note-2 yeah no
    [ "N",              "text",                     "$NVIDIA"                           ],

    //  OpenAI; match articles or anchors about OA too.
    //  primary user: openai.com
    [ "openai",             "svg",                  "$OPEN_AI"                          ],

    //  Facebook.
    [ "facebook",           "svg",                  "$FACEBOOK"                         ],

    //  Google.
    [ "google",             "svg",                  "$GOOGLE"                           ],

    /**************************/
    /*  Unicode trickery icons.
     */

    //  (ψ) GREEK SMALL LETTER PSI
    [ "\u{03C8}",       "text",                     "psyarxiv.com"                      ],

    //  SSC’s book
    /*  (ℵ) ALEF SYMBOL (We use the math symbol instead of the Hebrew
        deliberately to avoid triggering bizarre Hebrew bidirectional
        text-related layout bugs on Mac Firefox.)
     */
    [ "\u{2135}",       "text",                     "unsongbook.com"                    ],

    /*  Favicon is a little normal distribution/histogram
        (▅▇▃) LOWER FIVE EIGHTHS BLOCK, LOWER SEVEN EIGHTHS BLOCK,
              LOWER THREE EIGHTHS BLOCK
     */
    [ "\u{2585}\u{2587}\u{2583}",   "text",         "andrewgelman.com"                  ],
    [ "\u{2585}\u{2587}\u{2583}",   "text",         "statmodeling.stat.columbia.edu"    ],

    //  Kevin Simler’s Melting Asphalt blog
    //  (▲) BLACK UP-POINTING TRIANGLE
    [ "\u{25B2}",       "text",                     "meltingasphalt.com"                ],

    //  (♘) WHITE CHESS KNIGHT
    [ "\u{2658}",       "text",                     "link.springer.com"                 ],

    /*  TinyLetter’s icon, without color, isn’t memorable enough; throw in the
        other email services
        (✉) ENVELOPE
     */
    [ "\u{2709}",       "text",                     "www.tinyletter.com"                ],
    [ "\u{2709}",       "text",                     "groups.google.com"                 ],
    [ "\u{2709}",       "text",                     "groups.yahoo.com"                  ],
    [ "\u{2709}",       "text",                     "www.mail-archive.com"              ],

    //  Bloomberg: no usable logo, just an inset-B
    //  (𝐁) MATHEMATICAL BOLD CAPITAL B
    [ "\u{1D401}",      "text",                     /(.+\.)?bloomberg.com$/             ],

    //  Medium: cheaper to abuse Unicode
    //  (𝐌) MATHEMATICAL BOLD CAPITAL M
    [ "\u{1D40C}",      "text",                     "medium.com"                        ],

    //  MR: cheaper to abuse Unicode
    //  (𝐑) MATHEMATICAL BOLD CAPITAL R
    [ "M\u{1D411}",     "text",                     "marginalrevolution.com"            ],

    /*  Haskell: simplify logo; the double-lambda is too busy when used for link
        icons
        (𝛌) MATHEMATICAL BOLD SMALL LAMDA
        primary user: hackage.haskell.org
     */
    [ "\u{1D6CC}",      "text",                     "$HASKELL_DOT_ORG"                  ],

    /*  ArXiv: Their skull+smiley logo is too bizarre & off-putting to use, in
        addition to not working as a tiny monochrome image
        (𝛘) MATHEMATICAL BOLD SMALL CHI (bold makes it show up better when tiny)
     */
    [ "\u{1D6D8}",      "text",                     "arxiv.org"                         ],

    /******************************************************/
    /*  Icons made of one or more regular ASCII characters.
     */

    //  The Atlantic: replicate sloping by italics
    [ "A",              "text,italic",              /(.+\.)?theatlantic.com$/           ],

    [ "AF",             "text",                     "$ALIGNMENT_FORUM"                  ],

    [ "ANN",            "text,sans",                /(.+\.)?animenewsnetwork.com$/      ],

    //  Ars is an orange box, not usable
    [ "ars",            "text,sans",                "arstechnica.com"                   ],

    //  BBC: no usable logo
    [ "BBC",            "text,sans",                /(.+\.)?bbc.com$/                   ],
    [ "BBC",            "text,sans",                /(.+\.)?bbc.co.uk$/                 ],

    //  British Medical Journal or just ‘bmj’
    [ "bmj",            "text,sans",                /(.+\.)?bmj.com$/                   ],

    [ "CDC",            "text",                     "www.cdc.gov"                       ],

    //  US federal Department of Justice
    [ "DoJ",            "text",                     /(.+\.)?justice.gov$/               ],

    [ "E",              "text,italic",              "www.edge.org"                      ],

    //  Economist: logo is just ‘Economist’...
    [ "E",              "text,sans",                /(.+\.)?economist.com$/             ],

    //  Elsevier/Sciencedirect.com: also an ‘E’
    [ "E",              "text",                     /(.+\.)?sciencedirect.com$/         ],

    //  Evangelion: we’ll split this into EGF-related and other NGE sites
    //  Primary user: eva.onegeek.org
    [ "EG",             "text",                     /(.+\.)?evageeks.org$/              ],
    [ "EG",             "text",                     /(.+\.)?evamonkey.com$/             ],

    //  Filfre.net/The Digital Antiquarian has no logo or usable substitute...
    [ "F",              "text",                     /(.+\.)?filfre.net$/                ],

    //  U.S. Food & Drug Administration
    [ "FDA",            "text,sans",                /(.+\.)?fda.gov$/                   ],

    /*  The FF.net logo is pretty crazy, and I don’t think anyone would
        recognize it in monochrome
     */
    [ "FF",             "text",                     "www.fanfiction.net"                ],

    //  GoodReads: logo doesn’t make sense as a grayscale
    [ "GR",             "text",                     /(.+\.)?goodreads.com$/             ],

    //  The Harney & Sons logo is too fancy to scale down reasonably
    [ "H",              "text",                     /(.+\.)?harney.com$/                ],

    //  Kevin Kelly
    [ "KK",             "text,sans",                "kk.org"                            ],

    //  LW logo is just a colored ‘LW’, so no point in converting
    //  other user: wiki.lesswrong.com
    [ "LW",             "text",                     /(.+\.)?lesswrong.com$/             ],
    [ "LW",             "text",                     "www.greaterwrong.com"              ],

    //  MAL: the blue of their logo doesn’t work, so just text
    [ "MAL",            "text,sans",                "myanimelist.net"                   ],

    [ "MJ",             "text,sans",                "www.motherjones.com"               ],

    //  Nature
    [ "n",              "text",                     /(.+\.)?nature.com$/                ],

    //  Primary user: forum.evageeks.org wiki.evageeks.org
    [ "NGE",            "text",                     /(.+\.)?onegeek.org$/               ],
    [ "NGE",            "text",                     "eva-fan.com"                       ],
    [ "NGE",            "text",                     "evaotaku.com"                      ],
    [ "NGE",            "text",                     "khara.co.jp"                       ],
    [ "NGE",            "text",                     "gainax.co.jp"                      ],
    [ "NGE",            "text",                     "17th-angel.tumblr.com"             ],

    //  OB logo too bad to use
    [ "OB",             "text",                     "www.overcomingbias.com"            ],

    //  Oxford Academic Journals / OUP
    [ "OUP",            "text",                     "academic.oup.com"                  ],

    [ "P@D",            "text",                     "poniesatdawn.bandcamp.com"         ],

    //  The Paris Review: not even going to try to make their weird bird logo work
    [ "PR",             "text",                     /(.+\.)?theparisreview.org$/        ],

    /*  R: at this point R Studio has taken over a lot of control of the R
        ecosystem, so might as well treat them as official too...
        primary user: cran.r-project.org
     */
    [ "R",              "text",                     /(.+\.)?r-project.org$/             ],
    [ "R",              "text",                     /(.+\.)?rstudio.com$/               ],

    //  Science is just typeset in red
    [ "S",              "text",                     /(.+\.)?science.org$/               ],
    [ "S",              "text",                     /(.+\.)?sciencemag.org$/            ],

    [ "S",              "text,sans",                "slate.com"                         ],

    [ "s",              "text",                     /(.+\.)?salon.com$/                 ],

    //  Avoid the unfortunate connotations of ‘SS’
    [ "Ss",             "text",                     "scholars-stage.org"                ],

    //  SSC logo too bad to use
    //  primary user: slatestarcodex.com
    [ "SSC",            "text",                     "$SLATE_STAR_CODEX"                 ],

    /*  Technology Review (their logo has a little slash in it which you
        probably can’t see at low-res) but is otherwise just a ‘T’ so meh
     */
    [ "T",              "text,sans",                /(.+\.)?technologyreview.com$/      ],

    //  TV Tropes: their lampshade icon is unrecognizable & hard to see small
    [ "TV",             "text",                     "tvtropes.org"                      ],

    //  Gene Wolfe mailing list; no logo
    //  primary user: lists.urth.net
    [ "U",              "text",                     /(.+\.)?urth.net$/                  ],

    [ "VF",             "text",                     "www.vanityfair.com"                ],

    [ "Vox",            "text,italic",              "www.vox.com"                       ],

    /*  Wiley & Sons’s ‘W’ unfortunately overlaps with the WP ‘W’ but if we sans
        it, maybe that’ll help
        primary user: onlinelibrary.wiley.com
     */
    [ "W",              "text,sans",                /(.+\.)?wiley.com$/                 ],

    //  The Wall Street Journal
    [ "WSJ",            "text",                     /(.+\.)?wsj.com$/                   ],

    //  Long Now Foundation projects
    [ "X",              "text,overline",            /(.+\.)?longbets.org$/              ],
    [ "X",              "text,overline",            /(.+\.)?longnow.org$/               ],
    [ "X",              "text,overline",            /(.+\.)?rosettaproject.org$/        ],
    [ "X",              "text,overline",            /(.+\.)?theinterval.org$/           ],

    [ "ys",             "text",                     "yunnansourcing.com"                ],

    //  PB logo is confusing. A purple question mark...?
    [ "?",              "text,sans,bold",           "predictionbook.com"                ],

    /****************************/
    /*  Quad-letter-square icons.
     */

    //  Cell: their logo is unrecognizable (and dumb)
    [ "CELL",           "text,quad,sans",           "www.cell.com"                      ],

    [ "MLPW",           "text,quad,sans,italic",    "mlp.fandom.com"                    ],

    [ "NBER",           "text,quad",                "$NBER"                             ],

    /*  PNAS: they don’t have a real logo, but their favicon does a nice little
        compact square (white text on blue background), and we can replicate
        that in CSS (but just as black text on white background,
        per our monochrome theme)
     */
    [ "PNAS",           "text,quad",                /(.+\.)?pnas.org$/                  ],

    [ "RAND",           "text,quad,sans",           /(.+\.)?rand.org$/                  ],

    //  Sage Journals’s logo is a circled S... but would anyone recognize it?
    //  primary user: journals.sagepub.com
    [ "SAGE",           "text,quad,sans",           /(.+\.)?sagepub.com$/               ],

    [ "TPDR",           "text,quad",                "publicdomainreview.org"            ],

    [ "XKCD",           "text,quad,sans",           /(.+\.)?xkcd.com$/                  ],

    /*************/
    /*  SVG icons.
     */

    //  Amazon.
    [ "amazon",             "svg",                  /(.+\.)?amazon.com$/                ],
    [ "amazon",             "svg",                  /amazon.co.[^\.]+$/                 ],

    //  Bitcoin.
    //  primary user: en.bitcoin.it
    [ "bitcoin",            "svg",                  /(.+\.)?bitcoin.it$/                ],
    [ "bitcoin",            "svg",                  "bitcointalk.org"                   ],

    //  BioRxiv (custom icon: italic Chi with DNA cross-strands).
    [ "chi-dna",            "svg",                  /(.+\.)?biorxiv.org$/               ],
    [ "chi-dna",            "svg",                  /(.+\.)?medrxiv.org$/               ],

    //  Distill ML journal.
    [ "distillpub",         "svg",                  "distill.pub"                       ],

    /*  Dropbox: old file-host, deprecated since they’ve started killing
        inactive accounts
        primary user: dl.dropboxusercontent.com
     */
    [ "dropbox",            "svg",                  /(.+\.)?dropbox.com$/               ],
    [ "dropbox",            "svg",                  /(.+\.)?dropboxusercontent.com$/    ],

    //  Erowid.
    [ "erowid",             "svg",                  /(.+\.)?erowid.org$/                ],

    //  Github; I exclude github.io because that’s blogs.
    [ "github",             "svg",                  /(.+\.)?github.com$/                ],

    //  Google Scholar.
    [ "google-scholar",     "svg",                  "scholar.google.com"                ],

    //  PG/HN/YC (shared logo).
    //  primary user: news.ycombinator.com
    [ "hn",                 "svg",                  /(.+\.)?paulgraham.com$/            ],
    [ "hn",                 "svg",                  /(.+\.)?ycombinator.com$/           ],

    //  Web archives.
    //  primary user: web.archive.org
    //  secondary user: replay.waybackmachine.org
    [ "internetarchive",    "svg",                  /(.+\.)?webcitation.org$/           ],
    [ "internetarchive",    "svg",                  /(.+\.)?mementoweb.org$/            ],
    [ "internetarchive",    "svg",                  /(.+\.)?archive.org$/               ],
    [ "internetarchive",    "svg",                  /(.+\.)?archive-it.org$/            ],
    [ "internetarchive",    "svg",                  /(.+\.)?archiveteam.org$/           ],
    [ "internetarchive",    "svg",                  /(.+\.)?waybackmachine.org$/        ],
    [ "internetarchive",    "svg",                  "$LOCAL_ARCHIVE"                    ],

    //  MegaUpload/Mega: filesharing (used for big files).
    [ "mega",               "svg",                  "mega.nz"                           ],

    //  MIRI/intelligence.org.
    [ "miri",               "svg",                  "intelligence.org"                  ],

    //  The New York Times: reduction of full SVG logo to just the ‘T’ they use as an icon.
    [ "newyorktimes",       "svg",                  /(.+\.)?nytimes.com$/               ],

    /*  NCBI/Pubmed: simplification of their logo
        (https://upload.wikimedia.org/wikipedia/commons/0/07/US-NLM-NCBI-Logo.svg).
        primary user: ncbi.nlm.nih.gov
     */
    [ "nlm-ncbi",           "svg",                  /(.+\.)?nlm.nih.gov$/               ],

    /*  Patreon. (Used the old one
        (https://upload.wikimedia.org/wikipedia/commons/9/94/Patreon_logo.svg)
        because I don’t like the new one.)
     */
    [ "patreon",            "svg",                  /(.+\.)?patreon.com$/               ],

    //  PLOS ONE in all their domain permutations...
    //  primary user: journals.plos.org
    [ "plos",               "svg",                  /(.+\.)?plos.org$/                  ],
    [ "plos",               "svg",                  /(.+\.)?plosone.org$/               ],
    [ "plos",               "svg",                  /(.+\.)?plosbiology.org$/           ],

    //  Reddit.
    //  primary user: old.reddit.com
    [ "reddit",             "svg",                  /(.+\.)?reddit.com$/                ],

    //  The *Exchange/*Overflow family of websites.
    [ "stackexchange",      "svg",                  /(.+\.?)?overflow.net$/             ],
    [ "stackexchange",      "svg",                  /(.+\.?)?overflow.com$/             ],
    [ "stackexchange",      "svg",                  /(.+\.)?stackexchange.com$/         ],

    //  Substack.
    //  primary user: gwern.substack.com
    [ "substack",           "svg",                  /(.+\.)?substack.com$/              ],

    //  El Grauniad.
    [ "theguardian",        "svg",                  /(.+\.)?theguardian.com$/           ],
    [ "theguardian",        "svg",                  "www.guardian.co.uk"                ],

    //  The New Yorker: the Dandy SVG, simplified & rotated more vertically.
    [ "thenewyorker",       "svg",                  /(.+\.)?newyorker.com$/             ],

    //  Tumblr.
    [ "tumblr",             "svg",                  /(.+\.)?tumblr.com$/                ],

    //  Twitter.
    [ "twitter",            "svg",                  /(.+\.)?x.com$/               ],
    [ "twitter",            "svg",                  "nitter.net"                  ],

    //  Upton Tea.
    [ "uptontea",           "svg",                  /(.+\.)?uptontea.com$/              ],

    //  Bandcamp/SoundCloud links.
    [ "video",              "svg",                  /(.+\.)?soundcloud.com$/            ],
    [ "video",              "svg",                  /(.+\.)?bandcamp.com$/              ],

    //  The Washington Post: truncated their blackletter to ‘WP’.
    [ "washingtonpost",     "svg",                  /(.+\.)?washingtonpost.com$/        ],

    //  Wikipedia.
    //  primary user: en.wikipedia.org
    [ "wikipedia",          "svg",                  /(.+\.)?wikipedia.org$/             ],
    //  primary user: meta.wikimedia.org
    [ "wikipedia",          "svg",                  /(.+\.)?wikimedia.org$/             ],
    //  primary user: en.wiktionary.org
    [ "wikipedia",          "svg",                  /(.+\.)?wiktionary.org$/            ],
    //  primary user: en.wikisource.org
    [ "wikipedia",          "svg",                  /(.+\.)?wikisource.org$/            ],
    [ "wikipedia",          "svg",                  /(.+\.)?wikimediafoundation.org$/   ],

    //  Wired.
    [ "wired",              "svg",                  /(.+\.)?wired.com$/                 ],

    //  YouTube links.
    [ "youtube",            "svg",                  "www.youtube.com"                   ],
    [ "youtube",            "svg",                  "www.youtu.be"                      ],

//  [ "ICON_NAME",      "ICON_TYPE",                "URL_PATTERN"                       ],
];

/*  Some domains / sites require have special conditions for link icon
    assignment.
 */
Links.specialTargetTestFunctions = {
    //  Links to haskell.org, NOT including links to .hs code files
    "HASKELL_DOT_ORG": (link) => (   Links.linkHostnameMatchesPattern(link, /(.+\.)?haskell.org$/)
                                  && Links.linkFileExtensionMatches(link, "hs") == false),

    //  Either the Alignment Forum itself, or the AF view on GreaterWrong
    "ALIGNMENT_FORUM": (link) => (   Links.linkHostnameMatchesPattern(link, /(.+\.)?alignmentforum.org$/)
                                  || (   Links.linkHostnameMatchesPattern(link, "www.greaterwrong.com")
                                      && link.searchParams.get("view") == "alignment-forum")),

    //  SPEAK THE DEVIL’S NAME AND HIS LINKICON SHALL APPEAR
    "MICROSOFT": (link) => (/microsoft/i).test(link.href),

    //  A lesser demon, but the same principle applies
    "NVIDIA": (link) => (/nvidia/i).test(link.href),

    //  Slate Star Codex & co.
    "SLATE_STAR_CODEX": (link) => (   (   Links.linkHostnameMatchesPattern(link, /(.+\.)?slatestarcodex.com$/)
                                       && !(link.classList.contains("link-pdf")))
                                   || (/slatestarcodex/i).test(link.href)
                                   || (/yvain/i).test(link.href)
                                   || Links.linkHostnameMatchesPattern(link, "slatestarscratchpad.tumblr.com")
                                   || Links.linkHostnameMatchesPattern(link, "www.astralcodexten.com")),

    //  Links to NBER, NOT including PDF file links
    "NBER": (link) => (   Links.linkHostnameMatchesPattern(link, "www.nber.org")
                       && !(link.classList.contains("link-pdf"))),

    //  All that gives voice to Deepmind is Deepmind
    "DEEPMIND": (link) => (/deepmind/i).test(link.href),

    //  Whosoever speaks the name of OpenAI shall make manifest its linkicon
    "OPEN_AI": (link) => (/openai/i).test(link.href),

    //  Locally archived documents / pages (but not the ones in PDF format)
    "LOCAL_ARCHIVE": (link) => (    link.classList.contains("local-archive-link")
                                && !link.classList.contains("link-pdf")),

    //  The Enemy wears many faces
    //  ... enough to fill a whole book, one might say
    "FACEBOOK": (link) => (   Links.linkHostnameMatchesPattern(link, /(.+\.)?facebook.com$/)
                           || link.hash.toLowerCase() == "#facebook"
                           || link.hash.toLowerCase().includes("org=facebook")),

    //  They say Don’t Be Evil... they wouldn’t lie, would they...?
    /*  Google searches, other tools.
        Note that there are many Google subdomains, which we may wish to iconify
        differently, so we narrow down with just ‘www’.

        Google Brain doesn’t have any consistent or recognizable logo, don’t
        bother trying to replicate one of the dots (no one will recognize it);
        use ‘GB’ would not be a bad idea, but I suspect that would also confuse
        people. So reusing the ‘G’ is the least bad option.
     */
    "GOOGLE": (link) => (   Links.linkHostnameMatchesPattern(link, "www.google.com")
                         || link.hash.toLowerCase() == "#google"
                         || link.hash.toLowerCase().includes("org=google")),

};