mourningdove/cgi-bin/DW/Formats.pm
2026-05-24 01:03:05 +00:00

163 lines
6 KiB
Perl

#!/usr/bin/perl
#
# DW::Formats
#
# A helper package for getting info about DW's supported markup formats. Each of
# these named formats represents a specific set of expected formatting
# behaviors, and each entry and comment is assigned a specific format. By
# remembering the expected formats of archived content, we can always display it
# as originally intended despite changing our markup behaviors over time.
#
# When adding a new format, you must also:
# - Implement its markup transformations in LJ::CleanHTML. We're deliberately
# sacrificing some convenience in this package to keep CleanHTML more legible.
# - If it replaces an existing format, update @active_formats, %format_upgrades,
# and $default_format accordingly.
#
# Authors:
# Nick Fagerlund <nick.fagerlund@gmail.com>
#
# Copyright (c) 2020 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself. For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.
package DW::Formats;
use strict;
our @active_formats = qw( html_casual1 markdown0 html_raw0 rte0 );
our $default_format = 'html_casual1';
# obsolete version => newest version:
our %format_upgrades = ( html_casual0 => 'html_casual1', );
# format hashref keys:
# id: identifies formats everywhere, including the HTML cleaner.
# name: displayed in web UI.
# description and features: displayed on /dev/formats.
our %formats = (
html_casual0 => {
id => 'html_casual0',
name => "Casual HTML (legacy 0)",
features => q{DW tags, auto linebreaks, auto links},
description => q{An older version of casual HTML that doesn't support @mentions.},
},
html_casual1 => {
id => 'html_casual1',
name => "Casual HTML",
features => q{@mentions, DW tags, auto linebreaks, auto links},
description =>
q{The classic default format: uses HTML tags for formatting, but automatically formats paragraphs.},
},
html_raw0 => {
id => 'html_raw0',
name => "Raw HTML",
features => q{DW tags},
description =>
q{Normal HTML, plus Dreamwidth's special <code>&lt;user&gt;</code> and <code>&lt;cut&gt;</code> tags. Doesn't automatically format paragraphs, and doesn't support @mentions.},
},
html_extra_raw => {
id => 'html_extra_raw',
name => "Raw HTML (external source)",
features => q{none},
description =>
q{Normal HTML from a feed or some other external source. Doesn't support any special Dreamwidth syntax, including tags like <code>&lt;user&gt;</code>.},
},
markdown0 => {
id => 'markdown0',
name => "Markdown",
features => q{Markdown syntax, @mentions, DW tags},
description =>
q{A lightweight markup syntax that automatically formats paragraphs, provides shortcuts for the most common HTML tags, and allows inline HTML for more complex formatting. This implementation uses Perl's Text::Markdown module (which is very close to <a href="https://daringfireball.net/projects/markdown/">the original Markdown syntax</a>), plus some DW-specific extensions.},
},
rte0 => {
id => 'rte0',
name => "Rich Text Editor",
features => q{DW tags, auto linebreaks, auto links},
description => q{Markup generated by the legacy FCKEditor RTE.},
},
);
# Legacy aliases:
$formats{markdown} = $formats{markdown0};
# Builds items that can be passed to an LJ::html_select for picking a format,
# plus the format that should be initially selected.
# Args: opts hash like (current => "format", preferred => "format").
# Returns: Hashref like {selected => "format", items => [...]}. (Unfortunately,
# LJ::html_select doesn't support just setting selected=true on one of the items,
# so we need to return an extra value.)
sub select_items {
my %opts = @_;
# Canonicalize em
my $current = validate( $opts{current} );
my $preferred = validate( $opts{preferred} );
my @formats = @active_formats;
my $selected = $default_format;
# Use the content's existing format if possible, then fall back to the
# user's preference (IF it's active or has an active replacement), then the
# site default.
if ($current) {
push( @formats, $current ) unless grep( $_ eq $current, @formats );
$selected = $current;
}
elsif ( $preferred && is_active($preferred) ) {
$selected = $preferred;
}
elsif ( $preferred && is_active( upgrade($preferred) ) ) {
$selected = upgrade($preferred);
}
return {
selected => $selected,
items =>
[ map { { value => $formats{$_}->{id}, text => $formats{$_}->{name}, } } @formats ],
};
}
# Return the canonical version of the provided format ID if valid, empty string if not.
sub validate {
my $format = $_[0];
if ( $format && $formats{$format} ) {
return $formats{$format}->{id};
}
else {
return '';
}
}
sub is_active {
my $format = $_[0];
if ( $format && grep( $_ eq $format, @active_formats ) ) {
return 1;
}
return 0;
}
sub display_name {
my $format = $_[0];
if ( $format && $formats{$format} ) {
return $formats{$format}->{name};
}
return "Unknown markup format";
}
# If the provided format ID was replaced by a newer format, return the newest ID
# in its lineage.
sub upgrade {
my $format = validate( $_[0] );
return $format_upgrades{$format} || $format;
}
# Convenience aliases for email posts -- these should never be entered into the
# database as an editor prop value, but email posts pass these to validate() as
# a way of asking which format a user currently means if they just said 'html'
# or 'markdown'. (Why not just use 'html' and 'markdown' as the "current"
# aliases? Because 'markdown' is already in use by old email comments.)
$formats{markdown_latest} = $formats{ upgrade('markdown0') };
$formats{html_casual_latest} = $formats{ upgrade('html_casual1') };