Fossil SCM

If a footnote's text starts with a token of the special form then use this token to derive a set of CSS classes that are added to that footnote and its references. This enables users to style elements of a particular footnote provided that the administrator provisioned and documented some special CSS classes in a custum skin. Default skin does not provide any of such special classes which makes this feature an "opt-in".

george 2022-02-17 22:09 markdown-footnotes
Commit 92516ced8b0371a8b4df0a8775cfd2683b27c73f1d3ffdcdf8971a79da44bc86
+1 -1
--- src/backlink.c
+++ src/backlink.c
@@ -279,11 +279,11 @@
279279
/* image */ (int(*)(Blob*,Blob*,Blob*,Blob*,void*))mkdn_noop1,
280280
/* linebreak */ (int(*)(Blob*,void*))mkdn_noop1,
281281
/* link */ backlink_md_link,
282282
/* r_html_tag */ (int(*)(Blob*,Blob*,void*))mkdn_noop1,
283283
/* tri_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
284
- /* footnoteref*/ (int(*)(Blob*,const Blob*,int,int,void*))mkdn_noop1,
284
+ /* footnoteref*/ (int(*)(Blob*,const Blob*,const Blob*,int,int,void*))mkdn_noop1,
285285
286286
0, /* entity */
287287
0, /* normal_text */
288288
"*_", /* emphasis characters */
289289
0 /* client data */
290290
--- src/backlink.c
+++ src/backlink.c
@@ -279,11 +279,11 @@
279 /* image */ (int(*)(Blob*,Blob*,Blob*,Blob*,void*))mkdn_noop1,
280 /* linebreak */ (int(*)(Blob*,void*))mkdn_noop1,
281 /* link */ backlink_md_link,
282 /* r_html_tag */ (int(*)(Blob*,Blob*,void*))mkdn_noop1,
283 /* tri_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
284 /* footnoteref*/ (int(*)(Blob*,const Blob*,int,int,void*))mkdn_noop1,
285
286 0, /* entity */
287 0, /* normal_text */
288 "*_", /* emphasis characters */
289 0 /* client data */
290
--- src/backlink.c
+++ src/backlink.c
@@ -279,11 +279,11 @@
279 /* image */ (int(*)(Blob*,Blob*,Blob*,Blob*,void*))mkdn_noop1,
280 /* linebreak */ (int(*)(Blob*,void*))mkdn_noop1,
281 /* link */ backlink_md_link,
282 /* r_html_tag */ (int(*)(Blob*,Blob*,void*))mkdn_noop1,
283 /* tri_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1,
284 /* footnoteref*/ (int(*)(Blob*,const Blob*,const Blob*,int,int,void*))mkdn_noop1,
285
286 0, /* entity */
287 0, /* normal_text */
288 "*_", /* emphasis characters */
289 0 /* client data */
290
+116 -8
--- src/markdown.c
+++ src/markdown.c
@@ -83,11 +83,11 @@
8383
struct Blob *content, void *opaque);
8484
int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
8585
int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
8686
char c, void *opaque);
8787
int (*footnote_ref)(struct Blob *ob, const struct Blob *span,
88
- int index, int locus, void *opaque);
88
+ const struct Blob *upc, int index, int locus, void *opaque);
8989
9090
/* low level callbacks - NULL copies input directly into the output */
9191
void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
9292
void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
9393
@@ -145,18 +145,19 @@
145145
};
146146
147147
struct footnote {
148148
struct Blob id; /* must be the first field as in link_ref struct */
149149
struct Blob text; /* footnote's content that is rendered at the end */
150
+ struct Blob upc; /* user-provided classes .ASCII-alnum.or-hypen: */
150151
int bRndred; /* indicates if `text` holds a rendered content */
151152
152153
int defno; /* serial number of definition, set during the first pass */
153154
int index; /* set to the index within array after ordering by id */
154155
int iMark; /* user-visible numeric marker, assigned upon the first use*/
155156
int nUsed; /* counts references to this note, increments upon each use*/
156157
};
157
-
158
+#define FOOTNOTE_INITIALIZER {empty_blob,empty_blob,empty_blob, 0,0,0,0,0}
158159
159160
/* char_trigger -- function pointer to render active chars */
160161
/* returns the number of chars taken care of */
161162
/* data is the pointer of the beginning of the span */
162163
/* offset is the number of valid chars before data */
@@ -1100,27 +1101,99 @@
11001101
assert( fn->nUsed > 0 );
11011102
cleanup:
11021103
release_work_buffer( rndr, id );
11031104
return fn;
11041105
}
1106
+
1107
+/* Counts characters in the blank prefix within at most nHalfLines.
1108
+** A sequence of spaces and tabs counts as odd halfline,
1109
+** a newline counts as even halfline
1110
+*/
1111
+static inline size_t count_whitespaces(
1112
+ const char *data, size_t size, int nHalfLines
1113
+){
1114
+ const char *p = data;
1115
+ const char * const end = data+size;
1116
+ while( nHalfLines > 0 ){
1117
+ while( p!=end && (*p==' ' || *p=='\t' ) ){ p++; }
1118
+ if( p==end || --nHalfLines == 0 ) break;
1119
+ if( *p=='\n' || *p=='\r' ){
1120
+ p++;
1121
+ if( p==end ) break;
1122
+ if( *p=='\n' && p[-1]=='\r' ){
1123
+ p++;
1124
+ }
1125
+ }
1126
+ nHalfLines--;
1127
+ }
1128
+ return (size_t)(p-data);
1129
+}
1130
+
1131
+/* Check if the data starts with a classlist token of the special form.
1132
+** If so then return the length of that token, otherwise return 0.
1133
+**
1134
+** The token must start with a dot and must end with a colon;
1135
+** in between of these it must be a dot-separated list of words;
1136
+** each word may contain only alphanumeric characters and hyphens.
1137
+*/
1138
+size_t is_footnote_classlist(const char * const data, size_t size){
1139
+ const char *p;
1140
+ const char * const end = data+size;
1141
+ if( data==end || *data != '.' ) return 0;
1142
+ for(p=data+1; p!=end; p++){
1143
+ if( fossil_isalnum(*p) || *p=='-' ) continue;
1144
+ if( *p==':' ){
1145
+ return p[-1]!='.' ? (size_t)(p-data)+1 : 0;
1146
+ }
1147
+ if( *p=='.' ){
1148
+ if( p[-1]!='.' ) continue;
1149
+ else break;
1150
+ }
1151
+ break;
1152
+ }
1153
+ return 0;
1154
+}
11051155
11061156
/* Adds unlabeled footnote to the rndr.
11071157
* If text is blank then returns 0,
11081158
* otherwise returns the address of the added footnote. */
11091159
static inline const struct footnote* add_inline_footnote(
11101160
struct render *rndr,
11111161
const char *text,
11121162
size_t size
11131163
){
1114
- struct footnote fn = { empty_blob, empty_blob, 0, 0, 0, 0, 0 };
1115
- while(size && (*text==' ' || *text=='\t')){ text++; size--; }
1164
+ struct footnote fn = FOOTNOTE_INITIALIZER;
1165
+ const char *zUPC = 0;
1166
+ size_t nUPC = 0, n = count_whitespaces(text, size, 3);
1167
+ if( n >= size ) return 0;
1168
+ text += n;
1169
+ size -= n;
1170
+ n = is_footnote_classlist(text, size);
1171
+ if( n && n < size ){
1172
+ nUPC = n;
1173
+ zUPC = text;
1174
+ text += nUPC;
1175
+ size -= nUPC;
1176
+ n = count_whitespaces(text, size, 3);
1177
+ /* naked classlist is treated as plain text */
1178
+ if( n >= size || text[n]=='\n' || text[n]=='\r' ){
1179
+ size += nUPC;
1180
+ nUPC = 0;
1181
+ text = zUPC;
1182
+ zUPC = 0;
1183
+ }else{
1184
+ text += n;
1185
+ size -= n;
1186
+ }
1187
+ }
11161188
if(!size) return 0;
11171189
fn.iMark = ++(rndr->notes.nMarks);
11181190
fn.nUsed = 1;
11191191
fn.index = COUNT_FOOTNOTES(&rndr->notes.all);
11201192
assert( fn.iMark > 0 );
11211193
blob_append(&fn.text, text, size);
1194
+ if(nUPC) blob_append(&fn.upc, zUPC, nUPC);
11221195
blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
11231196
return (struct footnote*)( blob_buffer(&rndr->notes.all)
11241197
+( blob_size(&rndr->notes.all)-sizeof fn ));
11251198
}
11261199
@@ -1164,11 +1237,11 @@
11641237
11651238
if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0;
11661239
end = matching_bracket_offset(data, data+size);
11671240
if( !end ) return 0;
11681241
fn = add_inline_footnote(rndr, data+2, end-2);
1169
- if(fn) rndr->make.footnote_ref(ob,0,fn->iMark,1,rndr->make.opaque);
1242
+ if(fn) rndr->make.footnote_ref(ob,0,&fn->upc,fn->iMark,1,rndr->make.opaque);
11701243
return end+1;
11711244
}
11721245
11731246
/* char_link -- '[': parsing a link or an image */
11741247
static size_t char_link(
@@ -1296,11 +1369,11 @@
12961369
if( is_img ){
12971370
if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
12981371
ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
12991372
}else if(fn){
13001373
if(rndr->make.footnote_ref){
1301
- ret = rndr->make.footnote_ref(ob, content, fn->iMark, fn->nUsed,
1374
+ ret = rndr->make.footnote_ref(ob, content, &fn->upc, fn->iMark, fn->nUsed,
13021375
rndr->make.opaque);
13031376
}
13041377
}else{
13051378
ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
13061379
}
@@ -2389,12 +2462,12 @@
23892462
size_t beg, /* offset of the beginning of the line */
23902463
size_t end, /* offset of the end of the text */
23912464
size_t *last, /* last character of the link */
23922465
struct Blob * footnotes
23932466
){
2394
- size_t i, id_offset, id_end;
2395
- struct footnote fn = { empty_blob, empty_blob, 0, 0, 0, 0, 0 };
2467
+ size_t i, id_offset, id_end, upc_offset = 0, upc_size = 0;
2468
+ struct footnote fn = FOOTNOTE_INITIALIZER;
23962469
23972470
/* failfast if data is too short */
23982471
if( beg+5>=end ) return 0;
23992472
i = beg;
24002473
@@ -2418,10 +2491,23 @@
24182491
if( i>=end ) return 0;
24192492
24202493
if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0;
24212494
24222495
/* footnote's text may start on the same line */
2496
+ upc_offset = 0;
2497
+ if( data[i]!='\n' && data[i]!='\r' ){
2498
+ upc_offset = i; /* prevent a retry on the second line */
2499
+ upc_size = is_footnote_classlist(data+i,end-i);
2500
+ if( upc_size ){
2501
+ i += upc_size;
2502
+ while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2503
+ if( i>=end ){
2504
+ upc_size = 0; /* naked classlist is treated as plain text */
2505
+ i = upc_offset;
2506
+ }
2507
+ }
2508
+ }
24232509
if( data[i]!='\n' && data[i]!='\r' ){
24242510
const size_t j = i;
24252511
do i++; while( i<end && data[i]!='\n' && data[i]!='\r' );
24262512
blob_append(&fn.text, data+j, i-j);
24272513
if( i<end ){
@@ -2450,10 +2536,25 @@
24502536
/* process the 2nd and the following lines */
24512537
while( i+indent<end && memcmp(data+i,spaces,indent)==0 ){
24522538
size_t j;
24532539
i += indent;
24542540
j = i;
2541
+ if( !upc_offset ){
2542
+ /* a classlist must be provided no later than at the 2nd line */
2543
+ upc_offset = i + count_whitespaces(data+i, end-i, 1);
2544
+ upc_size = is_footnote_classlist(data+upc_offset, end-upc_offset);
2545
+ if( upc_size ){
2546
+ i = upc_offset + upc_size;
2547
+ while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2548
+ if( i>=end ){
2549
+ upc_size = 0; /* naked classlist is treated as plain text */
2550
+ i = j;
2551
+ }else{
2552
+ j = i;
2553
+ }
2554
+ }
2555
+ }
24552556
while( i<end && data[i]!='\n' && data[i]!='\r' ) i++;
24562557
blob_append(&fn.text, data+j, i-j);
24572558
if( i>=end ) break;
24582559
blob_append_char(&fn.text, data[i]);
24592560
i++;
@@ -2470,10 +2571,15 @@
24702571
}
24712572
/* a valid note has been found */
24722573
if( last ) *last = i;
24732574
if( footnotes ){
24742575
fn.defno = COUNT_FOOTNOTES( footnotes );
2576
+ if( upc_size ){
2577
+ assert( upc_offset );
2578
+ assert( upc_offset+upc_size < end );
2579
+ blob_append(&fn.upc, data+upc_offset, upc_size);
2580
+ }
24752581
blob_append(footnotes, (char *)&fn, sizeof fn);
24762582
}
24772583
return 1;
24782584
}
24792585
@@ -2680,10 +2786,11 @@
26802786
while( ++j < COUNT_FOOTNOTES(notes) ){
26812787
const struct footnote* x = CAST_AS_FOOTNOTES(notes) + j;
26822788
assert( !x->nUsed );
26832789
assert( !x->bRndred );
26842790
assert( (&x->id) + 1 == &x->text ); /* see html_footnote_item() */
2791
+ assert( (&x->upc)- 1 == &x->text );
26852792
rndr.make.footnote_item(all_items,&x->text,0,0,rndr.make.opaque);
26862793
}
26872794
rndr.make.footnotes(ob, all_items, rndr.make.opaque);
26882795
release_work_buffer(&rndr, all_items);
26892796
}
@@ -2704,12 +2811,13 @@
27042811
blob_reset(&rndr.refs);
27052812
fn = CAST_AS_FOOTNOTES( allNotes );
27062813
end = COUNT_FOOTNOTES( allNotes );
27072814
for(i=0; i<end; i++){
27082815
if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
2816
+ if(blob_size(&fn[i].upc)) blob_reset(&fn[i].upc);
27092817
blob_reset(&fn[i].text);
27102818
}
27112819
blob_reset(&rndr.notes.all);
27122820
for(i=0; i<rndr.nBlobCache; i++){
27132821
fossil_free(rndr.aBlobCache[i]);
27142822
}
27152823
}
27162824
--- src/markdown.c
+++ src/markdown.c
@@ -83,11 +83,11 @@
83 struct Blob *content, void *opaque);
84 int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
85 int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
86 char c, void *opaque);
87 int (*footnote_ref)(struct Blob *ob, const struct Blob *span,
88 int index, int locus, void *opaque);
89
90 /* low level callbacks - NULL copies input directly into the output */
91 void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
92 void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
93
@@ -145,18 +145,19 @@
145 };
146
147 struct footnote {
148 struct Blob id; /* must be the first field as in link_ref struct */
149 struct Blob text; /* footnote's content that is rendered at the end */
 
150 int bRndred; /* indicates if `text` holds a rendered content */
151
152 int defno; /* serial number of definition, set during the first pass */
153 int index; /* set to the index within array after ordering by id */
154 int iMark; /* user-visible numeric marker, assigned upon the first use*/
155 int nUsed; /* counts references to this note, increments upon each use*/
156 };
157
158
159 /* char_trigger -- function pointer to render active chars */
160 /* returns the number of chars taken care of */
161 /* data is the pointer of the beginning of the span */
162 /* offset is the number of valid chars before data */
@@ -1100,27 +1101,99 @@
1100 assert( fn->nUsed > 0 );
1101 cleanup:
1102 release_work_buffer( rndr, id );
1103 return fn;
1104 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1105
1106 /* Adds unlabeled footnote to the rndr.
1107 * If text is blank then returns 0,
1108 * otherwise returns the address of the added footnote. */
1109 static inline const struct footnote* add_inline_footnote(
1110 struct render *rndr,
1111 const char *text,
1112 size_t size
1113 ){
1114 struct footnote fn = { empty_blob, empty_blob, 0, 0, 0, 0, 0 };
1115 while(size && (*text==' ' || *text=='\t')){ text++; size--; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1116 if(!size) return 0;
1117 fn.iMark = ++(rndr->notes.nMarks);
1118 fn.nUsed = 1;
1119 fn.index = COUNT_FOOTNOTES(&rndr->notes.all);
1120 assert( fn.iMark > 0 );
1121 blob_append(&fn.text, text, size);
 
1122 blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
1123 return (struct footnote*)( blob_buffer(&rndr->notes.all)
1124 +( blob_size(&rndr->notes.all)-sizeof fn ));
1125 }
1126
@@ -1164,11 +1237,11 @@
1164
1165 if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0;
1166 end = matching_bracket_offset(data, data+size);
1167 if( !end ) return 0;
1168 fn = add_inline_footnote(rndr, data+2, end-2);
1169 if(fn) rndr->make.footnote_ref(ob,0,fn->iMark,1,rndr->make.opaque);
1170 return end+1;
1171 }
1172
1173 /* char_link -- '[': parsing a link or an image */
1174 static size_t char_link(
@@ -1296,11 +1369,11 @@
1296 if( is_img ){
1297 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
1298 ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
1299 }else if(fn){
1300 if(rndr->make.footnote_ref){
1301 ret = rndr->make.footnote_ref(ob, content, fn->iMark, fn->nUsed,
1302 rndr->make.opaque);
1303 }
1304 }else{
1305 ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
1306 }
@@ -2389,12 +2462,12 @@
2389 size_t beg, /* offset of the beginning of the line */
2390 size_t end, /* offset of the end of the text */
2391 size_t *last, /* last character of the link */
2392 struct Blob * footnotes
2393 ){
2394 size_t i, id_offset, id_end;
2395 struct footnote fn = { empty_blob, empty_blob, 0, 0, 0, 0, 0 };
2396
2397 /* failfast if data is too short */
2398 if( beg+5>=end ) return 0;
2399 i = beg;
2400
@@ -2418,10 +2491,23 @@
2418 if( i>=end ) return 0;
2419
2420 if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0;
2421
2422 /* footnote's text may start on the same line */
 
 
 
 
 
 
 
 
 
 
 
 
 
2423 if( data[i]!='\n' && data[i]!='\r' ){
2424 const size_t j = i;
2425 do i++; while( i<end && data[i]!='\n' && data[i]!='\r' );
2426 blob_append(&fn.text, data+j, i-j);
2427 if( i<end ){
@@ -2450,10 +2536,25 @@
2450 /* process the 2nd and the following lines */
2451 while( i+indent<end && memcmp(data+i,spaces,indent)==0 ){
2452 size_t j;
2453 i += indent;
2454 j = i;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2455 while( i<end && data[i]!='\n' && data[i]!='\r' ) i++;
2456 blob_append(&fn.text, data+j, i-j);
2457 if( i>=end ) break;
2458 blob_append_char(&fn.text, data[i]);
2459 i++;
@@ -2470,10 +2571,15 @@
2470 }
2471 /* a valid note has been found */
2472 if( last ) *last = i;
2473 if( footnotes ){
2474 fn.defno = COUNT_FOOTNOTES( footnotes );
 
 
 
 
 
2475 blob_append(footnotes, (char *)&fn, sizeof fn);
2476 }
2477 return 1;
2478 }
2479
@@ -2680,10 +2786,11 @@
2680 while( ++j < COUNT_FOOTNOTES(notes) ){
2681 const struct footnote* x = CAST_AS_FOOTNOTES(notes) + j;
2682 assert( !x->nUsed );
2683 assert( !x->bRndred );
2684 assert( (&x->id) + 1 == &x->text ); /* see html_footnote_item() */
 
2685 rndr.make.footnote_item(all_items,&x->text,0,0,rndr.make.opaque);
2686 }
2687 rndr.make.footnotes(ob, all_items, rndr.make.opaque);
2688 release_work_buffer(&rndr, all_items);
2689 }
@@ -2704,12 +2811,13 @@
2704 blob_reset(&rndr.refs);
2705 fn = CAST_AS_FOOTNOTES( allNotes );
2706 end = COUNT_FOOTNOTES( allNotes );
2707 for(i=0; i<end; i++){
2708 if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
 
2709 blob_reset(&fn[i].text);
2710 }
2711 blob_reset(&rndr.notes.all);
2712 for(i=0; i<rndr.nBlobCache; i++){
2713 fossil_free(rndr.aBlobCache[i]);
2714 }
2715 }
2716
--- src/markdown.c
+++ src/markdown.c
@@ -83,11 +83,11 @@
83 struct Blob *content, void *opaque);
84 int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
85 int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
86 char c, void *opaque);
87 int (*footnote_ref)(struct Blob *ob, const struct Blob *span,
88 const struct Blob *upc, int index, int locus, void *opaque);
89
90 /* low level callbacks - NULL copies input directly into the output */
91 void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque);
92 void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque);
93
@@ -145,18 +145,19 @@
145 };
146
147 struct footnote {
148 struct Blob id; /* must be the first field as in link_ref struct */
149 struct Blob text; /* footnote's content that is rendered at the end */
150 struct Blob upc; /* user-provided classes .ASCII-alnum.or-hypen: */
151 int bRndred; /* indicates if `text` holds a rendered content */
152
153 int defno; /* serial number of definition, set during the first pass */
154 int index; /* set to the index within array after ordering by id */
155 int iMark; /* user-visible numeric marker, assigned upon the first use*/
156 int nUsed; /* counts references to this note, increments upon each use*/
157 };
158 #define FOOTNOTE_INITIALIZER {empty_blob,empty_blob,empty_blob, 0,0,0,0,0}
159
160 /* char_trigger -- function pointer to render active chars */
161 /* returns the number of chars taken care of */
162 /* data is the pointer of the beginning of the span */
163 /* offset is the number of valid chars before data */
@@ -1100,27 +1101,99 @@
1101 assert( fn->nUsed > 0 );
1102 cleanup:
1103 release_work_buffer( rndr, id );
1104 return fn;
1105 }
1106
1107 /* Counts characters in the blank prefix within at most nHalfLines.
1108 ** A sequence of spaces and tabs counts as odd halfline,
1109 ** a newline counts as even halfline
1110 */
1111 static inline size_t count_whitespaces(
1112 const char *data, size_t size, int nHalfLines
1113 ){
1114 const char *p = data;
1115 const char * const end = data+size;
1116 while( nHalfLines > 0 ){
1117 while( p!=end && (*p==' ' || *p=='\t' ) ){ p++; }
1118 if( p==end || --nHalfLines == 0 ) break;
1119 if( *p=='\n' || *p=='\r' ){
1120 p++;
1121 if( p==end ) break;
1122 if( *p=='\n' && p[-1]=='\r' ){
1123 p++;
1124 }
1125 }
1126 nHalfLines--;
1127 }
1128 return (size_t)(p-data);
1129 }
1130
1131 /* Check if the data starts with a classlist token of the special form.
1132 ** If so then return the length of that token, otherwise return 0.
1133 **
1134 ** The token must start with a dot and must end with a colon;
1135 ** in between of these it must be a dot-separated list of words;
1136 ** each word may contain only alphanumeric characters and hyphens.
1137 */
1138 size_t is_footnote_classlist(const char * const data, size_t size){
1139 const char *p;
1140 const char * const end = data+size;
1141 if( data==end || *data != '.' ) return 0;
1142 for(p=data+1; p!=end; p++){
1143 if( fossil_isalnum(*p) || *p=='-' ) continue;
1144 if( *p==':' ){
1145 return p[-1]!='.' ? (size_t)(p-data)+1 : 0;
1146 }
1147 if( *p=='.' ){
1148 if( p[-1]!='.' ) continue;
1149 else break;
1150 }
1151 break;
1152 }
1153 return 0;
1154 }
1155
1156 /* Adds unlabeled footnote to the rndr.
1157 * If text is blank then returns 0,
1158 * otherwise returns the address of the added footnote. */
1159 static inline const struct footnote* add_inline_footnote(
1160 struct render *rndr,
1161 const char *text,
1162 size_t size
1163 ){
1164 struct footnote fn = FOOTNOTE_INITIALIZER;
1165 const char *zUPC = 0;
1166 size_t nUPC = 0, n = count_whitespaces(text, size, 3);
1167 if( n >= size ) return 0;
1168 text += n;
1169 size -= n;
1170 n = is_footnote_classlist(text, size);
1171 if( n && n < size ){
1172 nUPC = n;
1173 zUPC = text;
1174 text += nUPC;
1175 size -= nUPC;
1176 n = count_whitespaces(text, size, 3);
1177 /* naked classlist is treated as plain text */
1178 if( n >= size || text[n]=='\n' || text[n]=='\r' ){
1179 size += nUPC;
1180 nUPC = 0;
1181 text = zUPC;
1182 zUPC = 0;
1183 }else{
1184 text += n;
1185 size -= n;
1186 }
1187 }
1188 if(!size) return 0;
1189 fn.iMark = ++(rndr->notes.nMarks);
1190 fn.nUsed = 1;
1191 fn.index = COUNT_FOOTNOTES(&rndr->notes.all);
1192 assert( fn.iMark > 0 );
1193 blob_append(&fn.text, text, size);
1194 if(nUPC) blob_append(&fn.upc, zUPC, nUPC);
1195 blob_append(&rndr->notes.all, (char *)&fn, sizeof fn);
1196 return (struct footnote*)( blob_buffer(&rndr->notes.all)
1197 +( blob_size(&rndr->notes.all)-sizeof fn ));
1198 }
1199
@@ -1164,11 +1237,11 @@
1237
1238 if( size<4 || data[1]!='^' || !rndr->make.footnote_ref ) return 0;
1239 end = matching_bracket_offset(data, data+size);
1240 if( !end ) return 0;
1241 fn = add_inline_footnote(rndr, data+2, end-2);
1242 if(fn) rndr->make.footnote_ref(ob,0,&fn->upc,fn->iMark,1,rndr->make.opaque);
1243 return end+1;
1244 }
1245
1246 /* char_link -- '[': parsing a link or an image */
1247 static size_t char_link(
@@ -1296,11 +1369,11 @@
1369 if( is_img ){
1370 if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--;
1371 ret = rndr->make.image(ob, link, title, content, rndr->make.opaque);
1372 }else if(fn){
1373 if(rndr->make.footnote_ref){
1374 ret = rndr->make.footnote_ref(ob, content, &fn->upc, fn->iMark, fn->nUsed,
1375 rndr->make.opaque);
1376 }
1377 }else{
1378 ret = rndr->make.link(ob, link, title, content, rndr->make.opaque);
1379 }
@@ -2389,12 +2462,12 @@
2462 size_t beg, /* offset of the beginning of the line */
2463 size_t end, /* offset of the end of the text */
2464 size_t *last, /* last character of the link */
2465 struct Blob * footnotes
2466 ){
2467 size_t i, id_offset, id_end, upc_offset = 0, upc_size = 0;
2468 struct footnote fn = FOOTNOTE_INITIALIZER;
2469
2470 /* failfast if data is too short */
2471 if( beg+5>=end ) return 0;
2472 i = beg;
2473
@@ -2418,10 +2491,23 @@
2491 if( i>=end ) return 0;
2492
2493 if( build_ref_id(&fn.id, data+id_offset, id_end-id_offset)<0 ) return 0;
2494
2495 /* footnote's text may start on the same line */
2496 upc_offset = 0;
2497 if( data[i]!='\n' && data[i]!='\r' ){
2498 upc_offset = i; /* prevent a retry on the second line */
2499 upc_size = is_footnote_classlist(data+i,end-i);
2500 if( upc_size ){
2501 i += upc_size;
2502 while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2503 if( i>=end ){
2504 upc_size = 0; /* naked classlist is treated as plain text */
2505 i = upc_offset;
2506 }
2507 }
2508 }
2509 if( data[i]!='\n' && data[i]!='\r' ){
2510 const size_t j = i;
2511 do i++; while( i<end && data[i]!='\n' && data[i]!='\r' );
2512 blob_append(&fn.text, data+j, i-j);
2513 if( i<end ){
@@ -2450,10 +2536,25 @@
2536 /* process the 2nd and the following lines */
2537 while( i+indent<end && memcmp(data+i,spaces,indent)==0 ){
2538 size_t j;
2539 i += indent;
2540 j = i;
2541 if( !upc_offset ){
2542 /* a classlist must be provided no later than at the 2nd line */
2543 upc_offset = i + count_whitespaces(data+i, end-i, 1);
2544 upc_size = is_footnote_classlist(data+upc_offset, end-upc_offset);
2545 if( upc_size ){
2546 i = upc_offset + upc_size;
2547 while( i<end && (data[i]==' ' || data[i]=='\t') ){ i++; }
2548 if( i>=end ){
2549 upc_size = 0; /* naked classlist is treated as plain text */
2550 i = j;
2551 }else{
2552 j = i;
2553 }
2554 }
2555 }
2556 while( i<end && data[i]!='\n' && data[i]!='\r' ) i++;
2557 blob_append(&fn.text, data+j, i-j);
2558 if( i>=end ) break;
2559 blob_append_char(&fn.text, data[i]);
2560 i++;
@@ -2470,10 +2571,15 @@
2571 }
2572 /* a valid note has been found */
2573 if( last ) *last = i;
2574 if( footnotes ){
2575 fn.defno = COUNT_FOOTNOTES( footnotes );
2576 if( upc_size ){
2577 assert( upc_offset );
2578 assert( upc_offset+upc_size < end );
2579 blob_append(&fn.upc, data+upc_offset, upc_size);
2580 }
2581 blob_append(footnotes, (char *)&fn, sizeof fn);
2582 }
2583 return 1;
2584 }
2585
@@ -2680,10 +2786,11 @@
2786 while( ++j < COUNT_FOOTNOTES(notes) ){
2787 const struct footnote* x = CAST_AS_FOOTNOTES(notes) + j;
2788 assert( !x->nUsed );
2789 assert( !x->bRndred );
2790 assert( (&x->id) + 1 == &x->text ); /* see html_footnote_item() */
2791 assert( (&x->upc)- 1 == &x->text );
2792 rndr.make.footnote_item(all_items,&x->text,0,0,rndr.make.opaque);
2793 }
2794 rndr.make.footnotes(ob, all_items, rndr.make.opaque);
2795 release_work_buffer(&rndr, all_items);
2796 }
@@ -2704,12 +2811,13 @@
2811 blob_reset(&rndr.refs);
2812 fn = CAST_AS_FOOTNOTES( allNotes );
2813 end = COUNT_FOOTNOTES( allNotes );
2814 for(i=0; i<end; i++){
2815 if(blob_size(&fn[i].id)) blob_reset(&fn[i].id);
2816 if(blob_size(&fn[i].upc)) blob_reset(&fn[i].upc);
2817 blob_reset(&fn[i].text);
2818 }
2819 blob_reset(&rndr.notes.all);
2820 for(i=0; i<rndr.nBlobCache; i++){
2821 fossil_free(rndr.aBlobCache[i]);
2822 }
2823 }
2824
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -333,13 +333,62 @@
333333
){
334334
BLOB_APPEND_LITERAL(ob, " <tr>\n");
335335
BLOB_APPEND_BLOB(ob, cells);
336336
BLOB_APPEND_LITERAL(ob, " </tr>\n");
337337
}
338
+
339
+static void append_footnote_upc(
340
+ struct Blob *ob,
341
+ const struct Blob *upc, /* token of user-provided classes */
342
+ int bHTML /* if true then render markup, otherwise just a list of classes */
343
+){
344
+ const char *z = blob_buffer(upc);
345
+ int i, n = blob_size(upc);
346
+ if( n<3 ) return;
347
+ assert( z[0]=='.' && z[n-1] == ':' );
348
+
349
+ if( bHTML ){
350
+ BLOB_APPEND_LITERAL(ob, "<span class='fn-upc'>"
351
+ "<span class='fn-upcDot'>.</span>");
352
+ }
353
+ n = 0;
354
+ do{
355
+ z++;
356
+ if( *z!='.' && *z!=':' ){
357
+ assert( fossil_isalnum(*z) || *z=='-' );
358
+ n++;
359
+ continue;
360
+ }
361
+ assert( n );
362
+ if( bHTML ) BLOB_APPEND_LITERAL(ob, "<span class='");
363
+ BLOB_APPEND_LITERAL(ob, "fn-upc-");
364
+
365
+ for(i=-n; i<0; i++){
366
+ blob_append_char(ob, fossil_tolower(z[i]) );
367
+ }
368
+ if( bHTML ){
369
+ BLOB_APPEND_LITERAL(ob, "'>");
370
+ blob_append(ob, z-n, n);
371
+ BLOB_APPEND_LITERAL(ob, "</span>");
372
+ }else{
373
+ blob_append_char(ob, ' ');
374
+ }
375
+ n = 0;
376
+ if( bHTML ){
377
+ if( *z==':' ){
378
+ BLOB_APPEND_LITERAL(ob,"<span class='fn-upcColon'>:</span>");
379
+ }else{
380
+ BLOB_APPEND_LITERAL(ob,"<span class='fn-upcDot'>.</span>");
381
+ }
382
+ }
383
+ }while( *z != ':' );
384
+ if( bHTML ) BLOB_APPEND_LITERAL(ob,"</span>\n");
385
+}
338386
339387
static int html_footnote_ref(
340
- struct Blob *ob, const struct Blob *span, int iMark, int locus, void *opaque
388
+ struct Blob *ob, const struct Blob *span, const struct Blob *upc,
389
+ int iMark, int locus, void *opaque
341390
){
342391
const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
343392
const bitfield64_t l = to_base26(locus-1,0);
344393
char pos[32];
345394
memset(pos,0,32);
@@ -347,20 +396,24 @@
347396
/* expect BUGs if the following yields compiler warnings */
348397
if( iMark > 0 ){ /* a regular reference to a footnote */
349398
350399
sprintf(pos, "%s-%i-%s", ctx->unique.c, iMark, l.c);
351400
if(span && blob_size(span)) {
352
- BLOB_APPEND_LITERAL(ob,"<span class='notescope' id='noteref");
401
+ BLOB_APPEND_LITERAL(ob,"<span class='");
402
+ append_footnote_upc(ob, upc, 0);
403
+ BLOB_APPEND_LITERAL(ob,"notescope' id='noteref");
353404
blob_appendf(ob,"%s'>",pos);
354405
BLOB_APPEND_BLOB(ob, span);
355406
blob_trim(ob);
356407
BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='");
357408
BLOB_APPEND_URI(ob, ctx);
358409
blob_appendf(ob,"#footnote%s'>%i</a></sup></span>", pos, iMark);
359410
}else{
360411
blob_trim(ob);
361
- BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='");
412
+ BLOB_APPEND_LITERAL(ob,"<sup class='");
413
+ append_footnote_upc(ob, upc, 0);
414
+ BLOB_APPEND_LITERAL(ob,"noteref'><a href='");
362415
BLOB_APPEND_URI(ob, ctx);
363416
blob_appendf(ob,"#footnote%s' id='noteref%s'>%i</a></sup>",
364417
pos, pos, iMark);
365418
}
366419
}else{ /* misreference */
@@ -418,10 +471,12 @@
418471
BLOB_APPEND_LITERAL(ob,"</sup>\n<span>Misreference</span>");
419472
420473
}else if( nUsed ){ /* a regular footnote */
421474
char pos[24];
422475
const char *join = "";
476
+ /* make.footnote_item() invocations should pass args accordingly */
477
+ const struct Blob * upc = text+1;
423478
#define _joined_footnote_indicator "<ul class='fn-joined'>"
424479
#define _jfi_sz (sizeof(_joined_footnote_indicator)-1)
425480
assert( text );
426481
assert( blob_size(text) );
427482
if( blob_size(text)>=_jfi_sz &&
@@ -429,10 +484,11 @@
429484
join = "fn-joined ";
430485
}
431486
memset(pos,0,24);
432487
sprintf(pos, "%s-%i", unique, iMark);
433488
blob_appendf(ob, "<li id='footnote%s' class='%s", pos, join);
489
+ append_footnote_upc(ob, upc, 0);
434490
435491
if( nUsed == 1 ){
436492
BLOB_APPEND_LITERAL(ob, "fn-monoref'><sup class='fn-backrefs'>");
437493
blob_appendf(ob,"<a id='footnote%s-a' href='", pos);
438494
BLOB_APPEND_URI(ob, ctx);
@@ -455,10 +511,11 @@
455511
blob_appendf(ob,"#noteref%s-%s'>%s</a>", pos,l.c, l.c);
456512
}
457513
if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
458514
}
459515
BLOB_APPEND_LITERAL(ob,"</sup>\n");
516
+ append_footnote_upc(ob, upc, 1);
460517
if( join[0] ){
461518
BLOB_APPEND_LITERAL(ob,"<sup class='fn-joined'></sup><ul>");
462519
blob_append(ob,blob_buffer(text)+_jfi_sz,blob_size(text)-_jfi_sz);
463520
}else{
464521
BLOB_APPEND_BLOB(ob, text);
465522
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -333,13 +333,62 @@
333 ){
334 BLOB_APPEND_LITERAL(ob, " <tr>\n");
335 BLOB_APPEND_BLOB(ob, cells);
336 BLOB_APPEND_LITERAL(ob, " </tr>\n");
337 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
339 static int html_footnote_ref(
340 struct Blob *ob, const struct Blob *span, int iMark, int locus, void *opaque
 
341 ){
342 const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
343 const bitfield64_t l = to_base26(locus-1,0);
344 char pos[32];
345 memset(pos,0,32);
@@ -347,20 +396,24 @@
347 /* expect BUGs if the following yields compiler warnings */
348 if( iMark > 0 ){ /* a regular reference to a footnote */
349
350 sprintf(pos, "%s-%i-%s", ctx->unique.c, iMark, l.c);
351 if(span && blob_size(span)) {
352 BLOB_APPEND_LITERAL(ob,"<span class='notescope' id='noteref");
 
 
353 blob_appendf(ob,"%s'>",pos);
354 BLOB_APPEND_BLOB(ob, span);
355 blob_trim(ob);
356 BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='");
357 BLOB_APPEND_URI(ob, ctx);
358 blob_appendf(ob,"#footnote%s'>%i</a></sup></span>", pos, iMark);
359 }else{
360 blob_trim(ob);
361 BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='");
 
 
362 BLOB_APPEND_URI(ob, ctx);
363 blob_appendf(ob,"#footnote%s' id='noteref%s'>%i</a></sup>",
364 pos, pos, iMark);
365 }
366 }else{ /* misreference */
@@ -418,10 +471,12 @@
418 BLOB_APPEND_LITERAL(ob,"</sup>\n<span>Misreference</span>");
419
420 }else if( nUsed ){ /* a regular footnote */
421 char pos[24];
422 const char *join = "";
 
 
423 #define _joined_footnote_indicator "<ul class='fn-joined'>"
424 #define _jfi_sz (sizeof(_joined_footnote_indicator)-1)
425 assert( text );
426 assert( blob_size(text) );
427 if( blob_size(text)>=_jfi_sz &&
@@ -429,10 +484,11 @@
429 join = "fn-joined ";
430 }
431 memset(pos,0,24);
432 sprintf(pos, "%s-%i", unique, iMark);
433 blob_appendf(ob, "<li id='footnote%s' class='%s", pos, join);
 
434
435 if( nUsed == 1 ){
436 BLOB_APPEND_LITERAL(ob, "fn-monoref'><sup class='fn-backrefs'>");
437 blob_appendf(ob,"<a id='footnote%s-a' href='", pos);
438 BLOB_APPEND_URI(ob, ctx);
@@ -455,10 +511,11 @@
455 blob_appendf(ob,"#noteref%s-%s'>%s</a>", pos,l.c, l.c);
456 }
457 if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
458 }
459 BLOB_APPEND_LITERAL(ob,"</sup>\n");
 
460 if( join[0] ){
461 BLOB_APPEND_LITERAL(ob,"<sup class='fn-joined'></sup><ul>");
462 blob_append(ob,blob_buffer(text)+_jfi_sz,blob_size(text)-_jfi_sz);
463 }else{
464 BLOB_APPEND_BLOB(ob, text);
465
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -333,13 +333,62 @@
333 ){
334 BLOB_APPEND_LITERAL(ob, " <tr>\n");
335 BLOB_APPEND_BLOB(ob, cells);
336 BLOB_APPEND_LITERAL(ob, " </tr>\n");
337 }
338
339 static void append_footnote_upc(
340 struct Blob *ob,
341 const struct Blob *upc, /* token of user-provided classes */
342 int bHTML /* if true then render markup, otherwise just a list of classes */
343 ){
344 const char *z = blob_buffer(upc);
345 int i, n = blob_size(upc);
346 if( n<3 ) return;
347 assert( z[0]=='.' && z[n-1] == ':' );
348
349 if( bHTML ){
350 BLOB_APPEND_LITERAL(ob, "<span class='fn-upc'>"
351 "<span class='fn-upcDot'>.</span>");
352 }
353 n = 0;
354 do{
355 z++;
356 if( *z!='.' && *z!=':' ){
357 assert( fossil_isalnum(*z) || *z=='-' );
358 n++;
359 continue;
360 }
361 assert( n );
362 if( bHTML ) BLOB_APPEND_LITERAL(ob, "<span class='");
363 BLOB_APPEND_LITERAL(ob, "fn-upc-");
364
365 for(i=-n; i<0; i++){
366 blob_append_char(ob, fossil_tolower(z[i]) );
367 }
368 if( bHTML ){
369 BLOB_APPEND_LITERAL(ob, "'>");
370 blob_append(ob, z-n, n);
371 BLOB_APPEND_LITERAL(ob, "</span>");
372 }else{
373 blob_append_char(ob, ' ');
374 }
375 n = 0;
376 if( bHTML ){
377 if( *z==':' ){
378 BLOB_APPEND_LITERAL(ob,"<span class='fn-upcColon'>:</span>");
379 }else{
380 BLOB_APPEND_LITERAL(ob,"<span class='fn-upcDot'>.</span>");
381 }
382 }
383 }while( *z != ':' );
384 if( bHTML ) BLOB_APPEND_LITERAL(ob,"</span>\n");
385 }
386
387 static int html_footnote_ref(
388 struct Blob *ob, const struct Blob *span, const struct Blob *upc,
389 int iMark, int locus, void *opaque
390 ){
391 const struct MarkdownToHtml* ctx = (struct MarkdownToHtml*)opaque;
392 const bitfield64_t l = to_base26(locus-1,0);
393 char pos[32];
394 memset(pos,0,32);
@@ -347,20 +396,24 @@
396 /* expect BUGs if the following yields compiler warnings */
397 if( iMark > 0 ){ /* a regular reference to a footnote */
398
399 sprintf(pos, "%s-%i-%s", ctx->unique.c, iMark, l.c);
400 if(span && blob_size(span)) {
401 BLOB_APPEND_LITERAL(ob,"<span class='");
402 append_footnote_upc(ob, upc, 0);
403 BLOB_APPEND_LITERAL(ob,"notescope' id='noteref");
404 blob_appendf(ob,"%s'>",pos);
405 BLOB_APPEND_BLOB(ob, span);
406 blob_trim(ob);
407 BLOB_APPEND_LITERAL(ob,"<sup class='noteref'><a href='");
408 BLOB_APPEND_URI(ob, ctx);
409 blob_appendf(ob,"#footnote%s'>%i</a></sup></span>", pos, iMark);
410 }else{
411 blob_trim(ob);
412 BLOB_APPEND_LITERAL(ob,"<sup class='");
413 append_footnote_upc(ob, upc, 0);
414 BLOB_APPEND_LITERAL(ob,"noteref'><a href='");
415 BLOB_APPEND_URI(ob, ctx);
416 blob_appendf(ob,"#footnote%s' id='noteref%s'>%i</a></sup>",
417 pos, pos, iMark);
418 }
419 }else{ /* misreference */
@@ -418,10 +471,12 @@
471 BLOB_APPEND_LITERAL(ob,"</sup>\n<span>Misreference</span>");
472
473 }else if( nUsed ){ /* a regular footnote */
474 char pos[24];
475 const char *join = "";
476 /* make.footnote_item() invocations should pass args accordingly */
477 const struct Blob * upc = text+1;
478 #define _joined_footnote_indicator "<ul class='fn-joined'>"
479 #define _jfi_sz (sizeof(_joined_footnote_indicator)-1)
480 assert( text );
481 assert( blob_size(text) );
482 if( blob_size(text)>=_jfi_sz &&
@@ -429,10 +484,11 @@
484 join = "fn-joined ";
485 }
486 memset(pos,0,24);
487 sprintf(pos, "%s-%i", unique, iMark);
488 blob_appendf(ob, "<li id='footnote%s' class='%s", pos, join);
489 append_footnote_upc(ob, upc, 0);
490
491 if( nUsed == 1 ){
492 BLOB_APPEND_LITERAL(ob, "fn-monoref'><sup class='fn-backrefs'>");
493 blob_appendf(ob,"<a id='footnote%s-a' href='", pos);
494 BLOB_APPEND_URI(ob, ctx);
@@ -455,10 +511,11 @@
511 blob_appendf(ob,"#noteref%s-%s'>%s</a>", pos,l.c, l.c);
512 }
513 if( i < nUsed ) BLOB_APPEND_LITERAL(ob," &hellip;");
514 }
515 BLOB_APPEND_LITERAL(ob,"</sup>\n");
516 append_footnote_upc(ob, upc, 1);
517 if( join[0] ){
518 BLOB_APPEND_LITERAL(ob,"<sup class='fn-joined'></sup><ul>");
519 blob_append(ob,blob_buffer(text)+_jfi_sz,blob_size(text)-_jfi_sz);
520 }else{
521 BLOB_APPEND_BLOB(ob, text);
522
--- test/markdown-test3.md
+++ test/markdown-test3.md
@@ -63,10 +63,52 @@
6363
This facilitates the usage in the usual case
6464
when several footnotes are refenrenced at the end
6565
of a phrase.[^scipub][^many-refs](^All these four should
6666
be parsed as "free-standing" footnotes)[^Coelurosauria]
6767
68
+<style>
69
+ li.fn-upc-example span.fn-upc {
70
+ border: solid 2px lightgreen;
71
+ border-radius: 0.25em;
72
+ padding-left: 2px;
73
+ padding-right: 2px;
74
+ margin-bottom: 0.2em;
75
+ }
76
+ li.fn-upc-example span.fn-upcDot:first-child {
77
+ font-weight: bold;
78
+ }
79
+ sup.noteref.fn-upc-example,
80
+ span.notescope.fn-upc-example sup.noteref {
81
+ border: solid 2px lightgreen;
82
+ border-radius: 0.4em;
83
+ padding: 2px;
84
+ }
85
+ sup.noteref.fn-upc-example::after,
86
+ span.notescope.fn-upc-example sup.noteref::after {
87
+ content: " ⛄";
88
+ }
89
+ sup.noteref.fn-upc-example:hover::after,
90
+ span.notescope.fn-upc-example sup.noteref:hover::after {
91
+ content: " 👻";
92
+ }
93
+</style>
94
+
95
+It is possible to provide a list of classes for a particular footnote and
96
+all its references. This is achieved by prepending a footnote's text with
97
+a special token that starts with dot and ends with colon.
98
+(^
99
+ .alpha-Numeric123.EXAMPLE:
100
+ This token defines a dot-separated list of CSS classes
101
+ which are added to that particular footnote and also to the
102
+ corresponding reference(s). Hypens ('-') are also allowed.
103
+ Classes from the token are tranformed to lowercase and are prepended
104
+ with `"fn-upc-"` to avoid collisions.
105
+)
106
+This feature is "*opt-in*": there is nothing wrong in starting a footnote's
107
+text with a token of that form while not defining any corresponding classes
108
+in the stylesheet.[^nostyle]
109
+
68110
## Footnotes
69111
70112
[branch]: /timeline?r=markdown-footnotes&nowiki
71113
72114
[^ 1]: Footnotes is a Fossil' extention of
@@ -104,5 +146,10 @@
104146
[^undefined label is used]: For example due to a typo.
105147
106148
[^another stray]: Just to verify the correctness of ordering and styling.
107149
108150
[^scipub]: Which is common in the scientific publications.
151
+
152
+[^nostyle]:
153
+ .unused.classes:
154
+ In that case text of the footnote just looks like as if
155
+ no special processing occured.
109156
--- test/markdown-test3.md
+++ test/markdown-test3.md
@@ -63,10 +63,52 @@
63 This facilitates the usage in the usual case
64 when several footnotes are refenrenced at the end
65 of a phrase.[^scipub][^many-refs](^All these four should
66 be parsed as "free-standing" footnotes)[^Coelurosauria]
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68 ## Footnotes
69
70 [branch]: /timeline?r=markdown-footnotes&nowiki
71
72 [^ 1]: Footnotes is a Fossil' extention of
@@ -104,5 +146,10 @@
104 [^undefined label is used]: For example due to a typo.
105
106 [^another stray]: Just to verify the correctness of ordering and styling.
107
108 [^scipub]: Which is common in the scientific publications.
 
 
 
 
 
109
--- test/markdown-test3.md
+++ test/markdown-test3.md
@@ -63,10 +63,52 @@
63 This facilitates the usage in the usual case
64 when several footnotes are refenrenced at the end
65 of a phrase.[^scipub][^many-refs](^All these four should
66 be parsed as "free-standing" footnotes)[^Coelurosauria]
67
68 <style>
69 li.fn-upc-example span.fn-upc {
70 border: solid 2px lightgreen;
71 border-radius: 0.25em;
72 padding-left: 2px;
73 padding-right: 2px;
74 margin-bottom: 0.2em;
75 }
76 li.fn-upc-example span.fn-upcDot:first-child {
77 font-weight: bold;
78 }
79 sup.noteref.fn-upc-example,
80 span.notescope.fn-upc-example sup.noteref {
81 border: solid 2px lightgreen;
82 border-radius: 0.4em;
83 padding: 2px;
84 }
85 sup.noteref.fn-upc-example::after,
86 span.notescope.fn-upc-example sup.noteref::after {
87 content: " ⛄";
88 }
89 sup.noteref.fn-upc-example:hover::after,
90 span.notescope.fn-upc-example sup.noteref:hover::after {
91 content: " 👻";
92 }
93 </style>
94
95 It is possible to provide a list of classes for a particular footnote and
96 all its references. This is achieved by prepending a footnote's text with
97 a special token that starts with dot and ends with colon.
98 (^
99 .alpha-Numeric123.EXAMPLE:
100 This token defines a dot-separated list of CSS classes
101 which are added to that particular footnote and also to the
102 corresponding reference(s). Hypens ('-') are also allowed.
103 Classes from the token are tranformed to lowercase and are prepended
104 with `"fn-upc-"` to avoid collisions.
105 )
106 This feature is "*opt-in*": there is nothing wrong in starting a footnote's
107 text with a token of that form while not defining any corresponding classes
108 in the stylesheet.[^nostyle]
109
110 ## Footnotes
111
112 [branch]: /timeline?r=markdown-footnotes&nowiki
113
114 [^ 1]: Footnotes is a Fossil' extention of
@@ -104,5 +146,10 @@
146 [^undefined label is used]: For example due to a typo.
147
148 [^another stray]: Just to verify the correctness of ordering and styling.
149
150 [^scipub]: Which is common in the scientific publications.
151
152 [^nostyle]:
153 .unused.classes:
154 In that case text of the footnote just looks like as if
155 no special processing occured.
156

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button