Closed gep13 closed 5 years ago
@daveaglick I have been giving this some thought...
I found this:
Which results in quite a nice gallery in the blog post:
http://www.gep13.co.uk/blog/calgary-zoo
Am I right in saying that I could create a Wyam Module (terminology might be wrong here) that would look at a blog post for a special string, perhaps something like %flickr:72157673850557294% where the number if the flickr photoset number, and then have it be replaced with the necessary html/script to include the gallery in the blog post?
Or, did you have something else in mind when I suggested a gallery the other day?
That was along the lines of what I was thinking - there's a number of great JavaScript-based gallery widgets that could be implemented in this way.
A broader challenge is coming up with a systematic way for modules to deal with JavaScript. I've so far been hesitant to add modules like this, not because I don't think they're valuable (they certainly are), but because I haven't come up with a good mechanism. Specifically, there's usually up to three parts of a JavaScript component that has to end up in the final HTML:
<script>
tag in the <head>
that loads the JavaScript library.<div>
).Coordinating the insertion of all three parts from a module with either a theme or arbitrary templates turns into a question of knowing where to stick the content inside each input document.
You could also make the argument that the rendering of something like a gallery is entirely a theme/template concern and the module should just be responsible for providing the necessary data. This approach has a number of advantages like allowing different themes to use totally different JavaScript gallery libraries. In this mode, the module would just create a series of documents with information about the images that should go in the gallery. Then a theme would be responsible for inserting whatever JavaScript was needed to show a gallery from that information.
Now that the creative juices are flowing though, I'm also thinking about a totally new way of handling this. Something like a single Snippets
module that would load snippets and perform substitution of content for matches. You could define snippets in various ways (in the config file, using a library, maybe even using YAML, JSON, or text files) and there would be a bunch built in. The Snippets
module would take the input documents, look for any snippet keys in their content, and replace those keys with the snippet text combined with any arguments. The snippet could look something like what you've put above %flickr:72157673850557294%
. We'd need support for multiple arguments to the snippet, escaping the argument delimiter, etc.
A nice benefit of this is that it would be totally template engine agnostic. I plan on integrating Liquid, Handlebars, and others eventually and I'd want this to work across them all. With the right syntax, it would also be independent of HTML - I.e., I wouldn't want to make something like TagHelpers that relies on an assumption about the syntax of the content to work like that it's HTML.
This idea could be really cool - let me think on it a bit more...
I'm starting to form a design for this, and the more I think about it, the more I like the idea of snippets. It solves a number use cases where I don't think modules are quite appropriate. In addition to galleries, the concept could be applied to commenting systems like Disqus, Google Analytics, or really any JavaScript library.
Here's my current thinking (want to get it down before it all leaks out of my head):
ISnippet
interface.string Render(string[] args, IDocument doc, IExecutionContext ctx)
.IModule
implementations, including being able to put them directly in the config file or in NuGet packages.Snippets.Add(string name, string result)
Snippets.Add(string name, ContextConfig result)
Snippets.Add(string name, DocumentConfig result)
Snippets
module is run. The new module will be responsible for finding snippets in the content of each input document and replacing them with the rendered snippet result.
Stream
without having to copy the whole stream to a string.%%
and if %%
needs to appear inside the snippet arguments.Wyam.Core
/Wyam.Common
will contain support for snippets and the Snippets
module. A library of actual snippets will go inside a Wyam.Snippets
library (or maybe they'll go in Wyam.Html
since all the initial snippets will be focused on JavaScript and HTML) that contains a dependency to the command line parsing library. This library will be included in all appropriate recipe packages.%%gallery head%%
might render the <script ...>
tag that goes in the head of the page and %%gallery%%
might render the actual gallery logic.I changed the issue title to track the broader implementation, but we'll make galleries one of the initial snippets.
@daveaglick all this sounds good to me! đź‘Ť
will snippites also support something content dependent like:
%%BEGIN%%
Some content
%%END%%
So the snippet will have access on whatever is between those two tags (in this case Some content), simular to a div tag in html.
Currently I use html tags in markdown and the client side JavaScript is replacing this tags with a more complex structure that allows hiding and animating the content. But compile time support would be much better.
Note to self - take a look at the Hexo tag plugins which are quite similar to this concept: https://hexo.io/docs/tag-plugins.html
Specifically, there's already a request for something like the gist plugin
Another note to self: snippets need a way to add files to the output folder. Use case is a snippet for a JavaScript widget that requires a separate JavaScript file. Two ways to handle - the Snippet
module could either write to the output file system, or it could output additional documents intended to be passed to WriteFiles
(currently prefer the former, but will need to think about it).
As discussed on Gitter - syntax should be really simple to make easy snippet cases short and sweet. For example, should favor something like %%gist:id%%
over %%gist -GistIdentifer XXX%%
. One thought is to make the first :
a delimiter between the snippet name and just pass the rest to the snippet implementation to handle however it wants to (or omit the colon to pass an empty or null string to the snippet).
Another interesting idea from Gitter - recipes/themes could support a global metadata array value called something like JsSnippets
. Then you wouldn't even need to add the snippet to the content - the theme would do that by iterating the global metadata collection and adding the snippets for you in the appropriate place.
It's well past time to review this issue again. I've renamed it to shortcodes since that term is now used by both Wordpress and Hugo and seems to be gaining general acceptance. Not to mention snippets already has another meaning when referring to code (see #692 and #173).
Here's my current thinking on design taking into account everything discussed above:
IShortcode
or IContentShortcode
interface.IShortcode
will have a single method string Render(string[] args, IDocument doc, IExecutionContext ctx)
.
IContentShortcode
will have a single method string Render(string[] args, string content, IDocument doc, IExecutionContext ctx)
.IModule
implementations, including being able to put them directly in the config file or in NuGet packages.Shortcodes.Add(string name, string output)
Shortcodes.Add(string name, ContextConfig render)
Shortcodes.Add(string name, DocumentConfig render)
Shortcodes.Add(string name, Func<string[], object> render)
Shortcodes.Add(string name, Func<string[], IExecutionContext, object> render)
Shortcodes.Add(string name, Func<string[], IDocument, IExecutionContext, object> render)
Shortcodes.Add(string name, Func<string[], string, object> render)
Shortcodes.Add(string name, Func<string[], string, IExecutionContext, object> render)
Shortcodes.Add(string name, Func<string[], string, IDocument, IExecutionContext, object> render)
Shortcodes
module is run. The new module will be responsible for finding shortcodes in the content of each input document and replacing them with the rendered shortcode result.
Shortcodes
phase in between Process
and Render
(which could be opted-out). Or maybe a more general Prepare
or Prerender
phase where the Shortcode
module would generally go.Stream
without having to copy the whole stream to a string.%% MyShortcode foo bar %%
%%
and if %%
needs to appear inside the shortcode arguments.IContentShortcode
and related delegates require opening and closing (designated with /
) with content in between:%% myshortcode foo bar %% fizz buzz %%/ myshortcode %%
%% myshortcode foo bar /%%
%%
syntax and would welcome alternatives with rationale:{% .. %}
{{% ... %}}
to indicate a shortcode that should be processed by the template engine and {{< ... >}}
to indicate one that does not need further processing.[ ... ]
%% foo -fizz buzz %%
(like CLI parsing).%% foo fizz=buzz %%
(like XML attributes).Shortcode
and ContentShortcode
abstract base class that includes some standard parameter parsing support.
Shortcode
class static, put it in a helper instead, or add it to IExecutionContext
so that the delegate style shortcodes can use it too.Wyam.Core
/Wyam.Common
will contain support for shortcodes and the Shortcode
module.
Wyam.Shortcodes
library (or maybe in Wyam.Code
, or both, depending on how many external dependencies we end up using).%% gallery head %%
might render the <script ...>
tag that goes in the head of the page and %% gallery %%
might render the actual gallery logic.Open questions
%%
good?Shortcode
module be run - before or after templates (or both/either)?I continue to think hard about syntax, particularly as it relates to when the Shortcode
module is run and if the shortcode should “pass through” any template rendering (or not). This is a challenge because if you don’t want a template renderer to interfere with the shortcode definition or it’s content so it hits the shortcode module verbatim afterwards, you need to make the shortcode invisible to the template renderer. Being able to do so is necessary for shortcodes that need access to the post-rendered content of the page, for example a “table of contents” shortcode that looks for all headers on the page and constructs a table of contents. If that shortcode is evaluated before the Markdown engine, it won’t know anything about the final HTML content and headers.
Here’s my current thinking (I’ll update the design comment above too once I’m happy with this part of the design):
Shortcodes use a syntax similar to Hugo for consistency, but note that the behavior is quite different. The start and end delimiters are{{
and }}
by default but can be customized using the ShortcodeStartDelimiter
and ShortcodeEndDelimiter
settings (for the remainder of the description below, the default delimiters will be used for illustration).
Some shortcodes support content. This is indicated by pairing shortcode declarations with the same shortcode name and using a slash in the closing declaration:
{{% myshortcode %}} some content {{%/ myshortcode %}}
Any shortcode that either doesn’t support content or doesn’t require it for a specific usage should be closed with a slash on the closing delimiter:
{{% myshortcode /%}}
If you include content in a shortcode that doesn’t support it, no error will be generated and the content will be ignored.
Some shortcodes support parameters. These follow the shortcode name inside the shortcode definition and can follow whatever convention the shortcode specifies.
{{% myshortcode param1 param2 /%}}
If you include parameters in a shortcode that doesn’t support them, no error will be generated and the parameters will be ignored. However, if the shortcode is expecting parameters and you specify them incorrectly, an error will be generated.
Shortcodes can be evaluated before template engines (“prerender”) or after template engines (“postrender”). There are three different shortcode syntax conventions depending on the phase in which you want the shortcode to be evaluated and how it interacts with the content around it.
Make sure to pay attention to the intended phase for a given shortcode. Specifying a shortcode in an unintended phase won’t directly generate an error but will probably result in unexpected behavior and results. For example, if a shortcode outputs Markdown it’s probably intended to be evaluated in the prerender phase. Evaluating it in the postrender phase won’t hurt anything, but it also won’t evaluate the Markdown as such because the Markdown engine will have already been run.
Sometimes shortcodes need to be run before any other template processing. These are specified using a %
character inside the delimiters: {{% ... /%}}
. For example, if you evaluate a shortcode during this phase and it results in Markdown output, that Markdown output will be further processed by the Markdown engine. Because these shortcodes are evaluated very early in the process before any other template engines are run, you generally don’t need to worry about how the shortcode syntax will interact with any template engines that might process the document content.
To indicate that a shortcode should be evaluated after template engines have been run, you use !
inside the delimiters: {{! ... /!}}
.
One of the challenges of evaluating shortcodes after template rendering is that the template engine might try to interpret the shortcode definition and any content as part of the template. One way to avoid this is to surround the shortcode in a comment appropriate to the template engine(s). This can be indicate with a *
character inside the shortcode delimiters: {{* ... /*}}
. This type of shortcode will remove any text immediately before and after the shortcode up to the nearest white space when the shortcode is evaluated.
For example, the following shortcode will be ignored by the Razor template engine even though it contains Razor syntax because it’s surrounded by Razor comments:
<div> @*{{* myshortcode *}} @foo {{*/ myshortcode *}}*@ </div>
Assuming the shortcode just outputs whatever content it contains, this would result in the following:
<div> @foo </div>
By default this convention will not remove the proceeding and trailing whitespace. If you want the shortcode evaluation to also remove the surrounding whitespace characters, you can use #
:
<div> @*{{# myshortcode #}} @foo {{#/ myshortcode #}}*@ </div>
This will result in the following:
<div>@foo</div>
In some rare cases you may need to escape the shortcode entirely because you don’t want to evaluate the shortcode as such. In this situation you can use the special Raw
shortcode which outputs the shortcode content verbatim.
For example:
{{% raw %}}{{% foobar /%}}{{%/ raw %}}
will result in output of:
{{% foobar /%}}
Shortcodes cannot be nested. Any shortcodes inside the content of other shortcodes will be evaluated as text content of the outer shortcode. Note that if the outer shortcode is evaluated in the prerender phase and results in output that contains a postrender shortcode, the postrender shortcode in the output will be evaluated as such in the postrender phase.
Sent with GitHawk
Another update: after working on implementation, I've confident I could do what's described above. However, I'm much less certain I should do it. I'm now thinking it's overly complex and will create more confusion than a less-robust solution.
I'm now heavily leaning towards only running shortcodes before template rendering (including Markdown). No special syntax for pre vs post rendering. This will limit some of our use cases - for example, we won't be able to write a shortcode like {{% toc %}}
that analyzes the headers in a document because it'll get evaluated before there's even any HTML when all the headers are Markdown like #
or Razor has a chance to include partials, etc. The tradeoff is a simpler feature that's easier to understand, use, and implement that does one thing (text macros) and does it well.
Some other points:
IMetadata
(but not the entire IDocument
) being processed and can specify additional metadata to be added to the document.
Scripts
metadata value that eventually gets used by Razor to output scripts in the <head>
for example (in which case the Scripts
metadata value would be a convention supported by the recipe/theme)IDocument
until all shortcodes are processedSome more quick thoughts as I work on it:
<?% name foo bar ?>xyz<?%/ name ?>
=
that outputs whatever is in a metadata value would be cool: <?%= key /?>
. If there’s shortcode content, it could be the fallback if the key isn’t found. If the metadata value is enumerable, each item should be output (how to specify delimiter?)I'm not sure if I get this right. Shortcode will not be an module that runs somewhere in a pipeline.
Otherwise the execution time would be defined by where in the pipeline it is added. I think my shortcoming has its origin that I had never worked with templates, which are mentioned in your descriptions.
So how could I integrate it in my wyam build, if I don't use templates?
@LokiMidgard
I'm not sure if I get this right
As I continue to work on this feature, the thoughts in the issue here keep falling out of date.
Shortcode will not be an module that runs somewhere in a pipeline.
That's not true anymore - shortcodes will absolutely be a module (called ProcessShortcodes
). It'll be run after Markdown, Razor, etc. template processing in the recipes, though you could run it at any point in your pipeline like any other module when writing a custom config. There are tradeoffs to running it before vs. after template processing.
So how could I integrate it in my wyam build
The module scans the content of all input documents looking for shortcodes with the syntax <?# shortcode_name other arguments ?>
. If any are found, it'll execute the shortcode(s) and replace with definition with the shortcode result content. In a custom config you could run this against any sort of document, including text files you just read in.
Shortcode support was just merged into develop from my working branch. The architecture is essentially done, now I just need to implement a few more actual shortcodes.
Here's where we landed:
<?# name args /?>
or <?# name args ?>content<?#/ name ?>
if the shortcode has content.Shortcodes
is responsible for parsing and evaluating shortcodes within documents.Shortcodes
module has been added to the blog and docs recipes and is executed at the end of the rendering pipelines after Markdown, Razor, etc. has been processed.<?# meta key /?>
or <?#= key /?>
special syntax) and including files (<?# include file.html /?>
). Many more to come prior to release.I've got a decent set of initial shortcodes for the next version and will continue to add more. Just need to write some documentation and get the release out.
@daveaglick sweet!! Looking forward to taking this for a spin!
@gep13 This issue kind of got hijacked by the general-purpose shortcode functionality. Please do open a new issue to specifically track a gallery shortcode if that’s something you’re still interested in.
Given a set of photos, it would be good to be able to generate a gallery for showing the photos in say a blog post.
As discussed on Gitter.