Skip to content

Commit e0e3543

Browse files
committed
Merge branch '006-blogdown-live-editor'
2 parents a7d078f + 50a64f5 commit e0e3543

23 files changed

Lines changed: 1550 additions & 220 deletions

File tree

.github/copilot-instructions.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Auto-generated from all feature plans. Last updated: 2025-11-09
99
- N/A (static site generation, no runtime database for this feature) (004-collapsible-sections)
1010
- Perl 5.40+ + Template::Toolkit 3.102+, HTML::TokeParser::Simple, Text::Markdown::Blog (005-utf8-rebuild-warnings)
1111
- SQLite (build-time data only, in `db/ovid.db`) (005-utf8-rebuild-warnings)
12+
- Perl 5.40+ + Dancer2, Template::Toolkit, Text::Markdown::Blog (006-blogdown-live-editor)
13+
- Filesystem (source files), SQLite (read-only build data) (006-blogdown-live-editor)
1214

1315
- Perl 5.40+ + Devel::Cover, Test::Most, Type::Tiny, Getopt::Long, SQLite (001-test-coverage-improvement)
1416

@@ -72,9 +74,9 @@ Perl 5.40+: Follow standard conventions from constitution.md
7274
- All tasks must pass entire test suite before completion
7375

7476
## Recent Changes
75-
- 005-utf8-rebuild-warnings: Added Perl 5.40+ + Template::Toolkit 3.102+, HTML::TokeParser::Simple, Text::Markdown::Blog
76-
- 004-collapsible-sections: Added Perl 5.40+ (per Constitution Principle VII) + Template::Toolkit 3.102+, Text::Markdown::Blog (via Template::Plugin::Blogdown)
77-
- 004-collapsible-sections: Added Perl 5.40+ (per Constitution Principle VII) + Template::Toolkit 3.102+, Text::Markdown::Blog (via Template::Plugin::Blogdown)
77+
- 006-blogdown-live-editor: Added Perl 5.40+ + Dancer2, Template::Toolkit, Text::Markdown::Blog
78+
- 006-blogdown-live-editor: Added Perl 5.40+ + Dancer2, Template::Toolkit, Text::Markdown::Blog
79+
- 006-blogdown-live-editor: Added [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
7880

7981

8082
<!-- MANUAL ADDITIONS START -->

.gitignore

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
ttreerc
21
*.sw[pon]
32
/errors.err
43
*.DS_Store
@@ -18,3 +17,28 @@ coverage-report/
1817
*~
1918
*.swp
2019
*.bak
20+
21+
# Node.js specific patterns
22+
node_modules/
23+
24+
# Perl specific build files
25+
MYMETA.*
26+
blib/
27+
pm_to_blib
28+
_build/
29+
Build
30+
TODO.md
31+
32+
# IDEs
33+
.vscode/
34+
.idea/
35+
36+
# Makefiles
37+
Makefile
38+
Makefile.old
39+
40+
# artifacts of the new editor
41+
404.html
42+
editor.html
43+
44+
nytprof*

bin/article

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ use File::Spec::Functions qw(catfile);
77
use DateTime;
88
use Less::Script;
99
use Ovid::Site::Utils qw(use_smart_quotes);
10+
use Browser::Open qw(open_browser);
1011

1112
GetOptions(
1213
\my %opt_for,
1314
'nocomments', # disables comments for an entry
1415
'type=s', # one of 'article' or 'blog'
1516
'description=s',
17+
'open', # open the article in the live editor
1618
) or die "Bad options";
1719

1820
my $comments = $opt_for{nocomments} ? 0 : 1;
@@ -54,7 +56,7 @@ my $title = join( ' ' => @ARGV );
5456
my $slug = make_slug($title);
5557
my $template = write_article_stub( $title, $slug, \%opt_for );
5658
add_to_sqitch( $title, $slug, \%opt_for );
57-
edit_article($template); #exits this program
59+
edit_article($template, \%opt_for); #exits this program
5860

5961
sub add_to_sqitch ( $title, $slug, $opt_for ) {
6062
my $dbh = dbh();
@@ -109,9 +111,23 @@ sub write_article_stub ( $title, $slug, $opt_for ) {
109111
return $template;
110112
}
111113

112-
sub edit_article ($template) {
113-
my $editor = $ENV{EDITOR} || 'vim';
114-
system $editor => $template;
114+
sub edit_article ($template, $opt_for) {
115+
if ( $opt_for->{open} ) {
116+
my $pid = fork // die "Can't fork: $!";
117+
if ( $pid == 0 ) {
118+
exec 'bin/launch', $template;
119+
die "Failed to exec bin/launch: $!";
120+
}
121+
122+
sleep 1; # Wait for server to start
123+
open_browser('http://127.0.0.1:3000/');
124+
125+
waitpid $pid, 0;
126+
}
127+
else {
128+
my $editor = $ENV{EDITOR} || 'vim';
129+
system $editor => $template;
130+
}
115131
}
116132

117133
sub template ( $title, $directory, $slug, $opt_for ) {
@@ -170,5 +186,6 @@ article - Write an article
170186
nocomments If passed, comments are disabled for this article
171187
type Must be one of 'article' or 'blog'
172188
description A longer description of what the entry is about.
189+
open If passed, opens the article in the live editor (requires bin/launch)
173190
174191
If you do not enter the type, description, or a title, you will be prompted for them.

bin/launch

Lines changed: 52 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,55 @@
11
#!/usr/bin/env perl
22

3-
## Make dist-zilla happy
4-
package
5-
http_this;
6-
7-
use strict;
8-
use warnings;
9-
use Plack::MIME;
10-
Plack::MIME->add_type(".wasm" => "application/wasm");
11-
12-
use App::HTTPThis;
13-
14-
# ABSTRACT: export the current directory over HTTP
15-
16-
App::HTTPThis->new->run;
17-
18-
19-
20-
21-
=pod
22-
23-
=head1 NAME
24-
25-
http_this - export the current directory over HTTP
26-
27-
=head1 VERSION
28-
29-
version 0.002
30-
31-
=head1 SYNOPSIS
32-
33-
## Export the current directory with HTTP
34-
$ http_this
35-
36-
## Export the dir_name directory with HTTP
37-
$ http_this dir_name
38-
39-
## Start the server on a specific port
40-
$ http_this --port 9001
41-
42-
## Announces the HTTP server via Bonjour with the specified name
43-
$ http_this --name "My cool webserver"
44-
45-
## Show documentation about our options
46-
$ http_this --help
47-
48-
## Show the entire man page
49-
$ http_this --man
50-
51-
=head1 DESCRIPTION
52-
53-
The C<http_this> command exports the current directory via HTTP. You can
54-
also export any directory by providing the path as a parameter.
55-
56-
A simple web server is started and is kept running until you kill it
57-
with C<Ctrl-C>.
58-
59-
All the files and directories will be availble to a browser under the
60-
URL the script outputs.
61-
62-
=encoding utf8
63-
64-
=head1 ARGUMENTS
65-
66-
The script accepts a single optional argument: the path of the directory
67-
to export.
68-
69-
=head1 OPTIONS
70-
71-
The following options are available:
72-
73-
=over 4
74-
75-
=item --port PORT
76-
77-
Start the HTTP server on a specific C<PORT>.
78-
79-
=item --name NAME
80-
81-
Announces the server over Bonjour.
82-
83-
This feature requires the L<Net::Rendezvous::Publish> module and the
84-
appropriate backend for your operating system, both available from
85-
L<CPAN|http://search.cpan.org/>. If one of them cannot be found, a
86-
warning message will be displayed.
87-
88-
=item --help
89-
90-
Print information about the script usage and its options.
91-
92-
=item --man
93-
94-
Print the entire man page for the command.
95-
96-
=back
97-
98-
=head1 AUTHOR
99-
100-
Pedro Melo <melo@cpan.org>
101-
102-
=head1 COPYRIGHT AND LICENSE
103-
104-
This software is Copyright (c) 2010 by Pedro Melo.
105-
106-
This is free software, licensed under:
107-
108-
The Artistic License 2.0
109-
110-
=cut
111-
112-
113-
__END__
3+
use v5.40;
4+
use lib 'lib';
5+
use Ovid::App::LiveEditor;
6+
use Dancer2;
7+
use Path::Tiny;
8+
9+
my $file = shift @ARGV or die "Usage: $0 <file>\n";
10+
my $path = path($file)->absolute;
11+
12+
# Auto-correct generated files to source files
13+
# If user provides blog/foo.html, switch to root/blog/foo.tt
14+
if ($path =~ m{/(?:articles|blog)/.*\.html$}) {
15+
my $root = path('.')->absolute;
16+
my $rel = eval { $path->relative($root) };
17+
if (!$@ && $rel !~ /^\.\./) {
18+
# Try .tt
19+
my $base_path = $root->child('root')->child($rel);
20+
my $try_tt = $base_path->stringify;
21+
$try_tt =~ s/\.html$/.tt/;
22+
23+
if (-f $try_tt) {
24+
my $p = path($try_tt);
25+
say STDERR "Notice: You provided a generated file. Switching to source file: " . $p->relative($root);
26+
$path = $p;
27+
} else {
28+
# Try .tt2markdown
29+
my $try_md = $try_tt . "2markdown";
30+
if (-f $try_md) {
31+
my $p = path($try_md);
32+
say STDERR "Notice: You provided a generated file. Switching to source file: " . $p->relative($root);
33+
$path = $p;
34+
}
35+
}
36+
}
37+
}
38+
39+
die "File not found: $path\n" unless $path->exists;
40+
die "Path is a directory, not a file: $path\n" if $path->is_dir;
41+
die "File is not readable: $path\n" unless -r $path;
42+
43+
# Ensure file is within the project root (basic security check)
44+
my $project_root = path('.')->absolute;
45+
if ( $path->stringify !~ /^\Q$project_root\E/ ) {
46+
die "Security Error: Cannot edit files outside the project directory.\n";
47+
}
48+
49+
$ENV{LIVE_EDITOR_FILE} = $path->stringify;
50+
51+
# Security: Bind to localhost only
52+
set host => '127.0.0.1';
53+
54+
dance;
11455

cpanfile

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# vim: ft=perl
22
#
3-
# Autogenerated via bin/recreate-cpanfile on Thu Jan 2 14:15:55 2025
3+
# Autogenerated via bin/recreate-cpanfile on Wed Nov 19 18:32:23 2025
44

55
=comment
66
@@ -12,45 +12,65 @@ See https://metacpan.org/pod/CPAN::Meta::Spec#VERSION-NUMBERS for details.
1212
1313
=cut
1414

15-
requires 'App::HTTPThis' => 0.009;
16-
requires 'App::Sqitch' => 1;
17-
requires 'CHI' => 0.61;
18-
requires 'Capture::Tiny' => 0.50;
19-
requires 'Class::XSAccessor' => 1.19;
20-
requires 'Config::Any' => 0.33;
21-
requires 'Const::Fast' => 0.014;
22-
requires 'DBD::SQLite' => 1.76;
23-
requires 'DBI' => 1.645;
24-
requires 'DateTime' => 1.65;
25-
requires 'DateTime::Format::SQLite' => 0.11;
26-
requires 'File::Find::Rule' => 0.34;
27-
requires 'File::Which' => 1.27;
28-
requires 'HTML::Parser' => 3.83;
29-
requires 'HTML::SimpleLinkExtor' => 1.273;
30-
requires 'HTML::TokeParser::Simple' => 3.16;
31-
requires 'HTTP::Tiny::Mech' => 1.001002;
32-
requires 'Import::Into' => 1.002005;
33-
requires 'MetaCPAN::Client' => 2.033000;
34-
requires 'Module::ScanDeps' => 1.37;
35-
requires 'Mojolicious' => 9.39;
36-
requires 'Moo' => 2.005005;
37-
requires 'Moose' => 2.2207;
38-
requires 'MooseX::Extended' => 0.35;
39-
requires 'OpenAPI::Client::OpenAI' => 0.13;
40-
requires 'Path::Tiny' => 0.146;
41-
requires 'PerlX::Maybe' => 1.202;
42-
requires 'Plack' => 1.0051;
43-
requires 'String::Util' => 1.35;
44-
requires 'Template::Toolkit' => 3.102;
45-
requires 'Test::Most' => 0.38;
46-
requires 'Text::Markdown' => 1.000031;
47-
requires 'Text::Unidecode' => 1.30;
48-
requires 'Try::Tiny' => 0.32;
49-
requires 'Type::Tiny' => 2.006000;
50-
requires 'WWW::Mechanize::Cached' => 1.56;
51-
requires 'XML::RSS' => 1.64;
52-
requires 'YAML::LibYAML' => 0.83;
53-
requires 'aliased' => 0.34;
54-
requires 'namespace::autoclean' => 0.31;
55-
requires 'utf8::all' => 0.024;
15+
requires 'App::HTTPThis' => 0;
16+
requires 'App::Sqitch' => 1;
17+
requires 'Browser::Open' => 0.04;
18+
requires 'CHI' => 0.61;
19+
requires 'Capture::Tiny' => 0.50;
20+
requires 'Class::XSAccessor' => 1.19;
21+
requires 'Config::Any' => 0.33;
22+
requires 'Const::Fast' => 0.014;
23+
requires 'DBD::SQLite' => 1.76;
24+
requires 'DBI' => 1.647;
25+
requires 'Dancer2' => v2.0.1;
26+
requires 'DateTime' => 1.66;
27+
requires 'DateTime::Format::SQLite' => 0.11;
28+
requires 'File::Find::Rule' => 0.35;
29+
requires 'File::Which' => 1.27;
30+
requires 'HTML::Parser' => 3.83;
31+
requires 'HTML::SimpleLinkExtor' => 1.273;
32+
requires 'HTML::TokeParser::Simple' => 3.16;
33+
requires 'HTTP::Message' => 7.01;
34+
requires 'HTTP::Tiny::Mech' => 1.001002;
35+
requires 'IPC::Run' => 20250809.0;
36+
requires 'IPC::System::Simple' => 1.30;
37+
requires 'Import::Into' => 1.002005;
38+
requires 'MetaCPAN::Client' => 2.033000;
39+
requires 'Module::ScanDeps' => 1.37;
40+
requires 'Mojolicious' => 9.42;
41+
requires 'Moo' => 2.005005;
42+
requires 'Moose' => 2.4000;
43+
requires 'MooseX::Extended' => 0.35;
44+
requires 'OpenAPI::Client::OpenAI' => 0.24;
45+
requires 'Path::Tiny' => 0.150;
46+
requires 'PerlX::Maybe' => 1.202;
47+
requires 'Plack' => 1.0051;
48+
requires 'Set::Object' => 1.43;
49+
requires 'String::Util' => 1.35;
50+
requires 'Template::Plugin::CommonMark' => 1.000;
51+
requires 'Template::Plugin::EnvHash' => 1.06;
52+
requires 'Template::Plugin::JSON' => 0.08;
53+
requires 'Template::Timer' => 1.00;
54+
requires 'Template::Tiny' => 1.16;
55+
requires 'Template::Tiny::Strict' => 1.18;
56+
requires 'Template::Toolkit' => 3.102;
57+
requires 'Test::MockModule' => v0.180.0;
58+
requires 'Test::Most' => 0.38;
59+
requires 'Text::Markdown' => 1.000031;
60+
requires 'Text::Unidecode' => 1.30;
61+
requires 'Try::Tiny' => 0.32;
62+
requires 'Type::Tiny' => 2.008004;
63+
requires 'URI' => 5.34;
64+
requires 'URI::Encode' => v1.1.1;
65+
requires 'URI::Find' => 20160806;
66+
requires 'URI::FromHash' => 0.05;
67+
requires 'URI::Nested' => 0.10;
68+
requires 'URI::Router' => v0.1.4;
69+
requires 'URI::db' => 0.23;
70+
requires 'WWW::Mechanize::Cached' => 1.56;
71+
requires 'XML::RSS' => 1.65;
72+
requires 'YAML::LibYAML' => 0.83;
73+
requires 'aliased' => 0.34;
74+
requires 'namespace::autoclean' => 0.31;
75+
requires 'utf8::all' => 0.024;
5676

0 commit comments

Comments
 (0)