Merge branch 'master' of git://factorcode.org/git/factor
commit
ae6e3ee47c
|
@ -46,6 +46,6 @@ ARTICLE: "ascii" "ASCII character classes"
|
|||
{ $subsection printable? }
|
||||
{ $subsection control? }
|
||||
{ $subsection quotable? }
|
||||
"Modern applications should use Unicode 5.0 instead (" { $vocab-link "unicode" } ")." ;
|
||||
"Modern applications should use Unicode 5.0 instead (" { $vocab-link "unicode.categories" } ")." ;
|
||||
|
||||
ABOUT: "ascii"
|
||||
|
|
|
@ -1 +1 @@
|
|||
extensions
|
||||
concurrency
|
||||
|
|
|
@ -1 +1 @@
|
|||
extensions
|
||||
concurrency
|
||||
|
|
|
@ -19,7 +19,7 @@ HELP: SUPER->
|
|||
ARTICLE: "objc-calling" "Calling Objective C code"
|
||||
"Before an Objective C class can be used, it must be imported; by default, a small set of common classes are imported automatically, but additional classes can be imported as needed."
|
||||
{ $subsection import-objc-class }
|
||||
"Every imported Objective C class has as corresponding class word in the " { $vocab-link "objc-classes" } " vocabulary. Class words push the class object in the stack, allowing class methods to be invoked."
|
||||
"Every imported Objective C class has as corresponding class word in the " { $vocab-link "cocoa.classes" } " vocabulary. Class words push the class object in the stack, allowing class methods to be invoked."
|
||||
$nl
|
||||
"Messages can be sent to classes and instances using a pair of parsing words:"
|
||||
{ $subsection POSTPONE: -> }
|
||||
|
|
|
@ -4,7 +4,7 @@ kernel vectors arrays effects sequences ;
|
|||
IN: compiler.generator
|
||||
|
||||
ARTICLE: "generator" "Compiled code generator"
|
||||
"Most of the words in the " { $vocab-link "generator" } " vocabulary are internal to the compiler and user code has no reason to call them."
|
||||
"Most of the words in the " { $vocab-link "compiler.generator" } " vocabulary are internal to the compiler and user code has no reason to call them."
|
||||
$nl
|
||||
"Debugging information can be enabled or disabled; this hook is used by " { $link "tools.deploy" } ":"
|
||||
{ $subsection compiled-stack-traces? }
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -1,2 +1,2 @@
|
|||
concurrency
|
||||
enterprise
|
||||
extensions
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -0,0 +1 @@
|
|||
concurrency
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -312,13 +312,13 @@ ARTICLE: "cookbook-pitfalls" "Pitfalls to avoid"
|
|||
{ "When a source file uses two vocabularies which define words with the same name, the order of the vocabularies in the " { $link POSTPONE: USE: } " or " { $link POSTPONE: USING: } " forms is important. The parser prints warnings when vocabularies shadow words from other vocabularies; see " { $link "vocabulary-search-shadow" } ". The " { $vocab-link "qualified" } " vocabulary implements qualified naming, which can be used to resolve ambiguities." }
|
||||
{ "If a literal object appears in a word definition, the object itself is pushed on the stack when the word executes, not a copy. If you intend to mutate this object, you must " { $link clone } " it first. See " { $link "syntax-literals" } "." }
|
||||
{ "For a discussion of potential issues surrounding the " { $link f } " object, see " { $link "booleans" } "." }
|
||||
{ "Factor's object system is quite flexible. Careless usage of union, mixin and predicate classes can lead to similar problems to those caused by ``multiple inheritance'' in other languages. In particular, it is possible to have two classes such that they have a non-empty intersection and yet neither is a subclass of the other. If a generic word defines methods on two such classes, method precedence is undefined for objects that are instances of both classes. See " { $link "method-order" } " for details." }
|
||||
{ "Factor's object system is quite flexible. Careless usage of union, mixin and predicate classes can lead to similar problems to those caused by ``multiple inheritance'' in other languages. In particular, it is possible to have two classes such that they have a non-empty intersection and yet neither is a subclass of the other. If a generic word defines methods on two such classes, various disambiguation rules are applied to ensure method dispatch remains deterministic, however they may not be what you expect. See " { $link "method-order" } " for details." }
|
||||
{ "Performance-sensitive code should have a static stack effect so that it can be compiled by the optimizing word compiler, which generates more efficient code than the non-optimizing quotation compiler. See " { $link "inference" } " and " { $link "compiler" } "."
|
||||
$nl
|
||||
"This means that methods defined on performance sensitive, frequently-called core generic words such as " { $link nth } " should have static stack effects which are consistent with each other, since a generic word will only have a static stack effect if all methods do."
|
||||
$nl
|
||||
"Unit tests for the " { $vocab-link "inference" } " vocabulary can be used to ensure that any methods your vocabulary defines on core generic words have static stack effects:"
|
||||
{ $code "\"inference\" test" }
|
||||
"Unit tests for the " { $vocab-link "stack-checker" } " vocabulary can be used to ensure that any methods your vocabulary defines on core generic words have static stack effects:"
|
||||
{ $code "\"stack-checker\" test" }
|
||||
"In general, you should strive to write code with inferable stack effects, even for sections of a program which are not performance sensitive; the " { $link infer. } " tool together with the optimizing compiler's error reporting can catch many bugs ahead of time." }
|
||||
{ "Be careful when calling words which access variables from a " { $link make-assoc } " which constructs an assoc with arbitrary keys, since those keys might shadow variables." }
|
||||
{ "If " { $link run-file } " throws a stack depth assertion, it means that the top-level form in the file left behind values on the stack. The stack depth is compared before and after loading a source file, since this type of situation is almost always an error. If you have a legitimate need to load a source file which returns data in some manner, define a word in the source file which produces this data on the stack and call the word after loading the file." }
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
USING: help help.markup help.syntax help.definitions help.topics
|
||||
namespaces words sequences classes assocs vocabs kernel arrays
|
||||
prettyprint.backend kernel.private io generic math system
|
||||
strings sbufs vectors byte-arrays
|
||||
quotations io.streams.byte-array
|
||||
classes.builtin parser lexer classes.predicate classes.union
|
||||
classes.intersection classes.singleton classes.tuple ;
|
||||
strings sbufs vectors byte-arrays quotations
|
||||
io.streams.byte-array classes.builtin parser lexer
|
||||
classes.predicate classes.union classes.intersection
|
||||
classes.singleton classes.tuple tools.vocabs.browser ;
|
||||
IN: help.handbook
|
||||
|
||||
ARTICLE: "conventions" "Conventions"
|
||||
|
@ -139,7 +139,7 @@ ARTICLE: "collections" "Collections"
|
|||
{ $subsection "heaps" }
|
||||
{ $subsection "graphs" }
|
||||
{ $subsection "buffers" }
|
||||
"There are many other collections in " { $snippet "extra/" } ", such as " { $vocab-link "disjoint-sets" } ", " { $vocab-link "persistent-vectors" } ", and " { $vocab-link "tuple-arrays" } "." ;
|
||||
"There are also many other vocabularies tagged " { $link T{ vocab-tag { name "collections" } } } " in the library." ;
|
||||
|
||||
USING: io.encodings.utf8 io.encodings.utf16 io.encodings.binary io.encodings.ascii io.files ;
|
||||
|
||||
|
@ -244,7 +244,8 @@ ARTICLE: "handbook-language-reference" "Language reference"
|
|||
{ $subsection "program-org" }
|
||||
{ $subsection "numbers" }
|
||||
{ $subsection "collections" }
|
||||
{ $subsection "io" } ;
|
||||
{ $subsection "io" }
|
||||
"Vocabularies tagged " { $link T{ vocab-tag { name "extensions" } } } " implement various additional language abstractions." ;
|
||||
|
||||
ARTICLE: "handbook-environment-reference" "Environment reference"
|
||||
{ $subsection "prettyprint" }
|
||||
|
|
|
@ -61,10 +61,10 @@ IN: help.lint
|
|||
: vocab-exists? ( name -- ? )
|
||||
dup vocab swap "all-vocabs" get member? or ;
|
||||
|
||||
: check-modules ( word element -- )
|
||||
nip \ $vocab-link swap elements [
|
||||
: check-modules ( element -- )
|
||||
\ $vocab-link swap elements [
|
||||
second
|
||||
vocab-exists? [ "Missing vocabulary" throw ] unless
|
||||
vocab-exists? [ "$vocab-link to non-existent vocabulary" throw ] unless
|
||||
] each ;
|
||||
|
||||
: check-rendering ( word element -- )
|
||||
|
@ -91,7 +91,7 @@ M: help-error error.
|
|||
2dup check-examples
|
||||
2dup check-values
|
||||
2dup check-see-also
|
||||
2dup check-modules
|
||||
2dup nip check-modules
|
||||
2dup drop check-rendering
|
||||
] assert-depth 2drop
|
||||
] check-something
|
||||
|
@ -101,12 +101,20 @@ M: help-error error.
|
|||
|
||||
: check-article ( article -- )
|
||||
[
|
||||
[ dup check-rendering ] assert-depth drop
|
||||
dup article-content [
|
||||
2dup check-modules check-rendering
|
||||
] assert-depth 2drop
|
||||
] check-something ;
|
||||
|
||||
: files>vocabs ( -- assoc )
|
||||
vocabs
|
||||
[ [ [ vocab-docs-path ] keep ] H{ } map>assoc ]
|
||||
[ [ [ vocab-source-path ] keep ] H{ } map>assoc ]
|
||||
bi assoc-union ;
|
||||
|
||||
: group-articles ( -- assoc )
|
||||
articles get keys
|
||||
vocabs [ dup vocab-docs-path swap ] H{ } map>assoc
|
||||
files>vocabs
|
||||
H{ } clone [
|
||||
'[
|
||||
dup >link where dup
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
! Copyright (C) 2008 Your name.
|
||||
! See http://factorcode.org/license.txt for BSD license.
|
||||
USING: help.markup help.syntax io.streams.string kernel strings
|
||||
urls lcs inspector present io ;
|
||||
IN: html.components
|
||||
|
||||
HELP: checkbox
|
||||
{ $class-description "Checkbox components render a boolean value. The " { $slot "label" } " slot must be set to a string." } ;
|
||||
|
||||
HELP: choice
|
||||
{ $class-description "Choice components render a popup menu or list box with either single or multiple selection."
|
||||
$nl
|
||||
"The " { $slot "multiple" } " slot determines whether multiple elements may be selected at once; if this is set to a true value, then the component value must be a sequence of strings, otherwise it must be a single string."
|
||||
$nl
|
||||
"The " { $slot "size" } " slot determines the number of items visible at one time; if neither this nor " { $slot "multiple" } " is set, the component is rendered as a popup menu rather than a list."
|
||||
$nl
|
||||
"The " { $slot "choices" } " slot determines all possible choices which may be selected. It names a value, rather than storing the choices directly." } ;
|
||||
|
||||
HELP: code
|
||||
{ $class-description "Code components render string value with the " { $vocab-link "xmode.code2html" } " syntax highlighting vocabulary. The " { $slot "mode" } " slot names a value holding an XMode mode name." } ;
|
||||
|
||||
HELP: field
|
||||
{ $class-description "Field components display a one-line editor for a string value. The " { $slot "size" } " slot determines the maximum displayed width of the field." } ;
|
||||
|
||||
HELP: password
|
||||
{ $class-description "Password field components display a one-line editor which obscures the user's input. The " { $slot "size" } " slot determines the maximum displayed width of the field. Unlike other components, on failed validation, the contents of a password field are not sent back to the client. This is a security feature, intended to avoid revealing the password to potential snoopers who use the " { $strong "View Source" } " feature." } ;
|
||||
|
||||
HELP: textarea
|
||||
{ $class-description "Text area components display a multi-line editor for a string value. The " { $slot "rows" } " and " { $slot "cols" } " properties determine the size of the text area." } ;
|
||||
|
||||
HELP: link
|
||||
{ $description "Link components render a link to an object stored at a value, with the link title and URL determined by the " { $link link-title } " and " { $link link-href } " generic words." } ;
|
||||
|
||||
HELP: link-title
|
||||
{ $values { "obj" object } { "string" string } }
|
||||
{ $description "Outputs the title to render for a link to the object." } ;
|
||||
|
||||
HELP: link-href
|
||||
{ $values { "obj" object } { "url" "a " { $link string } " or " { $link url } } }
|
||||
{ $description "Outputs the URL to render for a link to the object." } ;
|
||||
|
||||
ARTICLE: "html.components.links" "Link components"
|
||||
"Link components render a link to an object."
|
||||
{ $subsection link }
|
||||
"The link title and URL are determined by passing the object to a pair of generic words:"
|
||||
{ $subsection link-title }
|
||||
{ $subsection link-href }
|
||||
"The generic words provide methods on the " { $link string } " and " { $link url } " classes which treat the object as a URL. New methods can be defined for rendering links to custom data types." ;
|
||||
|
||||
HELP: comparison
|
||||
{ $description "Comparison components render diffs output by the " { $link diff } " word." } ;
|
||||
|
||||
HELP: farkup
|
||||
{ $description "Farkup components render " { $link "farkup" } "." } ;
|
||||
|
||||
HELP: hidden
|
||||
{ $description "Hidden components render as a hidden form field. For example, a page for editing a weblog post might contain a hidden field with the post ID." } ;
|
||||
|
||||
HELP: html
|
||||
{ $description "HTML components render HTML verbatim, without any escaping. Care must be taken to only render trusted input, to avoid cross-site scripting attacks." } ;
|
||||
|
||||
HELP: inspector
|
||||
{ $description "Inspector components render an arbitrary object by passing it to the " { $link describe } " word." } ;
|
||||
|
||||
HELP: label
|
||||
{ $description "Label components render an object as a piece of text by passing it to the " { $link present } " word." } ;
|
||||
|
||||
HELP: render
|
||||
{ $values { "name" "a value name" } { "renderer" "a component renderer" } }
|
||||
{ $description "Renders an HTML component to the " { $link output-stream } "." } ;
|
||||
|
||||
HELP: render*
|
||||
{ $values { "value" "a value" } { "name" "a value name" } { "renderer" "a component renderer" } }
|
||||
{ $contract "Renders an HTML component to the " { $link output-stream } "." } ;
|
||||
|
||||
ARTICLE: "html.components" "HTML components"
|
||||
"The " { $vocab-link "html.components" } " vocabulary provides various HTML form components."
|
||||
$nl
|
||||
"Most web applications can use the " { $vocab-link "html.templates.chloe" } " templating framework instead of using this vocabulary directly. Where maximum flexibility is required, this vocabulary can be used together with the " { $vocab-link "html.templates.fhtml" } " templating framework."
|
||||
$nl
|
||||
"Rendering components:"
|
||||
{ $subsection render }
|
||||
"Components render a named value, and the name of the value is passed in every time the component is rendered, rather than being associated with the component itself. Named values are taken from the current HTML form (see " { $link "html.forms" } ")."
|
||||
$nl
|
||||
"Component come in two varieties: singletons and tuples. Components with no configuration are singletons; they do not have to instantiated, rather the class word represents the component. Tuple components have to be instantiated and offer configuration options."
|
||||
$nl
|
||||
"Singleton components:"
|
||||
{ $subsection hidden }
|
||||
{ $subsection link }
|
||||
{ $subsection inspector }
|
||||
{ $subsection comparison }
|
||||
{ $subsection html }
|
||||
"Tuple components:"
|
||||
{ $subsection field }
|
||||
{ $subsection password }
|
||||
{ $subsection textarea }
|
||||
{ $subsection choice }
|
||||
{ $subsection checkbox }
|
||||
{ $subsection code }
|
||||
{ $subsection farkup }
|
||||
"Creating custom components:"
|
||||
{ $subsection render* }
|
||||
"Custom components can emit HTML using the " { $vocab-link "html.elements" } " vocabulary." ;
|
||||
|
||||
ABOUT: "html.components"
|
|
@ -9,7 +9,7 @@ xmode.code2html lcs.diff2html farkup
|
|||
html.elements html.streams html.forms ;
|
||||
IN: html.components
|
||||
|
||||
GENERIC: render* ( value name render -- )
|
||||
GENERIC: render* ( value name renderer -- )
|
||||
|
||||
: render ( name renderer -- )
|
||||
prepare-value
|
||||
|
@ -146,6 +146,9 @@ M: code render*
|
|||
! Farkup component
|
||||
TUPLE: farkup no-follow disable-images parsed ;
|
||||
|
||||
: <farkup> ( -- farkup )
|
||||
farkup new ;
|
||||
|
||||
: string>boolean ( string -- boolean )
|
||||
{
|
||||
{ "true" [ t ] }
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
IN: html.elements
|
||||
USING: help.markup help.syntax io present ;
|
||||
|
||||
ARTICLE: "html.elements" "HTML elements"
|
||||
"The " { $vocab-link "html.elements" } " vocabulary provides words for writing HTML tags to the " { $link output-stream } " with a familiar look and feel in the code."
|
||||
$nl
|
||||
"HTML tags can be used in a number of different ways. The simplest is a tag with no attributes:"
|
||||
{ $code "<p> \"someoutput\" write </p>" }
|
||||
"In the above, " { $link <p> } " will output the opening tag with no attributes. and " { $link </p> } " will output the closing tag."
|
||||
{ $code "<p \"red\" =class p> \"someoutput\" write </p>" }
|
||||
"This time the opening tag does not have the '>'. Any attribute words used between the calls to " { $link <p } " and " { $link p> } " will write an attribute whose value is the top of the stack. Attribute values can be any object supported by the " { $link present } " word."
|
||||
$nl
|
||||
"Values for attributes can be used directly without any stack operations. Assuming we have a string on the stack, all three of the below will output a link:"
|
||||
{ $code "<a =href a> \"Click me\" write </a>" }
|
||||
{ $code "<a \"http://\" prepend =href a> \"click\" write </a>" }
|
||||
{ $code "<a [ \"http://\" % % ] \"\" make =href a> \"click\" write </a>" }
|
||||
"Tags that have no ``closing'' equivalent have a trailing " { $snippet "tag/>" } " form:"
|
||||
{ $code "<input \"text\" =type \"name\" =name 20 =size input/>" }
|
||||
"For the full list of HTML tags and attributes, consult the word list for the " { $vocab-link "html.elements" } " vocabulary. In addition to HTML tag and attribute words, a few utilities are provided."
|
||||
$nl
|
||||
"Writing unescaped HTML to " { $vocab-link "html.streams" } ":"
|
||||
{ $subsection write-html }
|
||||
{ $subsection print-html }
|
||||
"Writing some common HTML patterns:"
|
||||
{ $subsection xhtml-preamble }
|
||||
{ $subsection simple-page }
|
||||
{ $subsection render-error } ;
|
||||
|
||||
ABOUT: "html.elements"
|
|
@ -9,45 +9,6 @@ urls math math.parser combinators present fry ;
|
|||
|
||||
IN: html.elements
|
||||
|
||||
! These words are used to provide a means of writing
|
||||
! formatted HTML to standard output with a familiar 'html' look
|
||||
! and feel in the code.
|
||||
!
|
||||
! HTML tags can be used in a number of different ways. The highest
|
||||
! level involves a similar syntax to HTML:
|
||||
!
|
||||
! <p> "someoutput" write </p>
|
||||
!
|
||||
! <p> will output the opening tag and </p> will output the closing
|
||||
! tag with no attributes.
|
||||
!
|
||||
! <p "red" =class p> "someoutput" write </p>
|
||||
!
|
||||
! This time the opening tag does not have the '>'. It pushes
|
||||
! a namespace on the stack to hold the attributes and values.
|
||||
! Any attribute words used will store the attribute and values
|
||||
! in that namespace. Before the attribute word should come the
|
||||
! value of that attribute.
|
||||
! The finishing word will print out the operning tag including
|
||||
! attributes.
|
||||
! Any writes after this will appear after the opening tag.
|
||||
!
|
||||
! Values for attributes can be used directly without any stack
|
||||
! operations:
|
||||
!
|
||||
! (url -- )
|
||||
! <a =href a> "Click me" write </a>
|
||||
!
|
||||
! (url -- )
|
||||
! <a "http://" prepend =href a> "click" write </a>
|
||||
!
|
||||
! (url -- )
|
||||
! <a [ "http://" % % ] "" make =href a> "click" write </a>
|
||||
!
|
||||
! Tags that have no 'closing' equivalent have a trailing tag/> form:
|
||||
!
|
||||
! <input "text" =type "name" =name "20" =size input/>
|
||||
|
||||
SYMBOL: html
|
||||
|
||||
: write-html ( str -- )
|
||||
|
@ -170,7 +131,7 @@ SYMBOL: html
|
|||
|
||||
: xhtml-preamble ( -- )
|
||||
"<?xml version=\"1.0\"?>" write-html
|
||||
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" write-html ;
|
||||
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">" write-html ;
|
||||
|
||||
: simple-page ( title quot -- )
|
||||
#! Call the quotation, with all output going to the
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
IN: html.forms
|
||||
USING: help.markup help.syntax strings quotations kernel assocs ;
|
||||
|
||||
HELP: <form>
|
||||
{ $values { "form" form } }
|
||||
{ $description "Creates a new form. Usually " { $link with-form } " is used instead." } ;
|
||||
|
||||
HELP: form
|
||||
{ $var-description "Variable holding current form. Bound by " { $link with-form } ", " { $link nest-form } " and " { $link begin-form } "." }
|
||||
{ $class-description "The class of HTML forms. New instances are created by " { $link <form> } "." } ;
|
||||
|
||||
HELP: with-form
|
||||
{ $values { "name" string } { "quot" quotation } }
|
||||
{ $description "Runs the quotation in a new dynamic scope with the " { $link form } " variable rebound to the form stored in the value named " { $snippet "name" } "." } ;
|
||||
|
||||
HELP: nest-form
|
||||
{ $values { "name" string } { "quot" quotation } }
|
||||
{ $description "Runs the quotation in a new dynamic scope with the " { $link form } " variable rebound to a new form, which is subsequently stored in the value named " { $snippet "name" } "." }
|
||||
{ $examples
|
||||
"The " { $vocab-link "webapps.pastebin" } " uses a form to display pastes; inside this form it nests another form for creating annotations, and fills in some default values for new annotations:"
|
||||
{ $code
|
||||
"<page-action>"
|
||||
" ["
|
||||
" validate-integer-id"
|
||||
" \"id\" value paste from-object"
|
||||
""
|
||||
" \"id\" value"
|
||||
" \"new-annotation\" ["
|
||||
" \"parent\" set-value"
|
||||
" mode-names \"modes\" set-value"
|
||||
" \"factor\" \"mode\" set-value"
|
||||
" ] nest-form"
|
||||
" ] >>init"
|
||||
}
|
||||
} ;
|
||||
|
||||
HELP: begin-form
|
||||
{ $description "Begins a new form." } ;
|
||||
|
||||
HELP: value
|
||||
{ $values { "name" string } { "value" object } }
|
||||
{ $description "Gets a form value. This word is used to get form field values after validation." } ;
|
||||
|
||||
HELP: set-value
|
||||
{ $values { "value" object } { "name" string } }
|
||||
{ $description "Sets a form value. This word is used to preset form field values before rendering." } ;
|
||||
|
||||
HELP: from-object
|
||||
{ $values { "object" object } }
|
||||
{ $description "Sets the current form's values to the object's slot values." }
|
||||
{ $examples
|
||||
"Here is a typical action implementation, which selects a golf course object from the database with the ID specified in the HTTP request, and renders a form with values from this object:"
|
||||
{ $code
|
||||
"<page-action>"
|
||||
""
|
||||
" ["
|
||||
" validate-integer-id"
|
||||
" \"id\" value <golf-course>"
|
||||
" select-tuple from-object"
|
||||
" ] >>init"
|
||||
""
|
||||
" { golf \"view-course\" } >>template"
|
||||
}
|
||||
} ;
|
||||
|
||||
HELP: to-object
|
||||
{ $values { "destination" object } { "names" "a sequence of value names" } }
|
||||
{ $description "Stores the given sequence of form values into the slots of the object having the same names. This word is used to extract form field values after validation." } ;
|
||||
|
||||
HELP: with-each-value
|
||||
{ $values { "name" string } { "quot" quotation } }
|
||||
{ $description "Calls the quotation with each element of the value named " { $snippet "name" } "; the value must be a sequence. The quotation is called in a new dynamic scope with the " { $snippet "index" } " and " { $snippet "value" } " values set to the one-based index, and the sequence element in question, respectively." }
|
||||
{ $notes "This word is used to implement the " { $snippet "t:each" } " tag of the " { $vocab-link "html.templates.chloe" } " templating system. It can also be called directly from " { $vocab-link "html.templates.fhtml" } " templates." } ;
|
||||
|
||||
HELP: with-each-object
|
||||
{ $values { "name" string } { "quot" quotation } }
|
||||
{ $description "Calls the quotation with each element of the value named " { $snippet "name" } "; the value must be a sequence. The quotation is called in a new dynamic scope where the object's slots become named values, as if " { $link from-object } " was called." }
|
||||
{ $notes "This word is used to implement the " { $snippet "t:bind-each" } " tag of the " { $vocab-link "html.templates.chloe" } " templating system. It can also be called directly from " { $vocab-link "html.templates.fhtml" } " templates." } ;
|
||||
|
||||
HELP: validation-failed?
|
||||
{ $values { "?" "a boolean" } }
|
||||
{ $description "Tests if validation of the current form failed." } ;
|
||||
|
||||
HELP: validate-values
|
||||
{ $values { "assoc" assoc } { "validators" "an assoc mapping value names to quotations" } }
|
||||
{ $description "Validates values in the assoc by looking up the corresponding validation quotation, and storing the results in named values of the current form." } ;
|
||||
|
||||
ARTICLE: "html.forms.forms" "HTML form infrastructure"
|
||||
"The below words are used to implement the " { $vocab-link "furnace.actions" } " vocabulary. Calling them directly is rarely necessary."
|
||||
$nl
|
||||
"Creating a new form:"
|
||||
{ $subsection <form> }
|
||||
"Variable holding current form:"
|
||||
{ $subsection form }
|
||||
"Working with forms:"
|
||||
{ $subsection with-form }
|
||||
{ $subsection begin-form }
|
||||
"Validation:"
|
||||
{ $subsection validation-error }
|
||||
{ $subsection validation-failed? }
|
||||
{ $subsection validate-values } ;
|
||||
|
||||
ARTICLE: "html.forms.values" "HTML form values"
|
||||
"Form values are a central concept in the Furnace framework. Web actions primarily concern themselves with validating values, marshalling values to a database, and setting values for display in a form."
|
||||
$nl
|
||||
"Getting and setting values:"
|
||||
{ $subsection value }
|
||||
{ $subsection set-value }
|
||||
{ $subsection from-object }
|
||||
{ $subsection to-object }
|
||||
"Iterating over values; these words are used by " { $vocab-link "html.templates.chloe" } " to implement the " { $snippet "t:each" } " and " { $snippet "t:bind-each" } " tags:"
|
||||
{ $subsection with-each-value }
|
||||
{ $subsection with-each-object }
|
||||
"Nesting a form inside another form as a value:"
|
||||
{ $subsection nest-form } ;
|
||||
|
||||
ARTICLE: "html.forms" "HTML forms"
|
||||
"The " { $vocab-link "html.forms" } " vocabulary implements support for rendering and validating HTML forms. The definition of a " { $emphasis "form" } " is a bit more general than the content of an " { $snippet "<form>" } " tag. Namely, a page which displays a database record without offering any editing capability is considered a form too; it consists entirely of read-only components."
|
||||
$nl
|
||||
"This vocabulary is an integral part of the " { $vocab-link "furnace" } " web framework. The " { $vocab-link "html.templates.chloe" } " vocabulary uses the HTML form words to implement various template tags. The words are also often used directly from web action implementations."
|
||||
$nl
|
||||
"This vocabulary can be used without either the Furnace framework or the HTTP server; for example, as part of a static HTML generation tool."
|
||||
{ $subsection "html.forms.forms" }
|
||||
{ $subsection "html.forms.values" } ;
|
||||
|
||||
ABOUT: "html.forms"
|
|
@ -102,5 +102,5 @@ C: <validation-error> validation-error
|
|||
dup validation-error? [ form get t >>validation-failed drop ] when
|
||||
swap set-value ;
|
||||
|
||||
: validate-values ( assoc validators -- assoc' )
|
||||
: validate-values ( assoc validators -- )
|
||||
swap '[ [ dup _ at ] dip validate-value ] assoc-each ;
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
IN: html.streams
|
||||
USING: help.markup help.syntax kernel strings io io.styles
|
||||
quotations ;
|
||||
|
||||
HELP: browser-link-href
|
||||
{ $values { "presented" object } { "href" string } }
|
||||
{ $contract "Outputs a link to a page displaying a presentation of the given object. This word is called when " { $link write-object } " is called on " { $link html-stream } " instances." } ;
|
||||
|
||||
HELP: html-stream
|
||||
{ $class-description "A formatted output stream which emits HTML markup." } ;
|
||||
|
||||
HELP: <html-stream>
|
||||
{ $values { "stream" "an output stream" } { "html-stream" html-stream } }
|
||||
{ $description "Creates a new formatted output stream which emits HTML markup on " { $snippet "stream" } "." } ;
|
||||
|
||||
HELP: with-html-stream
|
||||
{ $values { "quot" quotation } }
|
||||
{ $description "Calls the quotation in a new dynamic scope with " { $link output-stream } " rebound to an " { $link html-stream } " wrapping the current " { $link output-stream } "." }
|
||||
{ $examples
|
||||
{ $example
|
||||
"USING: io io.styles html.streams ;"
|
||||
"[ \"Hello\" { { font-style bold } } format nl ] with-html-stream"
|
||||
"<span style='font-style: normal; font-weight: bold; '>Hello</span><br/>"
|
||||
}
|
||||
} ;
|
||||
|
||||
ARTICLE: "html.streams" "HTML streams"
|
||||
"The " { $vocab-link "html.streams" } " vocabulary provides a stream which implements " { $link "styles" } " by writing HTML markup to the wrapped stream."
|
||||
{ $subsection html-stream }
|
||||
{ $subsection <html-stream> }
|
||||
{ $subsection with-html-stream } ;
|
||||
|
||||
ABOUT: "html.streams"
|
|
@ -25,7 +25,7 @@ TUPLE: html-stream stream last-div ;
|
|||
: a-div ( stream -- straem )
|
||||
t >>last-div ; inline
|
||||
|
||||
: <html-stream> ( stream -- stream )
|
||||
: <html-stream> ( stream -- html-stream )
|
||||
f html-stream boa ;
|
||||
|
||||
<PRIVATE
|
||||
|
|
|
@ -0,0 +1,284 @@
|
|||
IN: html.templates.chloe
|
||||
USING: help.markup help.syntax html.components html.forms
|
||||
html.templates html.templates.chloe.syntax
|
||||
html.templates.chloe.compiler html.templates.chloe.components
|
||||
math xml.data strings quotations namespaces ;
|
||||
|
||||
HELP: <chloe> ( path -- template )
|
||||
{ $values { "path" "a pathname string without the trailing " { $snippet ".xml" } " extension" } { "template" chloe } }
|
||||
{ $description "Creates a new Chloe template object which can be passed to " { $link call-template } "." } ;
|
||||
|
||||
HELP: required-attr
|
||||
{ $values { "tag" tag } { "name" string } { "value" string } }
|
||||
{ $description "Extracts an attribute from a tag." }
|
||||
{ $errors "Throws an error if the attribute is not specified." } ;
|
||||
|
||||
HELP: optional-attr
|
||||
{ $values { "tag" tag } { "name" string } { "value" "a " { $link string } " or " { $link f } } }
|
||||
{ $description "Extracts an attribute from a tag." }
|
||||
{ $notes "Outputs " { $link f } " if the attribute is not specified." } ;
|
||||
|
||||
HELP: compile-attr
|
||||
{ $values { "value" "an attribute value" } }
|
||||
{ $description "Compiles code which pushes an attribute value previously extracted by " { $link required-attr } " or " { $link optional-attr } " on the stack. If the attribute value begins with " { $snippet "@" } ", compiles into code which pushes the a form value." } ;
|
||||
|
||||
HELP: CHLOE:
|
||||
{ $syntax "name definition... ;" }
|
||||
{ $values { "name" "the tag name" } { "definition" "a quotation with stack effect " { $snippet "( tag -- )" } } }
|
||||
{ $description "Defines compilation semantics for the Chloe tag named " { $snippet "tag" } ". The definition body receives a " { $link tag } " on the stack." } ;
|
||||
|
||||
HELP: CHLOE-SINGLETON:
|
||||
{ $syntax "CHLOE-SINGLETON: name" }
|
||||
{ $description "Defines a Chloe tag named " { $snippet "name" } " rendering an HTML component with singleton class word " { $snippet "name" } ". See " { $link "html.components" } "." } ;
|
||||
|
||||
HELP: CHLOE-TUPLE:
|
||||
{ $syntax "CHLOE-TUPLE: name" }
|
||||
{ $description "Defines a Chloe tag named " { $snippet "name" } " rendering an HTML component with tuple class word " { $snippet "name" } ". See " { $link "html.components" } "." } ;
|
||||
|
||||
HELP: reset-cache
|
||||
{ $description "Resets the compiled template cache. Chloe automatically recompiles templates when their file changes on disk, however other when redefining Chloe tags or words which they call, the cache may have to be reset manually for the changes to take effect." } ;
|
||||
|
||||
HELP: tag-stack
|
||||
{ $var-description "During template compilation, holds the current nesting of XML element names. Can be used from " { $link POSTPONE: CHLOE: } " definitions to make a custom tag behave differently depending on how it is nested." } ;
|
||||
|
||||
HELP: [write]
|
||||
{ $values { "string" string } }
|
||||
{ $description "Compiles code which writes the string when the template is called." } ;
|
||||
|
||||
HELP: [code]
|
||||
{ $values { "quot" quotation } }
|
||||
{ $description "Compiles the quotation. It will be called when the template is called." } ;
|
||||
|
||||
HELP: process-children
|
||||
{ $values { "tag" tag } { "quot" "a quotation with stack effect " { $snippet "( compiled-tag -- )" } } }
|
||||
{ $description "Compiles the tag. The quotation will be applied to the resulting quotation when the template is called." }
|
||||
{ $examples "See " { $link "html.templates.chloe.extend.tags.example" } " for an example which uses this word to implement a custom control flow tag." } ;
|
||||
|
||||
HELP: compile-children>string
|
||||
{ $values { "tag" tag } }
|
||||
{ $description "Compiles the tag so that the output it generates is written to a string, which is pushed on the stack when the template runs. A subsequent " { $link [code] } " call must be made with a quotation which consumes the string." } ;
|
||||
|
||||
HELP: compile-with-scope
|
||||
{ $values { "quot" quotation } }
|
||||
{ $description "Calls the quotation and wraps any output it compiles in a " { $link with-scope } " form." } ;
|
||||
|
||||
ARTICLE: "html.templates.chloe.tags.component" "Component Chloe tags"
|
||||
"The following Chloe tags correspond exactly to " { $link "html.components" } ". Singleton component tags do not allow any attributes. Attributes of tuple component tags are mapped to tuple slot values of the component instance."
|
||||
{ $table
|
||||
{ "Tag" "Component class" }
|
||||
{ { $snippet "t:checkbox" } { $link checkbox } }
|
||||
{ { $snippet "t:choice" } { $link choice } }
|
||||
{ { $snippet "t:code" } { $link code } }
|
||||
{ { $snippet "t:comparison" } { $link comparison } }
|
||||
{ { $snippet "t:farkup" } { $link farkup } }
|
||||
{ { $snippet "t:field" } { $link field } }
|
||||
{ { $snippet "t:hidden" } { $link hidden } }
|
||||
{ { $snippet "t:html" } { $link html } }
|
||||
{ { $snippet "t:inspector" } { $link inspector } }
|
||||
{ { $snippet "t:label" } { $link label } }
|
||||
{ { $snippet "t:link" } { $link link } }
|
||||
{ { $snippet "t:password" } { $link password } }
|
||||
{ { $snippet "t:textarea" } { $link textarea } }
|
||||
} ;
|
||||
|
||||
ARTICLE: "html.templates.chloe.tags.boilerplate" "Boilerplate Chloe tags"
|
||||
"The following Chloe tags interface with the HTML templating " { $link "html.templates.boilerplate" } "."
|
||||
$nl
|
||||
"The tags marked with (*) are only available if the " { $vocab-link "furnace.chloe-tags" } " vocabulary is loaded."
|
||||
{ $table
|
||||
{ { $snippet "t:title" } "Sets the title from a child template" }
|
||||
{ { $snippet "t:write-title" } "Renders the child's title from a master template" }
|
||||
{ { $snippet "t:style" } "Adds CSS markup from a child template" }
|
||||
{ { $snippet "t:write-style" } "Renders the children's CSS from a master template" }
|
||||
{ { $snippet "t:atom" } "Adds an Atom feed link from a child template (*)" }
|
||||
{ { $snippet "t:write-atom" } "Renders the children's list of Atom feed links (*)" }
|
||||
{ { $snippet "t:call-next-template" } "Calls the child template from a master template" }
|
||||
} ;
|
||||
|
||||
ARTICLE: "html.templates.chloe.tags.control" "Control-flow Chloe tags"
|
||||
"While most control flow and logic should be embedded in the web actions themselves and not in the template, Chloe templates do support a minimal amount of control flow."
|
||||
{ $table
|
||||
{ { $snippet "t:comment" } "All markup within a comment tag is ignored by the compiler." }
|
||||
{ { $snippet "t:bind" } { "Renders child content bound to a nested form named by the " { $snippet "t:name" } " attribute. See " { $link with-form } "." } }
|
||||
{ { $snippet "t:each" } { "Renders child content once for each element of the sequence in the value named by the " { $snippet "t:name" } " attribute. The sequence element and index are bound to the " { $snippet "value" } " and " { $snippet "index" } " values, respectively. See " { $link with-each-value } "." } }
|
||||
{ { $snippet "t:bind-each" } { "Renders child content once for each element of the sequence in the value named by the " { $snippet "t:name" } " attribute. The sequence element's slots are bound to values. See " { $link with-each-object } "." } }
|
||||
{ { $snippet "t:even" } { "Only valid inside a " { $snippet "t:each" } " or " { $snippet "t:bind-each" } ". Only renders child content if the " { $snippet "index" } " value is even." } }
|
||||
{ { $snippet "t:odd" } "As above, but only if the index value is odd." }
|
||||
{ { $snippet "t:if" } { "Renders child content if a boolean condition evaluates to true. The condition value is determined by the " { $snippet "t:code" } " or " { $snippet "t:value" } " attribute, exactly one of which must be specified. The former is a string of the form " { $snippet "vocabulary:word" } " denoting a word to execute with stack effect " { $snippet "( -- ? )" } ". The latter is a value name." } }
|
||||
} ;
|
||||
|
||||
ARTICLE: "html.templates.chloe.tags.form" "Chloe link and form tags"
|
||||
"The following tags are only available if the " { $vocab-link "furnace.chloe-tags" } " vocabulary is loaded."
|
||||
{ $table
|
||||
{ { $snippet "t:a" } { "Renders a link; extends the standard XHTML " { $snippet "a" } " tag by providing some integration with other web framework features. The following attributes are supported:"
|
||||
{ $list
|
||||
{ { $snippet "href" } " - a URL. If it begins with " { $snippet "$" } ", then it is interpreted as a responder-relative path." }
|
||||
{ { $snippet "rest" } " - a value to add at the end of the URL." }
|
||||
{ { $snippet "query" } " - a comma-separated list of value names defined in the current form which are to be passed to the link as query parameters." }
|
||||
{ { $snippet "value" } " - a value name holding a URL. If this attribute is specified, it overrides all others." }
|
||||
}
|
||||
"Any attributes not in the Chloe XML namespace are passed on to the generated " { $snippet "a" } " tag."
|
||||
$nl
|
||||
"An example:"
|
||||
{ $code
|
||||
"<t:a t:href=\"$wiki/view/\""
|
||||
" t:rest=\"title\""
|
||||
" class=\"small-link\">"
|
||||
" View"
|
||||
"</t:a>"
|
||||
}
|
||||
"The above might render as"
|
||||
{ $code
|
||||
"<a href=\"http://mysite.org/wiki/view/Factor\""
|
||||
" class=\"small-link\">"
|
||||
" View"
|
||||
"s</a>"
|
||||
}
|
||||
} }
|
||||
{ { $snippet "t:form" } {
|
||||
"Renders a form; extends the standard XHTML " { $snippet "form" } " tag by providing some integration with other web framework features, for example by adding hidden fields for authentication credentials and session management allowing those features to work with form submission transparently. The following attributes are supported:"
|
||||
{ $list
|
||||
{ { $snippet "t:method" } " - just like the " { $snippet "method" } " attribute of an HTML " { $snippet "form" } " tag, this can equal " { $snippet "get" } " or " { $snippet "post" } ". Unlike the HTML tag, the default is " { $snippet "post" } "." }
|
||||
{ { $snippet "t:action" } " - a URL. If it begins with " { $snippet "$" } ", then it is interpreted as a responder-relative path." }
|
||||
{ { $snippet "t:for" } " - a comma-separated list of form values which are to be inserted in the form as hidden fields. Other than being more concise, this is equivalent to nesting a series of " { $snippet "t:hidden" } " tags inside the form." }
|
||||
}
|
||||
"Any attributes not in the Chloe XML namespace are passed on to the generated " { $snippet "form" } " tag."
|
||||
} }
|
||||
{ { $snippet "t:button" } {
|
||||
"Shorthand for a form with a single button, whose label is the text child of the " { $snippet "t:button" } " tag. Attributes are processed as with the " { $snippet "t:form" } " tag, with the exception that any attributes not in the Chloe XML namespace are passed on to the generated " { $snippet "button" } " tag, rather than the " { $snippet "form" } " tag surrounding it."
|
||||
$nl
|
||||
"An example:"
|
||||
{ $code
|
||||
"<t:button t:method=\"POST\""
|
||||
" t:action=\"$wiki/delete\""
|
||||
" t:for=\"id\">"
|
||||
" class=\"link-button\""
|
||||
" Delete"
|
||||
"</t:button>"
|
||||
}
|
||||
} }
|
||||
} ;
|
||||
|
||||
ARTICLE: "html.templates.chloe.tags" "Standard Chloe tags"
|
||||
"A Chloe template is an XML file with a mix of standard XHTML and Chloe tags."
|
||||
$nl
|
||||
"XHTML tags are rendered verbatim, except attribute values which begin with " { $snippet "@" } " are replaced with the corresponding " { $link "html.forms.values" } "."
|
||||
$nl
|
||||
"Chloe tags are defined in the " { $snippet "http://factorcode.org/chloe/1.0" } " namespace; by convention, it is bound with a prefix of " { $snippet "t" } ". The top-level tag must always be the " { $snippet "t:chloe" } " tag. A typical Chloe template looks like so:"
|
||||
{ $code
|
||||
"<?xml version=\"1.0\"?>"
|
||||
""
|
||||
"<t:chloe xmlns:t=\"http://factorcode.org/chloe/1.0\">"
|
||||
" ..."
|
||||
"</t:chloe>"
|
||||
}
|
||||
{ $subsection "html.templates.chloe.tags.component" }
|
||||
{ $subsection "html.templates.chloe.tags.boilerplate" }
|
||||
{ $subsection "html.templates.chloe.tags.control" }
|
||||
{ $subsection "html.templates.chloe.tags.form" } ;
|
||||
|
||||
ARTICLE: "html.templates.chloe.extend" "Extending Chloe"
|
||||
"The " { $vocab-link "html.templates.chloe.syntax" } " and " { $vocab-link "html.templates.chloe.compiler" } " vocabularies contain the heart of the Chloe implementation."
|
||||
$nl
|
||||
"Chloe is implemented as a compiler which converts XML templates into Factor quotations. The template only has to be parsed and compiled once, and not on every HTTP request. This helps improve performance and memory usage."
|
||||
$nl
|
||||
"These vocabularies provide various hooks by which Chloe can be extended. First of all, new " { $link "html.components" } " can be wired in. If further flexibility is needed, entirely new tags can be defined by hooking into the Chloe compiler."
|
||||
{ $subsection "html.templates.chloe.extend.components" }
|
||||
{ $subsection "html.templates.chloe.extend.tags" } ;
|
||||
|
||||
ARTICLE: "html.templates.chloe.extend.tags" "Extending Chloe with custom tags"
|
||||
"Syntax for defining custom tags:"
|
||||
{ $subsection POSTPONE: CHLOE: }
|
||||
"A number of compiler words can be used from the " { $link POSTPONE: CHLOE: } " body to emit compiled template code."
|
||||
$nl
|
||||
"Extracting attributes from the XML tag:"
|
||||
{ $subsection required-attr }
|
||||
{ $subsection optional-attr }
|
||||
{ $subsection compile-attr }
|
||||
"Examining tag nesting:"
|
||||
{ $subsection tag-stack }
|
||||
"Generating code for printing strings and calling quotations:"
|
||||
{ $subsection [write] }
|
||||
{ $subsection [code] }
|
||||
"Generating code from child elements:"
|
||||
{ $subsection process-children }
|
||||
{ $subsection compile-children>string }
|
||||
{ $subsection compile-with-scope }
|
||||
"Examples which illustrate some of the above:"
|
||||
{ $subsection "html.templates.chloe.extend.tags.example" } ;
|
||||
|
||||
ARTICLE: "html.templates.chloe.extend.tags.example" "Examples of custom Chloe tags"
|
||||
"As a first example, let's develop a custom Chloe tag which simply renders a random number. The tag will be used as follows:"
|
||||
{ $code
|
||||
"<t:random t:min='10' t:max='20' t:generator='system' />"
|
||||
}
|
||||
"The " { $snippet "t:min" } " and " { $snippet "t:max" } " parameters are required, and " { $snippet "t:generator" } ", which can equal one of " { $snippet "default" } ", " { $snippet "system" } " or " { $snippet "secure" } ", is optional, with the default being " { $snippet "default" } "."
|
||||
$nl
|
||||
"Here is the " { $link POSTPONE: USING: } " form that we need for the below code to work:"
|
||||
{ $code
|
||||
"USING: combinators kernel math.parser math.ranges random"
|
||||
"html.templates.chloe.compiler html.templates.chloe.syntax ;"
|
||||
}
|
||||
"We write a word which extracts the relevant attributes from an XML tag:"
|
||||
{ $code
|
||||
": random-attrs ( tag -- min max generator )"
|
||||
" [ \"min\" required-attr string>number ]"
|
||||
" [ \"max\" required-attr string>number ]"
|
||||
" [ \"generator\" optional-attr ]"
|
||||
" tri ;"
|
||||
}
|
||||
"Next, we convert a random generator name into a random generator object:"
|
||||
{ $code
|
||||
": string>random-generator ( string -- generator )"
|
||||
" {"
|
||||
" { \"default\" [ random-generator ] }"
|
||||
" { \"system\" [ system-random-generator ] }"
|
||||
" { \"secure\" [ secure-random-generator ] }"
|
||||
" } case ;"
|
||||
}
|
||||
"Finally, we can write our Chloe tag:"
|
||||
{ $code
|
||||
"CHLOE: random"
|
||||
" random-attrs string>random-generator"
|
||||
" '["
|
||||
" _ _ _"
|
||||
" [ [a,b] random present write ]"
|
||||
" with-random-generator"
|
||||
" ] [code] ;"
|
||||
}
|
||||
"For the second example, let's develop a Chloe tag which repeatedly renders its child several times, where the number comes from a form value. The tag will be used as follows:"
|
||||
{ $code
|
||||
"<t:repeat t:times='n'>Hello world.<br /></t:repeat>"
|
||||
}
|
||||
"This time, we cannot simply extract the " { $snippet "t:times" } " attribute at compile time since its value cannot be determined then. Instead, we execute " { $link compile-attr } " to generate code which pushes the value of that attribute on the stack. We then use " { $link process-children } " to compile child elements as a nested quotation which we apply " { $link times } " to."
|
||||
{ $code
|
||||
"CHLOE: repeat"
|
||||
" [ \"times\" required-attr compile-attr ]"
|
||||
" [ [ times ] process-children ]"
|
||||
" bi ;"
|
||||
} ;
|
||||
|
||||
ARTICLE: "html.templates.chloe.extend.components.example" "An example of a custom Chloe component"
|
||||
"As an example, let's develop a custom Chloe component which renders an image stored in a form value. Since the component does not require any configuration, we can define a singleton class:"
|
||||
{ $code "SINGLETON: image" }
|
||||
"Now we define a method on the " { $link render* } " generic word which renders the image using " { $vocab-link "html.elements" } ":"
|
||||
{ $code "M: image render* 2drop <img =src img/> ;" }
|
||||
"Finally, we can define a Chloe component:"
|
||||
{ $code "CHLOE-SINGLETON: image" }
|
||||
"We can use it as follows, assuming the current form has a value named " { $snippet "image" } ":"
|
||||
{ $code "<t:image t:name='image' />" } ;
|
||||
|
||||
ARTICLE: "html.templates.chloe.extend.components" "Extending Chloe with custom components"
|
||||
"Custom HTML components implementing the " { $link render* } " word can be wired up with Chloe using the following syntax from " { $vocab-link "html.templates.chloe.components" } ":"
|
||||
{ $subsection POSTPONE: CHLOE-SINGLETON: }
|
||||
{ $subsection POSTPONE: CHLOE-TUPLE: }
|
||||
{ $subsection "html.templates.chloe.extend.components.example" } ;
|
||||
|
||||
ARTICLE: "html.templates.chloe" "Chloe templates"
|
||||
"The " { $vocab-link "html.templates.chloe" } " vocabulary implements an XHTML templating engine. Unlike " { $vocab-link "html.templates.fhtml" } ", Chloe templates are always well-formed XML, and no Factor code can be embedded in them, enforcing proper separation of concerns. Chloe templates can be edited using standard XML editing tools; they are less flexible than FHTML, but often simpler as a result."
|
||||
{ $subsection <chloe> }
|
||||
{ $subsection reset-cache }
|
||||
{ $subsection "html.templates.chloe.tags" }
|
||||
{ $subsection "html.templates.chloe.extend" } ;
|
||||
|
||||
ABOUT: "html.templates.chloe"
|
|
@ -134,7 +134,7 @@ TUPLE: person first-name last-name ;
|
|||
|
||||
[ ] [ H{ { "a" H{ { "b" "c" } } } } values set ] unit-test
|
||||
|
||||
[ "<form method='post' action='foo'><input type='hidden' name='__n' value='a'/></form>" ] [
|
||||
[ "<form method='post' action='foo'><div style='display: none;'><input type='hidden' name='__n' value='a'/></div></form>" ] [
|
||||
[
|
||||
"test10" test-template call-template
|
||||
] run-template
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
IN: html.templates.fhtml
|
||||
USING: help.markup help.syntax ;
|
||||
|
||||
HELP: <fhtml> ;
|
||||
|
||||
ARTICLE: "html.templates.fhtml" "FHTML templates"
|
||||
"The " { $vocab-link "html.templates.fhtml" } " vocabulary implements a templating engine which mixes markup with Factor code."
|
||||
$nl
|
||||
"FHTML provides an alternative to " { $vocab-link "html.templates.chloe" } " for situations where complex logic must be embedded in the presentation layer of a web application. While this is discouraged for larger applications, it is useful for prototyping as well as simpler applications."
|
||||
$nl
|
||||
"The entire syntax of an FHTML template can be summarized as thus: text outside of " { $snippet "<%" } " and " { $snippet "%>" } " is rendered literally. Text inside " { $snippet "<%" } " and " { $snippet "%>" } " is interpreted as Factor source code."
|
||||
{ $subsection <fhtml> } ;
|
||||
|
||||
ABOUT: "html.templates.fhtml"
|
|
@ -0,0 +1,88 @@
|
|||
IN: html.templates
|
||||
USING: help.markup help.syntax io strings quotations xml.data
|
||||
continuations urls ;
|
||||
|
||||
HELP: template
|
||||
{ $class-description "The class of HTML templates." } ;
|
||||
|
||||
HELP: call-template*
|
||||
{ $values { "template" template } }
|
||||
{ $contract "Writes a template to " { $link output-stream } ", possibly using " { $vocab-link "html.forms" } " state."
|
||||
$nl
|
||||
"In addition to methods added by other vocabularies, this generic word has methods on the following classes:"
|
||||
{ $list
|
||||
{ { $link string } " - the simplest type of template; simply written to " { $link output-stream } }
|
||||
{ { $link callable } " - a custom quotation, called to yield output" }
|
||||
{ { $link xml } " - written to " { $link output-stream } }
|
||||
{ "an input stream - copied to " { $link output-stream } }
|
||||
} } ;
|
||||
|
||||
HELP: call-template
|
||||
{ $values { "template" template } }
|
||||
{ $description "Writes a template to " { $link output-stream } ", possibly using " { $vocab-link "html.forms" } " state."
|
||||
$nl
|
||||
"This word calls " { $link call-template* } ", wrapping it in a " { $link recover } " form which improves error reporting by combining the underlying error with the template object." } ;
|
||||
|
||||
HELP: set-title
|
||||
{ $values { "string" string } }
|
||||
{ $description "Sets the title of the current page. This is usually called by child templates, and a master template calls " { $link write-title } "." } ;
|
||||
|
||||
HELP: write-title
|
||||
{ $description "Writes the title of the current page, previously set by " { $link set-title } ". This is usually called by a master template after rendering a child template." } ;
|
||||
|
||||
HELP: add-style
|
||||
{ $values { "string" string } }
|
||||
{ $description "Adds some CSS markup to the CSS stylesheet of a master template. Usually called by child templates which need to insert CSS style information in the " { $snippet "<head>" } " tag of the resulting HTML page." } ;
|
||||
|
||||
HELP: write-style
|
||||
{ $description "Writes a CSS stylesheet assembled from " { $link add-style } " calls by child templates. Usually called by the master template to emit a CSS style in the " { $snippet "<head>" } " tag of the resulting HTML page." } ;
|
||||
|
||||
HELP: add-atom-feed
|
||||
{ $values { "title" string } { "url" "a " { $link string } " or " { $link url } } }
|
||||
{ $description "Adds an Atom feed link to the list of feeds in a master template. Usually called by child templates which need to insert an Atom feed link information in the " { $snippet "<head>" } " tag of the resulting HTML page." } ;
|
||||
|
||||
HELP: write-atom-feeds
|
||||
{ $description "Writes a list of Atom feed links assembled from " { $link add-atom-feed } " calls by child templates. Usually called by the master template to emit a list of Atom feed links in the " { $snippet "<head>" } " tag of the resulting HTML page." } ;
|
||||
|
||||
HELP: nested-template?
|
||||
{ $var-description "Set to a true value if the current call to " { $link call-template } " is nested inside a " { $link with-boilerplate } " and will therefore appear as part of another template. In this case, XML processing instructions and document type declarations should be omitted." } ;
|
||||
|
||||
HELP: call-next-template
|
||||
{ $description "Calls the next innermost child template from a master template. This is used to implement the " { $snippet "t:call-next-template" } " tag in the " { $vocab-link "html.templates.chloe" } " templating engine." } ;
|
||||
|
||||
HELP: with-boilerplate
|
||||
{ $values { "child" template } { "master" template } }
|
||||
{ $description "Calls the child template, storing its output in a string, then calls the master template. The master template may call " { $link call-next-template } " to insert the output of the child template at any point; both templates may also use the master/child interface words documented in " { $link "html.templates.boilerplate" } "." } ;
|
||||
|
||||
HELP: template-convert
|
||||
{ $values { "template" template } { "output" "a pathname string" } }
|
||||
{ $description "Calls the template and writes its output to a file with UTF8 encoding." } ;
|
||||
|
||||
ARTICLE: "html.templates.boilerplate" "Boilerplate support"
|
||||
"The following words define the interface between a templating engine and the " { $vocab-link "furnace.boilerplate" } " vocabulary."
|
||||
$nl
|
||||
"The master/child template interface follows a pattern where for each concept there is a word called by the child to store an entity, and another word to write the entity out; this solves the problem where certain HTML tags, such as " { $snippet "<title>" } " and " { $snippet "<link>" } " must appear inside the " { $snippet "<head>" } " tag, even though those tags are usually precisely those that the child template will want to set."
|
||||
{ $subsection set-title }
|
||||
{ $subsection write-title }
|
||||
{ $subsection add-style }
|
||||
{ $subsection write-style }
|
||||
{ $subsection add-atom-feed }
|
||||
{ $subsection write-atom-feeds }
|
||||
"Processing a master template with a child:"
|
||||
{ $subsection with-boilerplate }
|
||||
{ $subsection call-next-template } ;
|
||||
|
||||
ARTICLE: "html.templates" "HTML template interface"
|
||||
"The " { $vocab-link "html.templates" } " vocabulary implements an abstract interface to HTML templating engines. The " { $vocab-link "html.templates.fhtml" } " and " { $vocab-link "html.templates.chloe" } " vocabularies are two implementations of this."
|
||||
$nl
|
||||
"An HTML template is an instance of a mixin:"
|
||||
{ $subsection template }
|
||||
"HTML templates must also implement a method on a generic word:"
|
||||
{ $subsection call-template* }
|
||||
"Calling an HTML template:"
|
||||
{ $subsection call-template }
|
||||
"Usually HTML templates are invoked dynamically by the Furnace web framework and HTTP server. They can also be used in static HTML generation tools:"
|
||||
{ $subsection template-convert }
|
||||
{ $subsection "html.templates.boilerplate" } ;
|
||||
|
||||
ABOUT: "html.templates"
|
|
@ -67,7 +67,7 @@ SYMBOL: next-template
|
|||
|
||||
M: f call-template* drop call-next-template ;
|
||||
|
||||
: with-boilerplate ( body template -- )
|
||||
: with-boilerplate ( child master -- )
|
||||
[
|
||||
title [ <box> or ] change
|
||||
style [ SBUF" " clone or ] change
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -1,3 +1,3 @@
|
|||
web
|
||||
enterprise
|
||||
network
|
||||
web
|
||||
|
|
|
@ -8,7 +8,7 @@ $nl
|
|||
$nl
|
||||
"Buffers are used to implement native I/O backends."
|
||||
$nl
|
||||
"Buffer words are found in the " { $vocab-link "buffers" } " vocabulary."
|
||||
"Buffer words are found in the " { $vocab-link "io.buffers" } " vocabulary."
|
||||
{ $subsection buffer }
|
||||
{ $subsection <buffer> }
|
||||
"Buffers must be manually deallocated by calling " { $link dispose } "."
|
||||
|
|
|
@ -36,7 +36,7 @@ ARTICLE: "presentations" "Presentations"
|
|||
ARTICLE: "styles" "Formatted output"
|
||||
"The " { $link stream-format } ", " { $link with-style } ", " { $link with-nesting } " and " { $link tabular-output } " words take a hashtable of style attributes. Output stream implementations are free to ignore style information."
|
||||
$nl
|
||||
"Style hashtables are keyed by symbols from the " { $vocab-link "styles" } " vocabulary."
|
||||
"Style hashtables are keyed by symbols from the " { $vocab-link "io.styles" } " vocabulary."
|
||||
{ $subsection "character-styles" }
|
||||
{ $subsection "paragraph-styles" }
|
||||
{ $subsection "table-styles" }
|
||||
|
|
|
@ -20,7 +20,7 @@ HELP: analyze-log
|
|||
{ $description "Analyzes a log file and prints a formatted report. The " { $snippet "word-names" } " parameter is documented in " { $link analyze-entries } "." } ;
|
||||
|
||||
ARTICLE: "logging.analysis" "Log analysis"
|
||||
"The " { $vocab-link "logging.analysis" } " vocabulary builds on the " { $vocab-link "logging.parser" } " vocabulary. It parses log files and produces formatted summary reports. It is used by the " { $vocab-link "logger.insomniac" } " vocabulary to e-mail daily reports."
|
||||
"The " { $vocab-link "logging.analysis" } " vocabulary builds on the " { $vocab-link "logging.parser" } " vocabulary. It parses log files and produces formatted summary reports. It is used by the " { $vocab-link "logging.insomniac" } " vocabulary to e-mail daily reports."
|
||||
$nl
|
||||
"Print log file summary:"
|
||||
{ $subsection analyze-log }
|
||||
|
|
|
@ -100,7 +100,7 @@ ARTICLE: "logging.rotation" "Log rotation"
|
|||
"The " { $vocab-link "logging.insomniac" } " vocabulary automates log rotation." ;
|
||||
|
||||
ARTICLE: "logging.server" "Log implementation"
|
||||
"The " { $vocab-link "logging.server" } " vocabulary implements a concurrent log server using " { $vocab-link "concurrency" } ". User code never interacts with the server directly, instead it uses the words in the " { $link "logging" } " vocabulary. The server is used to synchronize access to log files and ensure that log rotation can proceed in an orderly fashion."
|
||||
"The " { $vocab-link "logging.server" } " vocabulary implements a concurrent log server using " { $vocab-link "concurrency.messaging" } ". User code never interacts with the server directly, instead it uses the words in the " { $link "logging" } " vocabulary. The server is used to synchronize access to log files and ensure that log rotation can proceed in an orderly fashion."
|
||||
$nl
|
||||
"The " { $link log-message } " word sends a message to the server which results in the server executing an internal word:"
|
||||
{ $subsection (log-message) }
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
extensions
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1,110 @@
|
|||
USING: help.markup help.syntax io.streams.string quotations
|
||||
strings math parser-combinators.regexp ;
|
||||
IN: validators
|
||||
|
||||
HELP: v-captcha
|
||||
{ $values { "str" string } }
|
||||
{ $description "Throws a validation error if the string is non-empty. This is used to create bait fields for spam-bots to fill in." } ;
|
||||
|
||||
HELP: v-credit-card
|
||||
{ $values { "str" string } { "n" integer } }
|
||||
{ $description "If the credit card number passes the Luhn algorithm, converts it to an integer, otherwise throws an error." }
|
||||
{ $notes "See " { $url "http://en.wikipedia.org/wiki/Luhn_algorithm" } " for a description of this algorithm." } ;
|
||||
|
||||
HELP: v-default
|
||||
{ $values { "str" string } { "def" string } { "str/def" string } }
|
||||
{ $description "If the input string is not specified, replaces it with the default value." } ;
|
||||
|
||||
HELP: v-email
|
||||
{ $values { "str" string } }
|
||||
{ $description "Throws a validation error if the string is not a valid e-mail address, as determined by a regular expression." } ;
|
||||
|
||||
HELP: v-integer
|
||||
{ $values { "str" string } { "n" integer } }
|
||||
{ $description "Converts the string into an integer, throwing a validation error if the string is not a valid integer." } ;
|
||||
|
||||
HELP: v-min-length
|
||||
{ $values { "str" string } { "n" integer } }
|
||||
{ $description "Throws a validation error if the string is shorter than " { $snippet "n" } " characters." } ;
|
||||
|
||||
HELP: v-max-length
|
||||
{ $values { "str" string } { "n" integer } }
|
||||
{ $description "Throws a validation error if the string is longer than " { $snippet "n" } " characters." } ;
|
||||
|
||||
HELP: v-max-value
|
||||
{ $values { "x" integer } { "n" integer } }
|
||||
{ $description "Throws an error if " { $snippet "x" } " is larger than " { $snippet "n" } "." } ;
|
||||
|
||||
HELP: v-min-value
|
||||
{ $values { "x" integer } { "n" integer } }
|
||||
{ $description "Throws an error if " { $snippet "x" } " is smaller than " { $snippet "n" } "." } ;
|
||||
|
||||
HELP: v-mode
|
||||
{ $values { "str" string } }
|
||||
{ $description "Throws an error if " { $snippet "str" } " is not a valid XMode mode name." } ;
|
||||
|
||||
HELP: v-number
|
||||
{ $values { "str" string } { "n" real } }
|
||||
{ $description "Converts the string into a real number, throwing a validation error if the string is not a valid real number." } ;
|
||||
|
||||
HELP: v-one-line
|
||||
{ $values { "str" string } }
|
||||
{ $description "Throws a validation error if the string contains line breaks." } ;
|
||||
|
||||
HELP: v-one-word
|
||||
{ $values { "str" string } }
|
||||
{ $description "Throws a validation error if the string contains word breaks." } ;
|
||||
|
||||
HELP: v-optional
|
||||
{ $values { "str" string } { "quot" quotation } { "result" string } }
|
||||
{ $description "If the string is non-empty, applies the quotation to the string, otherwise outputs the empty string." } ;
|
||||
|
||||
HELP: v-password
|
||||
{ $values { "str" string } }
|
||||
{ $description "A reasonable default validator for passwords." } ;
|
||||
|
||||
HELP: v-regexp
|
||||
{ $values { "str" string } { "what" string } { "regexp" regexp } }
|
||||
{ $description "Throws a validation error that " { $snippet "what" } " failed if the string does not match the regular expression." } ;
|
||||
|
||||
HELP: v-required
|
||||
{ $values { "str" string } }
|
||||
{ $description "Throws a validation error if the string is empty." } ;
|
||||
|
||||
HELP: v-url
|
||||
{ $values { "str" string } }
|
||||
{ $description "Throws an error if the string is not a valid URL, as determined by a regular expression." } ;
|
||||
|
||||
HELP: v-username
|
||||
{ $values { "str" string } }
|
||||
{ $description "A reasonable default validator for usernames." } ;
|
||||
|
||||
ARTICLE: "validators" "Form validators"
|
||||
"The " { $vocab-link "validators" } " vocabulary provides a set of words which are intended to be used with the form validation functionality offered by " { $vocab-link "furnace.actions" } ". They can also be used independently of the web framework."
|
||||
$nl
|
||||
"Note that validators which take numbers must be preceded by " { $link v-integer } " or " { $link v-number } " if the original input is a string."
|
||||
$nl
|
||||
"Higher-order validators which require additional parameters:"
|
||||
{ $subsection v-default }
|
||||
{ $subsection v-optional }
|
||||
{ $subsection v-min-length }
|
||||
{ $subsection v-max-length }
|
||||
{ $subsection v-min-value }
|
||||
{ $subsection v-max-value }
|
||||
{ $subsection v-regexp }
|
||||
"Simple validators:"
|
||||
{ $subsection v-required }
|
||||
{ $subsection v-number }
|
||||
{ $subsection v-integer }
|
||||
{ $subsection v-one-line }
|
||||
{ $subsection v-one-word }
|
||||
{ $subsection v-captcha }
|
||||
"More complex validators:"
|
||||
{ $subsection v-email }
|
||||
{ $subsection v-url }
|
||||
{ $subsection v-username }
|
||||
{ $subsection v-password }
|
||||
{ $subsection v-credit-card }
|
||||
{ $subsection v-mode } ;
|
||||
|
||||
ABOUT: "validators"
|
|
@ -6,13 +6,13 @@ unicode.categories arrays hashtables words classes quotations
|
|||
xmode.catalog ;
|
||||
IN: validators
|
||||
|
||||
: v-default ( str def -- str )
|
||||
: v-default ( str def -- str/def )
|
||||
over empty? spin ? ;
|
||||
|
||||
: v-required ( str -- str )
|
||||
dup empty? [ "required" throw ] when ;
|
||||
|
||||
: v-optional ( str quot -- str )
|
||||
: v-optional ( str quot -- result )
|
||||
over empty? [ 2drop f ] [ call ] if ; inline
|
||||
|
||||
: v-min-length ( str n -- str )
|
||||
|
@ -91,7 +91,7 @@ IN: validators
|
|||
"not a valid syntax mode" throw
|
||||
] unless ;
|
||||
|
||||
: luhn? ( n -- ? )
|
||||
: luhn? ( str -- ? )
|
||||
string>digits <reversed>
|
||||
[ odd? [ 2 * 10 /mod + ] when ] map-index
|
||||
sum 10 mod 0 = ;
|
||||
|
|
|
@ -23,7 +23,8 @@ $nl
|
|||
"Since strings are sequences, basic string manipulation can be performed using sequence operations (" { $link "sequences" } "). More advanced functionality can be found in other vocabularies, including but not limited to:"
|
||||
{ $list
|
||||
{ { $vocab-link "ascii" } " - traditional ASCII character classes" }
|
||||
{ { $vocab-link "unicode" } " - Unicode 5.0-aware character classes, case conversion, word breaks, ..." }
|
||||
{ { $vocab-link "unicode.categories" } " - Unicode character classes" }
|
||||
{ { $vocab-link "unicode.case" } " - Unicode case conversion" }
|
||||
{ { $vocab-link "regexp" } " - regular expressions" }
|
||||
{ { $vocab-link "peg" } " - parser expression grammars" }
|
||||
} ;
|
||||
|
|
|
@ -39,7 +39,7 @@ HELP: sprintf
|
|||
{ $see-also printf } ;
|
||||
|
||||
ARTICLE: "printf" "Formatted printing"
|
||||
"The " { $vocab-link "printf" } " and " { $vocab-link "sprintf" } " words are used for formatted printing.\n"
|
||||
"The " { $link printf } " and " { $link sprintf } " words are used for formatted printing.\n"
|
||||
"\n"
|
||||
"Several format specifications exist for handling arguments of different types, and specifying attributes for the result string, including such things as maximum width, padding, and decimals.\n"
|
||||
{ $table
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -0,0 +1 @@
|
|||
web
|
|
@ -42,6 +42,6 @@ HELP: wordtimer-call
|
|||
|
||||
|
||||
ARTICLE: "wordtimer" "Word Timer"
|
||||
"The " { $vocab-link "wordtimer" } " vocabulary measures accumulated execution time for words. If you just want to profile the accumulated time taken by all words in a vocab you can use " { $vocab-link "profile-vocab" } ". If you need more fine grained control then do the following: First annotate individual words with the " { $link add-timer } " word or whole vocabularies with " { $link add-timers } ". Then use " { $link wordtimer-call } " to invoke a quotation and print out the timings." ;
|
||||
"The " { $vocab-link "wordtimer" } " vocabulary measures accumulated execution time for words. If you just want to profile the accumulated time taken by all words in a vocab you can use " { $link profile-vocab } ". If you need more fine grained control then do the following: First annotate individual words with the " { $link add-timer } " word or whole vocabularies with " { $link add-timers } ". Then use " { $link wordtimer-call } " to invoke a quotation and print out the timings." ;
|
||||
|
||||
ABOUT: "wordtimer"
|
||||
|
|
Loading…
Reference in New Issue