|
dbda8d6…
|
drh
|
1 |
/* |
|
c19f34c…
|
drh
|
2 |
** Copyright (c) 2007 D. Richard Hipp |
|
c19f34c…
|
drh
|
3 |
** Copyright (c) 2008 Stephan Beal |
|
dbda8d6…
|
drh
|
4 |
** |
|
dbda8d6…
|
drh
|
5 |
** This program is free software; you can redistribute it and/or |
|
c06edd2…
|
drh
|
6 |
** modify it under the terms of the Simplified BSD License (also |
|
c06edd2…
|
drh
|
7 |
** known as the "2-Clause License" or "FreeBSD License".) |
|
99fcc43…
|
drh
|
8 |
** |
|
dbda8d6…
|
drh
|
9 |
** This program is distributed in the hope that it will be useful, |
|
c06edd2…
|
drh
|
10 |
** but without any warranty; without even the implied warranty of |
|
c06edd2…
|
drh
|
11 |
** merchantability or fitness for a particular purpose. |
|
dbda8d6…
|
drh
|
12 |
** |
|
dbda8d6…
|
drh
|
13 |
** Author contact information: |
|
dbda8d6…
|
drh
|
14 |
** [email protected] |
|
dbda8d6…
|
drh
|
15 |
** http://www.hwaci.com/drh/ |
|
dbda8d6…
|
drh
|
16 |
** |
|
dbda8d6…
|
drh
|
17 |
******************************************************************************* |
|
dbda8d6…
|
drh
|
18 |
** |
|
dbda8d6…
|
drh
|
19 |
** This file contains code to do formatting of wiki text. |
|
dbda8d6…
|
drh
|
20 |
*/ |
|
c30cd93…
|
jan.nijtmans
|
21 |
#include "config.h" |
|
5fb1152…
|
stephan
|
22 |
#include <assert.h> |
|
5fb1152…
|
stephan
|
23 |
#include <ctype.h> |
|
dbda8d6…
|
drh
|
24 |
#include "wiki.h" |
|
99fcc43…
|
drh
|
25 |
|
|
3c2aba7…
|
george
|
26 |
#define has_prefix(literal_prfx, zStr) \ |
|
3c2aba7…
|
george
|
27 |
(fossil_strncmp((zStr), "" literal_prfx, (sizeof literal_prfx)-1)==0) |
|
3c2aba7…
|
george
|
28 |
|
|
d0305b3…
|
aku
|
29 |
/* |
|
d0305b3…
|
aku
|
30 |
** Return true if the input string is a well-formed wiki page name. |
|
d0305b3…
|
aku
|
31 |
** |
|
d0305b3…
|
aku
|
32 |
** Well-formed wiki page names do not begin or end with whitespace, |
|
d0305b3…
|
aku
|
33 |
** and do not contain tabs or other control characters and do not |
|
d0305b3…
|
aku
|
34 |
** contain more than a single space character in a row. Well-formed |
|
b7ff13a…
|
joel
|
35 |
** names must be between 1 and 100 characters in length, inclusive. |
|
d0305b3…
|
aku
|
36 |
*/ |
|
1942d58…
|
drh
|
37 |
int wiki_name_is_wellformed(const unsigned char *z){ |
|
d0305b3…
|
aku
|
38 |
int i; |
|
d0305b3…
|
aku
|
39 |
if( z[0]<=0x20 ){ |
|
d0305b3…
|
aku
|
40 |
return 0; |
|
d0305b3…
|
aku
|
41 |
} |
|
d0305b3…
|
aku
|
42 |
for(i=1; z[i]; i++){ |
|
d0305b3…
|
aku
|
43 |
if( z[i]<0x20 ) return 0; |
|
d0305b3…
|
aku
|
44 |
if( z[i]==0x20 && z[i-1]==0x20 ) return 0; |
|
d0305b3…
|
aku
|
45 |
} |
|
d0305b3…
|
aku
|
46 |
if( z[i-1]==' ' ) return 0; |
|
b7ff13a…
|
joel
|
47 |
if( i<1 || i>100 ) return 0; |
|
d0305b3…
|
aku
|
48 |
return 1; |
|
aa57354…
|
drh
|
49 |
} |
|
aa57354…
|
drh
|
50 |
|
|
aa57354…
|
drh
|
51 |
/* |
|
aa57354…
|
drh
|
52 |
** Output rules for well-formed wiki pages |
|
aa57354…
|
drh
|
53 |
*/ |
|
aa57354…
|
drh
|
54 |
static void well_formed_wiki_name_rules(void){ |
|
aa57354…
|
drh
|
55 |
@ <ul> |
|
3243e63…
|
drh
|
56 |
@ <li> Must not begin or end with a space.</li> |
|
aa57354…
|
drh
|
57 |
@ <li> Must not contain any control characters, including tab or |
|
3243e63…
|
drh
|
58 |
@ newline.</li> |
|
3243e63…
|
drh
|
59 |
@ <li> Must not have two or more spaces in a row internally.</li> |
|
b7ff13a…
|
joel
|
60 |
@ <li> Must be between 1 and 100 characters in length.</li> |
|
aa57354…
|
drh
|
61 |
@ </ul> |
|
d0305b3…
|
aku
|
62 |
} |
|
d0305b3…
|
aku
|
63 |
|
|
d0305b3…
|
aku
|
64 |
/* |
|
d0305b3…
|
aku
|
65 |
** Check a wiki name. If it is not well-formed, then issue an error |
|
d0305b3…
|
aku
|
66 |
** and return true. If it is well-formed, return false. |
|
d0305b3…
|
aku
|
67 |
*/ |
|
d0305b3…
|
aku
|
68 |
static int check_name(const char *z){ |
|
1942d58…
|
drh
|
69 |
if( !wiki_name_is_wellformed((const unsigned char *)z) ){ |
|
112c713…
|
drh
|
70 |
style_set_current_feature("wiki"); |
|
d0305b3…
|
aku
|
71 |
style_header("Wiki Page Name Error"); |
|
3243e63…
|
drh
|
72 |
@ The wiki name "<span class="wikiError">%h(z)</span>" is not well-formed. |
|
3243e63…
|
drh
|
73 |
@ Rules for wiki page names: |
|
aa57354…
|
drh
|
74 |
well_formed_wiki_name_rules(); |
|
112c713…
|
drh
|
75 |
style_finish_page(); |
|
d0305b3…
|
aku
|
76 |
return 1; |
|
d0305b3…
|
aku
|
77 |
} |
|
d0305b3…
|
aku
|
78 |
return 0; |
|
423c6a9…
|
drh
|
79 |
} |
|
423c6a9…
|
drh
|
80 |
|
|
423c6a9…
|
drh
|
81 |
/* |
|
423c6a9…
|
drh
|
82 |
** Return the tagid associated with a particular wiki page. |
|
423c6a9…
|
drh
|
83 |
*/ |
|
423c6a9…
|
drh
|
84 |
int wiki_tagid(const char *zPageName){ |
|
423c6a9…
|
drh
|
85 |
return db_int(0, "SELECT tagid FROM tag WHERE tagname='wiki-%q'",zPageName); |
|
423c6a9…
|
drh
|
86 |
} |
|
5602385…
|
drh
|
87 |
int wiki_tagid2(const char *zPrefix, const char *zPageName){ |
|
5602385…
|
drh
|
88 |
return db_int(0, "SELECT tagid FROM tag WHERE tagname='wiki-%q/%q'", |
|
5602385…
|
drh
|
89 |
zPrefix, zPageName); |
|
5602385…
|
drh
|
90 |
} |
|
423c6a9…
|
drh
|
91 |
|
|
423c6a9…
|
drh
|
92 |
/* |
|
275da70…
|
danield
|
93 |
** Return the RID of the next or previous version of a wiki page. |
|
423c6a9…
|
drh
|
94 |
** Return 0 if rid is the last/first version. |
|
423c6a9…
|
drh
|
95 |
*/ |
|
423c6a9…
|
drh
|
96 |
int wiki_next(int tagid, double mtime){ |
|
423c6a9…
|
drh
|
97 |
return db_int(0, |
|
423c6a9…
|
drh
|
98 |
"SELECT srcid FROM tagxref" |
|
423c6a9…
|
drh
|
99 |
" WHERE tagid=%d AND mtime>%.16g" |
|
423c6a9…
|
drh
|
100 |
" ORDER BY mtime ASC LIMIT 1", |
|
423c6a9…
|
drh
|
101 |
tagid, mtime); |
|
423c6a9…
|
drh
|
102 |
} |
|
423c6a9…
|
drh
|
103 |
int wiki_prev(int tagid, double mtime){ |
|
423c6a9…
|
drh
|
104 |
return db_int(0, |
|
423c6a9…
|
drh
|
105 |
"SELECT srcid FROM tagxref" |
|
423c6a9…
|
drh
|
106 |
" WHERE tagid=%d AND mtime<%.16g" |
|
423c6a9…
|
drh
|
107 |
" ORDER BY mtime DESC LIMIT 1", |
|
423c6a9…
|
drh
|
108 |
tagid, mtime); |
|
7ab0328…
|
drh
|
109 |
} |
|
7ab0328…
|
drh
|
110 |
|
|
7ab0328…
|
drh
|
111 |
/* |
|
d0305b3…
|
aku
|
112 |
** WEBPAGE: home |
|
d0305b3…
|
aku
|
113 |
** WEBPAGE: index |
|
d0305b3…
|
aku
|
114 |
** WEBPAGE: not_found |
|
7ab0328…
|
drh
|
115 |
** |
|
7ab0328…
|
drh
|
116 |
** The /home, /index, and /not_found pages all redirect to the homepage |
|
7ab0328…
|
drh
|
117 |
** configured by the administrator. |
|
d0305b3…
|
aku
|
118 |
*/ |
|
d0305b3…
|
aku
|
119 |
void home_page(void){ |
|
c51dd30…
|
drh
|
120 |
char *zPageName = db_get("project-name",0); |
|
baa6df0…
|
drh
|
121 |
char *zIndexPage = db_get("index-page",0); |
|
19f5d0b…
|
drh
|
122 |
login_check_credentials(); |
|
57f1e87…
|
drh
|
123 |
cgi_check_for_malice(); |
|
baa6df0…
|
drh
|
124 |
if( zIndexPage ){ |
|
1a00862…
|
drh
|
125 |
const char *zPathInfo = P("PATH_INFO"); |
|
f4033ec…
|
drh
|
126 |
while( zIndexPage[0]=='/' ) zIndexPage++; |
|
135fcbb…
|
drh
|
127 |
while( zPathInfo[0]=='/' ) zPathInfo++; |
|
32ad9a1…
|
drh
|
128 |
if( fossil_strcmp(zIndexPage, zPathInfo)==0 ) zIndexPage = 0; |
|
baa6df0…
|
drh
|
129 |
} |
|
baa6df0…
|
drh
|
130 |
if( zIndexPage ){ |
|
a40e8a0…
|
drh
|
131 |
cgi_redirectf("%R/%s", zIndexPage); |
|
135fcbb…
|
drh
|
132 |
} |
|
b344d3c…
|
drh
|
133 |
if( !g.perm.RdWiki ){ |
|
e5c1659…
|
drh
|
134 |
cgi_redirectf("%R/login?g=home"); |
|
19f5d0b…
|
drh
|
135 |
} |
|
c51dd30…
|
drh
|
136 |
if( zPageName ){ |
|
c51dd30…
|
drh
|
137 |
login_check_credentials(); |
|
c51dd30…
|
drh
|
138 |
g.zExtra = zPageName; |
|
f8a2aa0…
|
drh
|
139 |
cgi_set_parameter_nocopy("name", g.zExtra, 1); |
|
426a3ba…
|
drh
|
140 |
g.isHome = 1; |
|
c51dd30…
|
drh
|
141 |
wiki_page(); |
|
d0305b3…
|
aku
|
142 |
return; |
|
d0305b3…
|
aku
|
143 |
} |
|
112c713…
|
drh
|
144 |
style_set_current_feature("wiki"); |
|
d0305b3…
|
aku
|
145 |
style_header("Home"); |
|
d0305b3…
|
aku
|
146 |
@ <p>This is a stub home-page for the project. |
|
d0305b3…
|
aku
|
147 |
@ To fill in this page, first go to |
|
433cde1…
|
drh
|
148 |
@ %z(href("%R/setup_config"))setup/config</a> |
|
d0305b3…
|
aku
|
149 |
@ and establish a "Project Name". Then create a |
|
d0305b3…
|
aku
|
150 |
@ wiki page with that name. The content of that wiki page |
|
d96c4a4…
|
drh
|
151 |
@ will be displayed in place of this message.</p> |
|
112c713…
|
drh
|
152 |
style_finish_page(); |
|
d0305b3…
|
aku
|
153 |
} |
|
d0305b3…
|
aku
|
154 |
|
|
d0305b3…
|
aku
|
155 |
/* |
|
d0305b3…
|
aku
|
156 |
** Return true if the given pagename is the name of the sandbox |
|
d0305b3…
|
aku
|
157 |
*/ |
|
d0305b3…
|
aku
|
158 |
static int is_sandbox(const char *zPagename){ |
|
0b6c414…
|
drh
|
159 |
return fossil_stricmp(zPagename,"sandbox")==0 || |
|
0b6c414…
|
drh
|
160 |
fossil_stricmp(zPagename,"sand box")==0; |
|
0b6c414…
|
drh
|
161 |
} |
|
0b6c414…
|
drh
|
162 |
|
|
0b6c414…
|
drh
|
163 |
/* |
|
05cd9fa…
|
rberteig
|
164 |
** Formal, common and short names for the various wiki styles. |
|
05cd9fa…
|
rberteig
|
165 |
*/ |
|
05cd9fa…
|
rberteig
|
166 |
static const char *const azStyles[] = { |
|
05cd9fa…
|
rberteig
|
167 |
"text/x-fossil-wiki", "Fossil Wiki", "wiki", |
|
05cd9fa…
|
rberteig
|
168 |
"text/x-markdown", "Markdown", "markdown", |
|
05cd9fa…
|
rberteig
|
169 |
"text/plain", "Plain Text", "plain" |
|
05cd9fa…
|
rberteig
|
170 |
}; |
|
05cd9fa…
|
rberteig
|
171 |
|
|
05cd9fa…
|
rberteig
|
172 |
/* |
|
a5a5524…
|
drh
|
173 |
** Only allow certain mimetypes through. |
|
a5a5524…
|
drh
|
174 |
** All others become "text/x-fossil-wiki" |
|
a5a5524…
|
drh
|
175 |
*/ |
|
a5a5524…
|
drh
|
176 |
const char *wiki_filter_mimetypes(const char *zMimetype){ |
|
05cd9fa…
|
rberteig
|
177 |
if( zMimetype!=0 ){ |
|
63220d9…
|
jan.nijtmans
|
178 |
int i; |
|
3cb9ba4…
|
andygoth
|
179 |
for(i=0; i<count(azStyles); i+=3){ |
|
05cd9fa…
|
rberteig
|
180 |
if( fossil_strcmp(zMimetype,azStyles[i+2])==0 ){ |
|
05cd9fa…
|
rberteig
|
181 |
return azStyles[i]; |
|
05cd9fa…
|
rberteig
|
182 |
} |
|
05cd9fa…
|
rberteig
|
183 |
} |
|
05cd9fa…
|
rberteig
|
184 |
if( fossil_strcmp(zMimetype, "text/x-markdown")==0 |
|
05cd9fa…
|
rberteig
|
185 |
|| fossil_strcmp(zMimetype, "text/plain")==0 ){ |
|
05cd9fa…
|
rberteig
|
186 |
return zMimetype; |
|
05cd9fa…
|
rberteig
|
187 |
} |
|
a5a5524…
|
drh
|
188 |
} |
|
a5a5524…
|
drh
|
189 |
return "text/x-fossil-wiki"; |
|
a5a5524…
|
drh
|
190 |
} |
|
a5a5524…
|
drh
|
191 |
|
|
a5a5524…
|
drh
|
192 |
/* |
|
bd50848…
|
drh
|
193 |
** Render wiki text according to its mimetype. |
|
bd50848…
|
drh
|
194 |
** |
|
bd50848…
|
drh
|
195 |
** text/x-fossil-wiki Fossil wiki |
|
bd50848…
|
drh
|
196 |
** text/x-markdown Markdown |
|
e32214a…
|
drh
|
197 |
** text/x-pikchr Pikchr |
|
bd50848…
|
drh
|
198 |
** anything else... Plain text |
|
382f373…
|
drh
|
199 |
** |
|
382f373…
|
drh
|
200 |
** If zMimetype is a null pointer, then use "text/x-fossil-wiki". |
|
a5a5524…
|
drh
|
201 |
*/ |
|
89b6dda…
|
drh
|
202 |
void wiki_render_by_mimetype(Blob *pWiki, const char *zMimetype){ |
|
a5a5524…
|
drh
|
203 |
if( zMimetype==0 || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ |
|
89b6dda…
|
drh
|
204 |
wiki_convert(pWiki, 0, 0); |
|
a5a5524…
|
drh
|
205 |
}else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
|
a5a5524…
|
drh
|
206 |
Blob tail = BLOB_INITIALIZER; |
|
d407c38…
|
drh
|
207 |
markdown_to_html(pWiki, 0, &tail); |
|
89b6dda…
|
drh
|
208 |
safe_html(&tail); |
|
a5a5524…
|
drh
|
209 |
@ %s(blob_str(&tail)) |
|
a5a5524…
|
drh
|
210 |
blob_reset(&tail); |
|
e32214a…
|
drh
|
211 |
}else if( fossil_strcmp(zMimetype, "text/x-pikchr")==0 ){ |
|
363f01a…
|
drh
|
212 |
int isPopup = P("popup")!=0; |
|
e32214a…
|
drh
|
213 |
const char *zPikchr = blob_str(pWiki); |
|
e32214a…
|
drh
|
214 |
int w, h; |
|
e32214a…
|
drh
|
215 |
char *zOut = pikchr(zPikchr, "pikchr", 0, &w, &h); |
|
e32214a…
|
drh
|
216 |
if( w>0 ){ |
|
363f01a…
|
drh
|
217 |
if( isPopup ) cgi_set_content_type("image/svg+xml"); |
|
363f01a…
|
drh
|
218 |
else{ |
|
363f01a…
|
drh
|
219 |
@ <div class="pikchr-svg" style="max-width:%d(w)px"> |
|
363f01a…
|
drh
|
220 |
} |
|
e32214a…
|
drh
|
221 |
@ %s(zOut) |
|
363f01a…
|
drh
|
222 |
if( !isPopup){ |
|
363f01a…
|
drh
|
223 |
@ </div> |
|
363f01a…
|
drh
|
224 |
} |
|
e32214a…
|
drh
|
225 |
}else{ |
|
c440011…
|
stephan
|
226 |
@ <pre class='error'> |
|
c440011…
|
stephan
|
227 |
@ %h(zOut) |
|
e32214a…
|
drh
|
228 |
@ </pre> |
|
e32214a…
|
drh
|
229 |
} |
|
e32214a…
|
drh
|
230 |
free(zOut); |
|
a5a5524…
|
drh
|
231 |
}else{ |
|
99fcc43…
|
drh
|
232 |
@ <pre class='textPlain'> |
|
a5a5524…
|
drh
|
233 |
@ %h(blob_str(pWiki)) |
|
a5a5524…
|
drh
|
234 |
@ </pre> |
|
a5a5524…
|
drh
|
235 |
} |
|
72759f5…
|
drh
|
236 |
} |
|
72759f5…
|
drh
|
237 |
|
|
72759f5…
|
drh
|
238 |
/* |
|
72759f5…
|
drh
|
239 |
** WEBPAGE: md_rules |
|
72759f5…
|
drh
|
240 |
** |
|
72759f5…
|
drh
|
241 |
** Show a summary of the Markdown wiki formatting rules. |
|
72759f5…
|
drh
|
242 |
*/ |
|
72759f5…
|
drh
|
243 |
void markdown_rules_page(void){ |
|
72759f5…
|
drh
|
244 |
Blob x; |
|
72759f5…
|
drh
|
245 |
int fTxt = P("txt")!=0; |
|
112c713…
|
drh
|
246 |
style_set_current_feature("wiki"); |
|
72759f5…
|
drh
|
247 |
style_header("Markdown Formatting Rules"); |
|
72759f5…
|
drh
|
248 |
if( fTxt ){ |
|
187424e…
|
andygoth
|
249 |
style_submenu_element("Formatted", "%R/md_rules"); |
|
72759f5…
|
drh
|
250 |
}else{ |
|
187424e…
|
andygoth
|
251 |
style_submenu_element("Plain-Text", "%R/md_rules?txt=1"); |
|
72759f5…
|
drh
|
252 |
} |
|
0ae2dbd…
|
drh
|
253 |
style_submenu_element("Wiki", "%R/wiki_rules"); |
|
72759f5…
|
drh
|
254 |
blob_init(&x, builtin_text("markdown.md"), -1); |
|
e95c551…
|
drh
|
255 |
blob_materialize(&x); |
|
f4dc114…
|
drh
|
256 |
interwiki_append_map_table(&x); |
|
89b6dda…
|
drh
|
257 |
safe_html_context(DOCSRC_TRUSTED); |
|
89b6dda…
|
drh
|
258 |
wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-markdown"); |
|
72759f5…
|
drh
|
259 |
blob_reset(&x); |
|
112c713…
|
drh
|
260 |
style_finish_page(); |
|
bf69c6e…
|
drh
|
261 |
} |
|
bf69c6e…
|
drh
|
262 |
|
|
bf69c6e…
|
drh
|
263 |
/* |
|
bf69c6e…
|
drh
|
264 |
** WEBPAGE: wiki_rules |
|
bf69c6e…
|
drh
|
265 |
** |
|
bf69c6e…
|
drh
|
266 |
** Show a summary of the wiki formatting rules. |
|
bf69c6e…
|
drh
|
267 |
*/ |
|
bf69c6e…
|
drh
|
268 |
void wiki_rules_page(void){ |
|
bf69c6e…
|
drh
|
269 |
Blob x; |
|
bf69c6e…
|
drh
|
270 |
int fTxt = P("txt")!=0; |
|
112c713…
|
drh
|
271 |
style_set_current_feature("wiki"); |
|
bf69c6e…
|
drh
|
272 |
style_header("Wiki Formatting Rules"); |
|
bf69c6e…
|
drh
|
273 |
if( fTxt ){ |
|
bf69c6e…
|
drh
|
274 |
style_submenu_element("Formatted", "%R/wiki_rules"); |
|
bf69c6e…
|
drh
|
275 |
}else{ |
|
bf69c6e…
|
drh
|
276 |
style_submenu_element("Plain-Text", "%R/wiki_rules?txt=1"); |
|
bf69c6e…
|
drh
|
277 |
} |
|
0ae2dbd…
|
drh
|
278 |
style_submenu_element("Markdown","%R/md_rules"); |
|
bf69c6e…
|
drh
|
279 |
blob_init(&x, builtin_text("wiki.wiki"), -1); |
|
e95c551…
|
drh
|
280 |
blob_materialize(&x); |
|
f4dc114…
|
drh
|
281 |
interwiki_append_map_table(&x); |
|
89b6dda…
|
drh
|
282 |
safe_html_context(DOCSRC_TRUSTED); |
|
89b6dda…
|
drh
|
283 |
wiki_render_by_mimetype(&x, fTxt ? "text/plain" : "text/x-fossil-wiki"); |
|
bf69c6e…
|
drh
|
284 |
blob_reset(&x); |
|
112c713…
|
drh
|
285 |
style_finish_page(); |
|
18a84ed…
|
drh
|
286 |
} |
|
18a84ed…
|
drh
|
287 |
|
|
18a84ed…
|
drh
|
288 |
/* |
|
18a84ed…
|
drh
|
289 |
** WEBPAGE: markup_help |
|
18a84ed…
|
drh
|
290 |
** |
|
18a84ed…
|
drh
|
291 |
** Show links to the md_rules and wiki_rules pages. |
|
18a84ed…
|
drh
|
292 |
*/ |
|
18a84ed…
|
drh
|
293 |
void markup_help_page(void){ |
|
112c713…
|
drh
|
294 |
style_set_current_feature("wiki"); |
|
18a84ed…
|
drh
|
295 |
style_header("Fossil Markup Styles"); |
|
18a84ed…
|
drh
|
296 |
@ <ul> |
|
18a84ed…
|
drh
|
297 |
@ <li><p>%z(href("%R/wiki_rules"))Fossil Wiki Formatting Rules</a></p></li> |
|
18a84ed…
|
drh
|
298 |
@ <li><p>%z(href("%R/md_rules"))Markdown Formatting Rules</a></p></li> |
|
18a84ed…
|
drh
|
299 |
@ </ul> |
|
112c713…
|
drh
|
300 |
style_finish_page(); |
|
636c334…
|
mistachkin
|
301 |
} |
|
636c334…
|
mistachkin
|
302 |
|
|
636c334…
|
mistachkin
|
303 |
/* |
|
636c334…
|
mistachkin
|
304 |
** Returns non-zero if moderation is required for wiki changes and wiki |
|
636c334…
|
mistachkin
|
305 |
** attachments. |
|
636c334…
|
mistachkin
|
306 |
*/ |
|
636c334…
|
mistachkin
|
307 |
int wiki_need_moderation( |
|
636c334…
|
mistachkin
|
308 |
int localUser /* Are we being called for a local interactive user? */ |
|
636c334…
|
mistachkin
|
309 |
){ |
|
636c334…
|
mistachkin
|
310 |
/* |
|
636c334…
|
mistachkin
|
311 |
** If the FOSSIL_FORCE_WIKI_MODERATION variable is set, *ALL* changes for |
|
636c334…
|
mistachkin
|
312 |
** wiki pages will be required to go through moderation (even those performed |
|
636c334…
|
mistachkin
|
313 |
** by the local interactive user via the command line). This can be useful |
|
636c334…
|
mistachkin
|
314 |
** for local (or remote) testing of the moderation subsystem and its impact |
|
636c334…
|
mistachkin
|
315 |
** on the contents and status of wiki pages. |
|
636c334…
|
mistachkin
|
316 |
*/ |
|
636c334…
|
mistachkin
|
317 |
if( fossil_getenv("FOSSIL_FORCE_WIKI_MODERATION")!=0 ){ |
|
636c334…
|
mistachkin
|
318 |
return 1; |
|
636c334…
|
mistachkin
|
319 |
} |
|
636c334…
|
mistachkin
|
320 |
if( localUser ){ |
|
636c334…
|
mistachkin
|
321 |
return 0; |
|
636c334…
|
mistachkin
|
322 |
} |
|
636c334…
|
mistachkin
|
323 |
return g.perm.ModWiki==0 && db_get_boolean("modreq-wiki",0)==1; |
|
636c334…
|
mistachkin
|
324 |
} |
|
636c334…
|
mistachkin
|
325 |
|
|
6714c94…
|
drh
|
326 |
/* Standard submenu items for wiki pages */ |
|
6714c94…
|
drh
|
327 |
#define W_SRCH 0x00001 |
|
6714c94…
|
drh
|
328 |
#define W_LIST 0x00002 |
|
6714c94…
|
drh
|
329 |
#define W_HELP 0x00004 |
|
6714c94…
|
drh
|
330 |
#define W_NEW 0x00008 |
|
6714c94…
|
drh
|
331 |
#define W_BLOG 0x00010 |
|
6714c94…
|
drh
|
332 |
#define W_SANDBOX 0x00020 |
|
6714c94…
|
drh
|
333 |
#define W_ALL 0x0001f |
|
6714c94…
|
drh
|
334 |
#define W_ALL_BUT(x) (W_ALL&~(x)) |
|
6714c94…
|
drh
|
335 |
|
|
6714c94…
|
drh
|
336 |
/* |
|
6714c94…
|
drh
|
337 |
** Add some standard submenu elements for wiki screens. |
|
6714c94…
|
drh
|
338 |
*/ |
|
6714c94…
|
drh
|
339 |
static void wiki_standard_submenu(unsigned int ok){ |
|
2e9e369…
|
drh
|
340 |
if( (ok & W_SRCH)!=0 && search_restrict(SRCH_WIKI)!=0 ){ |
|
187424e…
|
andygoth
|
341 |
style_submenu_element("Search", "%R/wikisrch"); |
|
6714c94…
|
drh
|
342 |
} |
|
6714c94…
|
drh
|
343 |
if( (ok & W_LIST)!=0 ){ |
|
187424e…
|
andygoth
|
344 |
style_submenu_element("List", "%R/wcontent"); |
|
6714c94…
|
drh
|
345 |
} |
|
6714c94…
|
drh
|
346 |
if( (ok & W_HELP)!=0 ){ |
|
187424e…
|
andygoth
|
347 |
style_submenu_element("Help", "%R/wikihelp"); |
|
653dd40…
|
drh
|
348 |
} |
|
653dd40…
|
drh
|
349 |
if( (ok & W_NEW)!=0 && g.anon.NewWiki ){ |
|
187424e…
|
andygoth
|
350 |
style_submenu_element("New", "%R/wikinew"); |
|
653dd40…
|
drh
|
351 |
} |
|
6714c94…
|
drh
|
352 |
if( (ok & W_SANDBOX)!=0 ){ |
|
56a7446…
|
stephan
|
353 |
style_submenu_element("Sandbox", "%R/wikiedit?name=Sandbox"); |
|
6714c94…
|
drh
|
354 |
} |
|
6714c94…
|
drh
|
355 |
} |
|
6714c94…
|
drh
|
356 |
|
|
6714c94…
|
drh
|
357 |
/* |
|
6714c94…
|
drh
|
358 |
** WEBPAGE: wikihelp |
|
6714c94…
|
drh
|
359 |
** A generic landing page for wiki. |
|
6714c94…
|
drh
|
360 |
*/ |
|
6714c94…
|
drh
|
361 |
void wiki_helppage(void){ |
|
6714c94…
|
drh
|
362 |
login_check_credentials(); |
|
653dd40…
|
drh
|
363 |
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
|
112c713…
|
drh
|
364 |
style_set_current_feature("wiki"); |
|
6714c94…
|
drh
|
365 |
style_header("Wiki Help"); |
|
6714c94…
|
drh
|
366 |
wiki_standard_submenu(W_ALL_BUT(W_HELP)); |
|
6714c94…
|
drh
|
367 |
@ <h2>Wiki Links</h2> |
|
6714c94…
|
drh
|
368 |
@ <ul> |
|
6714c94…
|
drh
|
369 |
@ <li> %z(href("%R/timeline?y=w"))Recent changes</a> to wiki pages.</li> |
|
6714c94…
|
drh
|
370 |
@ <li> Formatting rules for %z(href("%R/wiki_rules"))Fossil Wiki</a> and for |
|
6714c94…
|
drh
|
371 |
@ %z(href("%R/md_rules"))Markdown Wiki</a>.</li> |
|
56a7446…
|
stephan
|
372 |
@ <li> Use the %z(href("%R/wikiedit?name=Sandbox"))Sandbox</a> |
|
6714c94…
|
drh
|
373 |
@ to experiment.</li> |
|
98f5b40…
|
drh
|
374 |
if( g.perm.NewWiki ){ |
|
6714c94…
|
drh
|
375 |
@ <li> Create a %z(href("%R/wikinew"))new wiki page</a>.</li> |
|
98f5b40…
|
drh
|
376 |
if( g.perm.Write ){ |
|
bd50848…
|
drh
|
377 |
@ <li> Create a %z(href("%R/technoteedit"))new tech-note</a>.</li> |
|
6714c94…
|
drh
|
378 |
} |
|
6714c94…
|
drh
|
379 |
} |
|
6714c94…
|
drh
|
380 |
@ <li> %z(href("%R/wcontent"))List of All Wiki Pages</a> |
|
653dd40…
|
drh
|
381 |
@ available on this server.</li> |
|
c00e5d6…
|
stephan
|
382 |
@ <li> %z(href("%R/timeline?y=e"))List of All Tech-notes</a> |
|
c00e5d6…
|
stephan
|
383 |
@ available on this server.</li> |
|
98f5b40…
|
drh
|
384 |
if( g.perm.ModWiki ){ |
|
6714c94…
|
drh
|
385 |
@ <li> %z(href("%R/modreq"))Tend to pending moderation requests</a></li> |
|
6714c94…
|
drh
|
386 |
} |
|
2e9e369…
|
drh
|
387 |
if( search_restrict(SRCH_WIKI)!=0 ){ |
|
6714c94…
|
drh
|
388 |
@ <li> %z(href("%R/wikisrch"))Search</a> for wiki pages containing key |
|
6714c94…
|
drh
|
389 |
@ words</li> |
|
6714c94…
|
drh
|
390 |
} |
|
6714c94…
|
drh
|
391 |
@ </ul> |
|
112c713…
|
drh
|
392 |
style_finish_page(); |
|
6714c94…
|
drh
|
393 |
return; |
|
6714c94…
|
drh
|
394 |
} |
|
6714c94…
|
drh
|
395 |
|
|
6714c94…
|
drh
|
396 |
/* |
|
6714c94…
|
drh
|
397 |
** WEBPAGE: wikisrch |
|
6714c94…
|
drh
|
398 |
** Usage: /wikisrch?s=PATTERN |
|
6714c94…
|
drh
|
399 |
** |
|
6714c94…
|
drh
|
400 |
** Full-text search of all current wiki text |
|
6714c94…
|
drh
|
401 |
*/ |
|
6714c94…
|
drh
|
402 |
void wiki_srchpage(void){ |
|
6714c94…
|
drh
|
403 |
login_check_credentials(); |
|
112c713…
|
drh
|
404 |
style_set_current_feature("wiki"); |
|
093943d…
|
drh
|
405 |
style_header("Wiki Search"); |
|
093943d…
|
drh
|
406 |
wiki_standard_submenu(W_HELP|W_LIST|W_SANDBOX); |
|
093943d…
|
drh
|
407 |
search_screen(SRCH_WIKI, 0); |
|
112c713…
|
drh
|
408 |
style_finish_page(); |
|
093943d…
|
drh
|
409 |
} |
|
093943d…
|
drh
|
410 |
|
|
093943d…
|
drh
|
411 |
/* Return values from wiki_page_type() */ |
|
ae1dac8…
|
drh
|
412 |
#if INTERFACE |
|
ae1dac8…
|
drh
|
413 |
# define WIKITYPE_UNKNOWN (-1) |
|
ae1dac8…
|
drh
|
414 |
# define WIKITYPE_NORMAL 0 |
|
ae1dac8…
|
drh
|
415 |
# define WIKITYPE_BRANCH 1 |
|
ae1dac8…
|
drh
|
416 |
# define WIKITYPE_CHECKIN 2 |
|
ae1dac8…
|
drh
|
417 |
# define WIKITYPE_TAG 3 |
|
25f43cc…
|
stephan
|
418 |
# define WIKITYPE_TICKET 4 |
|
ae1dac8…
|
drh
|
419 |
#endif |
|
093943d…
|
drh
|
420 |
|
|
093943d…
|
drh
|
421 |
/* |
|
093943d…
|
drh
|
422 |
** Figure out what type of wiki page we are dealing with. |
|
093943d…
|
drh
|
423 |
*/ |
|
ae1dac8…
|
drh
|
424 |
int wiki_page_type(const char *zPageName){ |
|
093943d…
|
drh
|
425 |
if( db_get_boolean("wiki-about",1)==0 ){ |
|
093943d…
|
drh
|
426 |
return WIKITYPE_NORMAL; |
|
093943d…
|
drh
|
427 |
}else |
|
3c2aba7…
|
george
|
428 |
if( has_prefix("checkin/", zPageName) |
|
093943d…
|
drh
|
429 |
&& db_exists("SELECT 1 FROM blob WHERE uuid=%Q",zPageName+8) |
|
093943d…
|
drh
|
430 |
){ |
|
093943d…
|
drh
|
431 |
return WIKITYPE_CHECKIN; |
|
093943d…
|
drh
|
432 |
}else |
|
3c2aba7…
|
george
|
433 |
if( has_prefix("branch/", zPageName) ){ |
|
093943d…
|
drh
|
434 |
return WIKITYPE_BRANCH; |
|
093943d…
|
drh
|
435 |
}else |
|
3c2aba7…
|
george
|
436 |
if( has_prefix("tag/", zPageName) ){ |
|
093943d…
|
drh
|
437 |
return WIKITYPE_TAG; |
|
25f43cc…
|
stephan
|
438 |
}else |
|
3c2aba7…
|
george
|
439 |
if( has_prefix("ticket/", zPageName) ){ |
|
25f43cc…
|
stephan
|
440 |
return WIKITYPE_TICKET; |
|
093943d…
|
drh
|
441 |
} |
|
093943d…
|
drh
|
442 |
return WIKITYPE_NORMAL; |
|
19f2753…
|
stephan
|
443 |
} |
|
19f2753…
|
stephan
|
444 |
|
|
19f2753…
|
stephan
|
445 |
/* |
|
19f2753…
|
stephan
|
446 |
** Returns a JSON-friendly string form of the integer value returned |
|
19f2753…
|
stephan
|
447 |
** by wiki_page_type(zPageName). |
|
19f2753…
|
stephan
|
448 |
*/ |
|
19f2753…
|
stephan
|
449 |
const char * wiki_page_type_name(const char *zPageName){ |
|
19f2753…
|
stephan
|
450 |
switch(wiki_page_type(zPageName)){ |
|
19f2753…
|
stephan
|
451 |
case WIKITYPE_CHECKIN: return "checkin"; |
|
19f2753…
|
stephan
|
452 |
case WIKITYPE_BRANCH: return "branch"; |
|
19f2753…
|
stephan
|
453 |
case WIKITYPE_TAG: return "tag"; |
|
25f43cc…
|
stephan
|
454 |
case WIKITYPE_TICKET: return "ticket"; |
|
19f2753…
|
stephan
|
455 |
case WIKITYPE_NORMAL: |
|
19f2753…
|
stephan
|
456 |
default: return "normal"; |
|
19f2753…
|
stephan
|
457 |
} |
|
093943d…
|
drh
|
458 |
} |
|
093943d…
|
drh
|
459 |
|
|
093943d…
|
drh
|
460 |
/* |
|
093943d…
|
drh
|
461 |
** Add an appropriate style_header() for either the /wiki or /wikiedit page |
|
093943d…
|
drh
|
462 |
** for zPageName. zExtra is an empty string for /wiki but has the text |
|
093943d…
|
drh
|
463 |
** "Edit: " for /wikiedit. |
|
093943d…
|
drh
|
464 |
** |
|
093943d…
|
drh
|
465 |
** If the page is /wiki and the page is one of the special times (check-in, |
|
275da70…
|
danield
|
466 |
** branch, or tag) and the "p" query parameter is omitted, then do a |
|
093943d…
|
drh
|
467 |
** redirect to the display of the check-in, branch, or tag rather than |
|
093943d…
|
drh
|
468 |
** continuing to the plain wiki display. |
|
093943d…
|
drh
|
469 |
*/ |
|
093943d…
|
drh
|
470 |
static int wiki_page_header( |
|
093943d…
|
drh
|
471 |
int eType, /* Page type. Might be WIKITYPE_UNKNOWN */ |
|
093943d…
|
drh
|
472 |
const char *zPageName, /* Name of the page */ |
|
093943d…
|
drh
|
473 |
const char *zExtra /* Extra prefix text on the page header */ |
|
093943d…
|
drh
|
474 |
){ |
|
112c713…
|
drh
|
475 |
style_set_current_feature("wiki"); |
|
093943d…
|
drh
|
476 |
if( eType==WIKITYPE_UNKNOWN ) eType = wiki_page_type(zPageName); |
|
867fe0e…
|
drh
|
477 |
switch( eType ){ |
|
867fe0e…
|
drh
|
478 |
case WIKITYPE_NORMAL: { |
|
867fe0e…
|
drh
|
479 |
style_header("%s%s", zExtra, zPageName); |
|
867fe0e…
|
drh
|
480 |
break; |
|
867fe0e…
|
drh
|
481 |
} |
|
867fe0e…
|
drh
|
482 |
case WIKITYPE_CHECKIN: { |
|
867fe0e…
|
drh
|
483 |
zPageName += 8; |
|
093943d…
|
drh
|
484 |
if( zExtra[0]==0 && !P("p") ){ |
|
093943d…
|
drh
|
485 |
cgi_redirectf("%R/info/%s",zPageName); |
|
093943d…
|
drh
|
486 |
}else{ |
|
bc36fdc…
|
danield
|
487 |
style_header("Notes About Check-in %S", zPageName); |
|
275da70…
|
danield
|
488 |
style_submenu_element("Check-in Timeline","%R/timeline?f=%s", |
|
275da70…
|
danield
|
489 |
zPageName); |
|
bc36fdc…
|
danield
|
490 |
style_submenu_element("Check-in Info","%R/info/%s", zPageName); |
|
093943d…
|
drh
|
491 |
} |
|
867fe0e…
|
drh
|
492 |
break; |
|
867fe0e…
|
drh
|
493 |
} |
|
867fe0e…
|
drh
|
494 |
case WIKITYPE_BRANCH: { |
|
867fe0e…
|
drh
|
495 |
zPageName += 7; |
|
093943d…
|
drh
|
496 |
if( zExtra[0]==0 && !P("p") ){ |
|
093943d…
|
drh
|
497 |
cgi_redirectf("%R/timeline?r=%t", zPageName); |
|
093943d…
|
drh
|
498 |
}else{ |
|
093943d…
|
drh
|
499 |
style_header("Notes About Branch %h", zPageName); |
|
093943d…
|
drh
|
500 |
style_submenu_element("Branch Timeline","%R/timeline?r=%t", zPageName); |
|
093943d…
|
drh
|
501 |
} |
|
867fe0e…
|
drh
|
502 |
break; |
|
867fe0e…
|
drh
|
503 |
} |
|
867fe0e…
|
drh
|
504 |
case WIKITYPE_TAG: { |
|
867fe0e…
|
drh
|
505 |
zPageName += 4; |
|
093943d…
|
drh
|
506 |
if( zExtra[0]==0 && !P("p") ){ |
|
093943d…
|
drh
|
507 |
cgi_redirectf("%R/timeline?t=%t",zPageName); |
|
093943d…
|
drh
|
508 |
}else{ |
|
093943d…
|
drh
|
509 |
style_header("Notes About Tag %h", zPageName); |
|
093943d…
|
drh
|
510 |
style_submenu_element("Tag Timeline","%R/timeline?t=%t",zPageName); |
|
093943d…
|
drh
|
511 |
} |
|
093943d…
|
drh
|
512 |
break; |
|
093943d…
|
drh
|
513 |
} |
|
25f43cc…
|
stephan
|
514 |
case WIKITYPE_TICKET: { |
|
25f43cc…
|
stephan
|
515 |
zPageName += 7; |
|
25f43cc…
|
stephan
|
516 |
if( zExtra[0]==0 && !P("p") ){ |
|
25f43cc…
|
stephan
|
517 |
cgi_redirectf("%R/tktview/%s",zPageName); |
|
25f43cc…
|
stephan
|
518 |
}else{ |
|
25f43cc…
|
stephan
|
519 |
style_header("Notes About Ticket %h", zPageName); |
|
25f43cc…
|
stephan
|
520 |
style_submenu_element("Ticket","%R/tktview/%s",zPageName); |
|
25f43cc…
|
stephan
|
521 |
} |
|
25f43cc…
|
stephan
|
522 |
break; |
|
25f43cc…
|
stephan
|
523 |
} |
|
867fe0e…
|
drh
|
524 |
} |
|
867fe0e…
|
drh
|
525 |
return eType; |
|
5602385…
|
drh
|
526 |
} |
|
5602385…
|
drh
|
527 |
|
|
5602385…
|
drh
|
528 |
/* |
|
5602385…
|
drh
|
529 |
** Wiki pages with special names "branch/...", "checkin/...", and "tag/..." |
|
5602385…
|
drh
|
530 |
** requires perm.Write privilege in addition to perm.WrWiki in order |
|
5602385…
|
drh
|
531 |
** to write. This function determines whether the extra perm.Write |
|
5602385…
|
drh
|
532 |
** is required and available. Return true if writing to the wiki page |
|
5602385…
|
drh
|
533 |
** may proceed, and return false if permission is lacking. |
|
5602385…
|
drh
|
534 |
*/ |
|
5602385…
|
drh
|
535 |
static int wiki_special_permission(const char *zPageName){ |
|
5602385…
|
drh
|
536 |
if( strncmp(zPageName,"branch/",7)!=0 |
|
5602385…
|
drh
|
537 |
&& strncmp(zPageName,"checkin/",8)!=0 |
|
5602385…
|
drh
|
538 |
&& strncmp(zPageName,"tag/",4)!=0 |
|
25f43cc…
|
stephan
|
539 |
&& strncmp(zPageName,"ticket/",7)!=0 |
|
5602385…
|
drh
|
540 |
){ |
|
5602385…
|
drh
|
541 |
return 1; |
|
5602385…
|
drh
|
542 |
} |
|
5602385…
|
drh
|
543 |
if( db_get_boolean("wiki-about",1)==0 ){ |
|
5602385…
|
drh
|
544 |
return 1; |
|
25f43cc…
|
stephan
|
545 |
} |
|
25f43cc…
|
stephan
|
546 |
if( strncmp(zPageName,"ticket/",7)==0 ){ |
|
25f43cc…
|
stephan
|
547 |
return g.perm.WrTkt; |
|
993700a…
|
drh
|
548 |
} |
|
5602385…
|
drh
|
549 |
return g.perm.Write; |
|
6714c94…
|
drh
|
550 |
} |
|
6714c94…
|
drh
|
551 |
|
|
a5a5524…
|
drh
|
552 |
/* |
|
ba418ee…
|
drh
|
553 |
** WEBPAGE: wiki |
|
993700a…
|
drh
|
554 |
** |
|
993700a…
|
drh
|
555 |
** Display a wiki page. Example: /wiki?name=PAGENAME |
|
993700a…
|
drh
|
556 |
** |
|
993700a…
|
drh
|
557 |
** Query parameters: |
|
993700a…
|
drh
|
558 |
** |
|
993700a…
|
drh
|
559 |
** name=NAME Name of the wiki page to display. Required. |
|
993700a…
|
drh
|
560 |
** nsm Omit the submenu if present. (Mnemonic: No SubMenu) |
|
093943d…
|
drh
|
561 |
** p Always show just the wiki page. For special |
|
093943d…
|
drh
|
562 |
** pages for check-ins, branches, or tags, there will |
|
093943d…
|
drh
|
563 |
** be a redirect to the associated /info page unless |
|
093943d…
|
drh
|
564 |
** this query parameter is present. |
|
36a17f3…
|
drh
|
565 |
** popup Suppress the header and footer and other page |
|
36a17f3…
|
drh
|
566 |
** boilerplate and only return the formatted content |
|
36a17f3…
|
drh
|
567 |
** of the wiki page. |
|
ba418ee…
|
drh
|
568 |
*/ |
|
ba418ee…
|
drh
|
569 |
void wiki_page(void){ |
|
ba418ee…
|
drh
|
570 |
char *zTag; |
|
ba418ee…
|
drh
|
571 |
int rid = 0; |
|
ba418ee…
|
drh
|
572 |
int isSandbox; |
|
98f5b40…
|
drh
|
573 |
unsigned submenuFlags = W_HELP; |
|
ba418ee…
|
drh
|
574 |
Blob wiki; |
|
ba418ee…
|
drh
|
575 |
Manifest *pWiki = 0; |
|
ba418ee…
|
drh
|
576 |
const char *zPageName; |
|
9f3fb6c…
|
drh
|
577 |
const char *zMimetype = 0; |
|
36a17f3…
|
drh
|
578 |
int isPopup = P("popup")!=0; |
|
4c3e172…
|
danield
|
579 |
char *zBody = fossil_strdup("<i>Empty Page</i>"); |
|
da2f152…
|
drh
|
580 |
int noSubmenu = P("nsm")!=0 || g.isHome; |
|
ba418ee…
|
drh
|
581 |
|
|
ba418ee…
|
drh
|
582 |
login_check_credentials(); |
|
653dd40…
|
drh
|
583 |
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
|
ba418ee…
|
drh
|
584 |
zPageName = P("name"); |
|
3d1e634…
|
stephan
|
585 |
(void)P("s")/*for cgi_check_for_malice(). "s" == search stringy*/; |
|
57f1e87…
|
drh
|
586 |
cgi_check_for_malice(); |
|
ba418ee…
|
drh
|
587 |
if( zPageName==0 ){ |
|
2e9e369…
|
drh
|
588 |
if( search_restrict(SRCH_WIKI)!=0 ){ |
|
6714c94…
|
drh
|
589 |
wiki_srchpage(); |
|
6714c94…
|
drh
|
590 |
}else{ |
|
6714c94…
|
drh
|
591 |
wiki_helppage(); |
|
6714c94…
|
drh
|
592 |
} |
|
ba418ee…
|
drh
|
593 |
return; |
|
ba418ee…
|
drh
|
594 |
} |
|
ba418ee…
|
drh
|
595 |
if( check_name(zPageName) ) return; |
|
ba418ee…
|
drh
|
596 |
isSandbox = is_sandbox(zPageName); |
|
ba418ee…
|
drh
|
597 |
if( isSandbox ){ |
|
6714c94…
|
drh
|
598 |
submenuFlags &= ~W_SANDBOX; |
|
ba418ee…
|
drh
|
599 |
zBody = db_get("sandbox",zBody); |
|
a5a5524…
|
drh
|
600 |
zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
|
66b3d39…
|
drh
|
601 |
rid = 0; |
|
ba418ee…
|
drh
|
602 |
}else{ |
|
8e02c26…
|
drh
|
603 |
const char *zUuid = P("id"); |
|
8e02c26…
|
drh
|
604 |
if( zUuid==0 || (rid = symbolic_name_to_rid(zUuid,"w"))==0 ){ |
|
8e02c26…
|
drh
|
605 |
zTag = mprintf("wiki-%s", zPageName); |
|
8e02c26…
|
drh
|
606 |
rid = db_int(0, |
|
8e02c26…
|
drh
|
607 |
"SELECT rid FROM tagxref" |
|
8e02c26…
|
drh
|
608 |
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
|
8e02c26…
|
drh
|
609 |
" ORDER BY mtime DESC", zTag |
|
8e02c26…
|
drh
|
610 |
); |
|
8e02c26…
|
drh
|
611 |
free(zTag); |
|
8e02c26…
|
drh
|
612 |
} |
|
ec81aee…
|
jan.nijtmans
|
613 |
pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
|
ba418ee…
|
drh
|
614 |
if( pWiki ){ |
|
ba418ee…
|
drh
|
615 |
zBody = pWiki->zWiki; |
|
a5a5524…
|
drh
|
616 |
zMimetype = pWiki->zMimetype; |
|
a5a5524…
|
drh
|
617 |
} |
|
a5a5524…
|
drh
|
618 |
} |
|
a5a5524…
|
drh
|
619 |
zMimetype = wiki_filter_mimetypes(zMimetype); |
|
da2f152…
|
drh
|
620 |
if( !noSubmenu ){ |
|
5602385…
|
drh
|
621 |
if( ((rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki)) |
|
5602385…
|
drh
|
622 |
&& wiki_special_permission(zPageName) |
|
5602385…
|
drh
|
623 |
){ |
|
19f2753…
|
stephan
|
624 |
style_submenu_element("Edit", "%R/wikiedit?name=%T", zPageName); |
|
3790dbb…
|
drh
|
625 |
}else if( rid && g.perm.ApndWiki ){ |
|
3790dbb…
|
drh
|
626 |
style_submenu_element("Edit", "%R/wikiappend?name=%T", zPageName); |
|
ba418ee…
|
drh
|
627 |
} |
|
ba418ee…
|
drh
|
628 |
if( g.perm.Hyperlink ){ |
|
3790dbb…
|
drh
|
629 |
style_submenu_element("History", "%R/whistory?name=%T", zPageName); |
|
3790dbb…
|
drh
|
630 |
} |
|
3790dbb…
|
drh
|
631 |
} |
|
36a17f3…
|
drh
|
632 |
if( !isPopup ){ |
|
36a17f3…
|
drh
|
633 |
style_set_current_page("%T?name=%T", g.zPath, zPageName); |
|
36a17f3…
|
drh
|
634 |
wiki_page_header(WIKITYPE_UNKNOWN, zPageName, ""); |
|
36a17f3…
|
drh
|
635 |
if( !noSubmenu ){ |
|
36a17f3…
|
drh
|
636 |
wiki_standard_submenu(submenuFlags); |
|
36a17f3…
|
drh
|
637 |
} |
|
993700a…
|
drh
|
638 |
} |
|
c7a7a56…
|
drh
|
639 |
if( zBody[0]==0 ){ |
|
c7a7a56…
|
drh
|
640 |
@ <i>This page has been deleted</i> |
|
c7a7a56…
|
drh
|
641 |
}else{ |
|
c7a7a56…
|
drh
|
642 |
blob_init(&wiki, zBody, -1); |
|
89b6dda…
|
drh
|
643 |
safe_html_context(DOCSRC_WIKI); |
|
89b6dda…
|
drh
|
644 |
wiki_render_by_mimetype(&wiki, zMimetype); |
|
c7a7a56…
|
drh
|
645 |
blob_reset(&wiki); |
|
c7a7a56…
|
drh
|
646 |
} |
|
ba418ee…
|
drh
|
647 |
manifest_destroy(pWiki); |
|
36a17f3…
|
drh
|
648 |
if( !isPopup ){ |
|
4f9ecd0…
|
stephan
|
649 |
char * zLabel = mprintf("<h2><a href='%R/attachlist?page=%T'>" |
|
4f9ecd0…
|
stephan
|
650 |
"Attachments</a>:</h2>", |
|
6e44230…
|
stephan
|
651 |
zPageName); |
|
4f9ecd0…
|
stephan
|
652 |
attachment_list(zPageName, zLabel, 1); |
|
6e44230…
|
stephan
|
653 |
fossil_free(zLabel); |
|
36a17f3…
|
drh
|
654 |
document_emit_js(/*for optional pikchr support*/); |
|
36a17f3…
|
drh
|
655 |
style_finish_page(); |
|
36a17f3…
|
drh
|
656 |
} |
|
ba418ee…
|
drh
|
657 |
} |
|
ba418ee…
|
drh
|
658 |
|
|
ba418ee…
|
drh
|
659 |
/* |
|
ba418ee…
|
drh
|
660 |
** Write a wiki artifact into the repository |
|
ba418ee…
|
drh
|
661 |
*/ |
|
99fcc43…
|
drh
|
662 |
int wiki_put(Blob *pWiki, int parent, int needMod){ |
|
ba418ee…
|
drh
|
663 |
int nrid; |
|
636c334…
|
mistachkin
|
664 |
if( !needMod ){ |
|
ba418ee…
|
drh
|
665 |
nrid = content_put_ex(pWiki, 0, 0, 0, 0); |
|
5b7888c…
|
stephan
|
666 |
if( parent ) content_deltify(parent, &nrid, 1, 0); |
|
ba418ee…
|
drh
|
667 |
}else{ |
|
ba418ee…
|
drh
|
668 |
nrid = content_put_ex(pWiki, 0, 0, 0, 1); |
|
ba418ee…
|
drh
|
669 |
moderation_table_create(); |
|
ba418ee…
|
drh
|
670 |
db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid); |
|
ba418ee…
|
drh
|
671 |
} |
|
673dc38…
|
stephan
|
672 |
db_add_unsent(nrid); |
|
ba418ee…
|
drh
|
673 |
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid); |
|
1311841…
|
jan.nijtmans
|
674 |
manifest_crosslink(nrid, pWiki, MC_NONE); |
|
d7e10ce…
|
drh
|
675 |
if( login_is_individual() ){ |
|
d7e10ce…
|
drh
|
676 |
alert_user_contact(login_name()); |
|
d7e10ce…
|
drh
|
677 |
} |
|
99fcc43…
|
drh
|
678 |
return nrid; |
|
05cd9fa…
|
rberteig
|
679 |
} |
|
a5a5524…
|
drh
|
680 |
|
|
a5a5524…
|
drh
|
681 |
/* |
|
a5a5524…
|
drh
|
682 |
** Output a selection box from which the user can select the |
|
fcf17b2…
|
drh
|
683 |
** wiki mimetype. Arguments: |
|
fcf17b2…
|
drh
|
684 |
** |
|
fcf17b2…
|
drh
|
685 |
** zMimetype - The current value of the query parameter |
|
fcf17b2…
|
drh
|
686 |
** zParam - The name of the query parameter |
|
a5a5524…
|
drh
|
687 |
*/ |
|
fcf17b2…
|
drh
|
688 |
void mimetype_option_menu(const char *zMimetype, const char *zParam){ |
|
a5a5524…
|
drh
|
689 |
unsigned i; |
|
fcf17b2…
|
drh
|
690 |
@ <select name="%s(zParam)" size="1"> |
|
3cb9ba4…
|
andygoth
|
691 |
for(i=0; i<count(azStyles); i+=3){ |
|
a5a5524…
|
drh
|
692 |
if( fossil_strcmp(zMimetype,azStyles[i])==0 ){ |
|
a5a5524…
|
drh
|
693 |
@ <option value="%s(azStyles[i])" selected>%s(azStyles[i+1])</option> |
|
a5a5524…
|
drh
|
694 |
}else{ |
|
a5a5524…
|
drh
|
695 |
@ <option value="%s(azStyles[i])">%s(azStyles[i+1])</option> |
|
a5a5524…
|
drh
|
696 |
} |
|
a5a5524…
|
drh
|
697 |
} |
|
a5a5524…
|
drh
|
698 |
@ </select> |
|
a5a5524…
|
drh
|
699 |
} |
|
a5a5524…
|
drh
|
700 |
|
|
a5a5524…
|
drh
|
701 |
/* |
|
a5a5524…
|
drh
|
702 |
** Given a mimetype, return its common name. |
|
a5a5524…
|
drh
|
703 |
*/ |
|
a5a5524…
|
drh
|
704 |
static const char *mimetype_common_name(const char *zMimetype){ |
|
a5a5524…
|
drh
|
705 |
int i; |
|
202cbcf…
|
stephan
|
706 |
for(i=6; i>=0; i-=3){ |
|
a5a5524…
|
drh
|
707 |
if( zMimetype && fossil_strcmp(zMimetype, azStyles[i])==0 ){ |
|
a5a5524…
|
drh
|
708 |
return azStyles[i+1]; |
|
a5a5524…
|
drh
|
709 |
} |
|
a5a5524…
|
drh
|
710 |
} |
|
a5a5524…
|
drh
|
711 |
return azStyles[1]; |
|
a5a5524…
|
drh
|
712 |
} |
|
a5a5524…
|
drh
|
713 |
|
|
a5a5524…
|
drh
|
714 |
/* |
|
19f2753…
|
stephan
|
715 |
** Tries to fetch a wiki page for the given name. If found, it |
|
19f2753…
|
stephan
|
716 |
** returns true, else false. |
|
19f2753…
|
stephan
|
717 |
** |
|
19f2753…
|
stephan
|
718 |
** versionsBack specifies how many versions back in the history to |
|
19f2753…
|
stephan
|
719 |
** fetch. Use 0 for the latest version, 1 for its parent, etc. |
|
19f2753…
|
stephan
|
720 |
** |
|
19f2753…
|
stephan
|
721 |
** If pRid is not NULL then if a result is found *pRid is set to its |
|
19f2753…
|
stephan
|
722 |
** RID. If ppWiki is not NULL then if found *ppWiki is set to the |
|
19f2753…
|
stephan
|
723 |
** loaded wiki object, which the caller is responsible for passing to |
|
19f2753…
|
stephan
|
724 |
** manifest_destroy(). |
|
19f2753…
|
stephan
|
725 |
*/ |
|
19f2753…
|
stephan
|
726 |
static int wiki_fetch_by_name( const char *zPageName, |
|
19f2753…
|
stephan
|
727 |
unsigned int versionsBack, |
|
19f2753…
|
stephan
|
728 |
int * pRid, Manifest **ppWiki ){ |
|
19f2753…
|
stephan
|
729 |
Manifest *pWiki = 0; |
|
19f2753…
|
stephan
|
730 |
char *zTag = mprintf("wiki-%s", zPageName); |
|
19f2753…
|
stephan
|
731 |
Stmt q = empty_Stmt; |
|
19f2753…
|
stephan
|
732 |
int rid = 0; |
|
19f2753…
|
stephan
|
733 |
|
|
19f2753…
|
stephan
|
734 |
db_prepare(&q, "SELECT rid FROM tagxref" |
|
19f2753…
|
stephan
|
735 |
" WHERE tagid=(SELECT tagid FROM tag WHERE" |
|
19f2753…
|
stephan
|
736 |
" tagname=%Q) " |
|
19f2753…
|
stephan
|
737 |
" ORDER BY mtime DESC LIMIT -1 OFFSET %u", zTag, |
|
19f2753…
|
stephan
|
738 |
versionsBack); |
|
19f2753…
|
stephan
|
739 |
fossil_free(zTag); |
|
19f2753…
|
stephan
|
740 |
if(SQLITE_ROW == db_step(&q)){ |
|
19f2753…
|
stephan
|
741 |
rid = db_column_int(&q, 0); |
|
19f2753…
|
stephan
|
742 |
} |
|
19f2753…
|
stephan
|
743 |
db_finalize(&q); |
|
19f2753…
|
stephan
|
744 |
if( rid == 0 ){ |
|
19f2753…
|
stephan
|
745 |
return 0; |
|
19f2753…
|
stephan
|
746 |
} |
|
19f2753…
|
stephan
|
747 |
else if(pRid){ |
|
19f2753…
|
stephan
|
748 |
*pRid = rid; |
|
19f2753…
|
stephan
|
749 |
} |
|
19f2753…
|
stephan
|
750 |
if(ppWiki){ |
|
19f2753…
|
stephan
|
751 |
pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
|
19f2753…
|
stephan
|
752 |
if( pWiki==0 ){ |
|
19f2753…
|
stephan
|
753 |
/* "Cannot happen." */ |
|
19f2753…
|
stephan
|
754 |
return 0; |
|
19f2753…
|
stephan
|
755 |
} |
|
19f2753…
|
stephan
|
756 |
*ppWiki = pWiki; |
|
19f2753…
|
stephan
|
757 |
} |
|
19f2753…
|
stephan
|
758 |
return 1; |
|
19f2753…
|
stephan
|
759 |
} |
|
19f2753…
|
stephan
|
760 |
|
|
19f2753…
|
stephan
|
761 |
/* |
|
19f2753…
|
stephan
|
762 |
** Determines whether the wiki page with the given name can be edited |
|
19f2753…
|
stephan
|
763 |
** or created by the current user. If not, an AJAX error is queued and |
|
19f2753…
|
stephan
|
764 |
** false is returned, else true is returned. A NULL, empty, or |
|
19f2753…
|
stephan
|
765 |
** malformed name is considered non-writable, regardless of the user. |
|
19f2753…
|
stephan
|
766 |
** |
|
19f2753…
|
stephan
|
767 |
** If pRid is not NULL then this function writes the page's rid to |
|
19f2753…
|
stephan
|
768 |
** *pRid (whether or not access is granted). On error or if the page |
|
19f2753…
|
stephan
|
769 |
** does not yet exist, *pRid will be set to 0. |
|
19f2753…
|
stephan
|
770 |
** |
|
19f2753…
|
stephan
|
771 |
** Note that the sandbox is a special case: it is a pseudo-page with |
|
19f2753…
|
stephan
|
772 |
** no rid and the /wikiajax API does not allow anyone to actually save |
|
19f2753…
|
stephan
|
773 |
** a sandbox page, but it is reported as writable here (with rid 0). |
|
19f2753…
|
stephan
|
774 |
*/ |
|
19f2753…
|
stephan
|
775 |
static int wiki_ajax_can_write(const char *zPageName, int * pRid){ |
|
19f2753…
|
stephan
|
776 |
int rid = 0; |
|
19f2753…
|
stephan
|
777 |
const char * zErr = 0; |
|
275da70…
|
danield
|
778 |
|
|
19f2753…
|
stephan
|
779 |
if(pRid) *pRid = 0; |
|
19f2753…
|
stephan
|
780 |
if(!zPageName || !*zPageName |
|
19f2753…
|
stephan
|
781 |
|| !wiki_name_is_wellformed((unsigned const char *)zPageName)){ |
|
19f2753…
|
stephan
|
782 |
zErr = "Invalid page name."; |
|
19f2753…
|
stephan
|
783 |
}else if(is_sandbox(zPageName)){ |
|
19f2753…
|
stephan
|
784 |
return 1; |
|
19f2753…
|
stephan
|
785 |
}else{ |
|
19f2753…
|
stephan
|
786 |
wiki_fetch_by_name(zPageName, 0, &rid, 0); |
|
19f2753…
|
stephan
|
787 |
if(pRid) *pRid = rid; |
|
19f2753…
|
stephan
|
788 |
if(!wiki_special_permission(zPageName)){ |
|
19f2753…
|
stephan
|
789 |
zErr = "Editing this page requires non-wiki write permissions."; |
|
19f2753…
|
stephan
|
790 |
}else if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){ |
|
19f2753…
|
stephan
|
791 |
return 3; |
|
19f2753…
|
stephan
|
792 |
}else if(rid && !g.perm.WrWiki){ |
|
19f2753…
|
stephan
|
793 |
zErr = "Requires wiki-write permissions."; |
|
19f2753…
|
stephan
|
794 |
}else if(!rid && !g.perm.NewWiki){ |
|
19f2753…
|
stephan
|
795 |
zErr = "Requires new-wiki permissions."; |
|
19f2753…
|
stephan
|
796 |
}else{ |
|
19f2753…
|
stephan
|
797 |
zErr = "Cannot happen! Please report this as a bug."; |
|
19f2753…
|
stephan
|
798 |
} |
|
19f2753…
|
stephan
|
799 |
} |
|
19f2753…
|
stephan
|
800 |
ajax_route_error(403, "%s", zErr); |
|
275da70…
|
danield
|
801 |
return 0; |
|
ce15e35…
|
stephan
|
802 |
} |
|
ce15e35…
|
stephan
|
803 |
|
|
ce15e35…
|
stephan
|
804 |
|
|
ce15e35…
|
stephan
|
805 |
/* |
|
ce15e35…
|
stephan
|
806 |
** Emits an array of attachment info records for the given wiki page |
|
ce15e35…
|
stephan
|
807 |
** artifact. |
|
ce15e35…
|
stephan
|
808 |
** |
|
ce15e35…
|
stephan
|
809 |
** Output format: |
|
ce15e35…
|
stephan
|
810 |
** |
|
ce15e35…
|
stephan
|
811 |
** [{ |
|
ce15e35…
|
stephan
|
812 |
** "uuid": attachment artifact hash, |
|
ce15e35…
|
stephan
|
813 |
** "src": hash of the attachment blob, |
|
ce15e35…
|
stephan
|
814 |
** "target": wiki page name or ticket/event ID, |
|
ce15e35…
|
stephan
|
815 |
** "filename": filename of attachment, |
|
ce15e35…
|
stephan
|
816 |
** "mtime": ISO-8601 timestamp UTC, |
|
ce15e35…
|
stephan
|
817 |
** "isLatest": true this is the latest version of this file |
|
ce15e35…
|
stephan
|
818 |
** else false, |
|
ce15e35…
|
stephan
|
819 |
** }, ...once per attachment] |
|
ce15e35…
|
stephan
|
820 |
** |
|
ce15e35…
|
stephan
|
821 |
** If there are no matching attachments then it will emit a JSON |
|
ce15e35…
|
stephan
|
822 |
** null (if nullIfEmpty) or an empty JSON array. |
|
ce15e35…
|
stephan
|
823 |
** |
|
ce15e35…
|
stephan
|
824 |
** If latestOnly is true then only the most recent entry for a given |
|
ce15e35…
|
stephan
|
825 |
** attachment is emitted, else all versions are emitted in descending |
|
ce15e35…
|
stephan
|
826 |
** mtime order. |
|
ce15e35…
|
stephan
|
827 |
*/ |
|
ce15e35…
|
stephan
|
828 |
static void wiki_ajax_emit_page_attachments(Manifest * pWiki, |
|
ce15e35…
|
stephan
|
829 |
int latestOnly, |
|
ce15e35…
|
stephan
|
830 |
int nullIfEmpty){ |
|
ce15e35…
|
stephan
|
831 |
int i = 0; |
|
ce15e35…
|
stephan
|
832 |
Stmt q = empty_Stmt; |
|
ce15e35…
|
stephan
|
833 |
db_prepare(&q, |
|
ce15e35…
|
stephan
|
834 |
"SELECT datetime(mtime), src, target, filename, isLatest," |
|
ce15e35…
|
stephan
|
835 |
" (SELECT uuid FROM blob WHERE rid=attachid) uuid" |
|
ce15e35…
|
stephan
|
836 |
" FROM attachment" |
|
ce15e35…
|
stephan
|
837 |
" WHERE target=%Q" |
|
ce15e35…
|
stephan
|
838 |
" AND (isLatest OR %d)" |
|
ce15e35…
|
stephan
|
839 |
" ORDER BY target, isLatest DESC, mtime DESC", |
|
ce15e35…
|
stephan
|
840 |
pWiki->zWikiTitle, !latestOnly |
|
ce15e35…
|
stephan
|
841 |
); |
|
ce15e35…
|
stephan
|
842 |
while(SQLITE_ROW == db_step(&q)){ |
|
ce15e35…
|
stephan
|
843 |
const char * zTime = db_column_text(&q, 0); |
|
ce15e35…
|
stephan
|
844 |
const char * zSrc = db_column_text(&q, 1); |
|
ce15e35…
|
stephan
|
845 |
const char * zTarget = db_column_text(&q, 2); |
|
ce15e35…
|
stephan
|
846 |
const char * zName = db_column_text(&q, 3); |
|
ce15e35…
|
stephan
|
847 |
const int isLatest = db_column_int(&q, 4); |
|
ce15e35…
|
stephan
|
848 |
const char * zUuid = db_column_text(&q, 5); |
|
ce15e35…
|
stephan
|
849 |
if(!i++){ |
|
ce15e35…
|
stephan
|
850 |
CX("["); |
|
ce15e35…
|
stephan
|
851 |
}else{ |
|
ce15e35…
|
stephan
|
852 |
CX(","); |
|
ce15e35…
|
stephan
|
853 |
} |
|
ce15e35…
|
stephan
|
854 |
CX("{"); |
|
ce15e35…
|
stephan
|
855 |
CX("\"uuid\": %!j, \"src\": %!j, \"target\": %!j, " |
|
ce15e35…
|
stephan
|
856 |
"\"filename\": %!j, \"mtime\": %!j, \"isLatest\": %s}", |
|
ce15e35…
|
stephan
|
857 |
zUuid, zSrc, zTarget, |
|
ce15e35…
|
stephan
|
858 |
zName, zTime, isLatest ? "true" : "false"); |
|
ce15e35…
|
stephan
|
859 |
} |
|
ce15e35…
|
stephan
|
860 |
db_finalize(&q); |
|
ce15e35…
|
stephan
|
861 |
if(!i){ |
|
ce15e35…
|
stephan
|
862 |
if(nullIfEmpty){ |
|
ce15e35…
|
stephan
|
863 |
CX("null"); |
|
ce15e35…
|
stephan
|
864 |
}else{ |
|
ce15e35…
|
stephan
|
865 |
CX("[]"); |
|
ce15e35…
|
stephan
|
866 |
} |
|
ce15e35…
|
stephan
|
867 |
}else{ |
|
ce15e35…
|
stephan
|
868 |
CX("]"); |
|
ce15e35…
|
stephan
|
869 |
} |
|
ce15e35…
|
stephan
|
870 |
} |
|
ce15e35…
|
stephan
|
871 |
|
|
ce15e35…
|
stephan
|
872 |
/* |
|
ce15e35…
|
stephan
|
873 |
** Proxy for wiki_ajax_emit_page_attachments() which attempts to load |
|
ce15e35…
|
stephan
|
874 |
** the given wiki page artifact. Returns true if it can load the given |
|
ce15e35…
|
stephan
|
875 |
** page, else false. If it returns false then it queues up a 404 ajax |
|
ce15e35…
|
stephan
|
876 |
** error response. |
|
ce15e35…
|
stephan
|
877 |
*/ |
|
ce15e35…
|
stephan
|
878 |
static int wiki_ajax_emit_page_attachments2(const char *zPageName, |
|
ce15e35…
|
stephan
|
879 |
int latestOnly, |
|
ce15e35…
|
stephan
|
880 |
int nullIfEmpty){ |
|
ce15e35…
|
stephan
|
881 |
Manifest * pWiki = 0; |
|
ce15e35…
|
stephan
|
882 |
if( !wiki_fetch_by_name(zPageName, 0, 0, &pWiki) ){ |
|
ce15e35…
|
stephan
|
883 |
ajax_route_error(404, "Wiki page could not be loaded: %s", |
|
ce15e35…
|
stephan
|
884 |
zPageName); |
|
ce15e35…
|
stephan
|
885 |
return 0; |
|
ce15e35…
|
stephan
|
886 |
} |
|
ce15e35…
|
stephan
|
887 |
wiki_ajax_emit_page_attachments(pWiki, latestOnly, nullIfEmpty); |
|
ce15e35…
|
stephan
|
888 |
manifest_destroy(pWiki); |
|
ce15e35…
|
stephan
|
889 |
return 1; |
|
19f2753…
|
stephan
|
890 |
} |
|
ce15e35…
|
stephan
|
891 |
|
|
19f2753…
|
stephan
|
892 |
|
|
19f2753…
|
stephan
|
893 |
/* |
|
19f2753…
|
stephan
|
894 |
** Loads the given wiki page, sets the response type to |
|
19f2753…
|
stephan
|
895 |
** application/json, and emits it as a JSON object. If zPageName is a |
|
19f2753…
|
stephan
|
896 |
** sandbox page then a "fake" object is emitted, as the wikiajax API |
|
19f2753…
|
stephan
|
897 |
** does not permit saving the sandbox. |
|
19f2753…
|
stephan
|
898 |
** |
|
a7d8c58…
|
stephan
|
899 |
** Returns true on success, false on error, and on error it |
|
a7d8c58…
|
stephan
|
900 |
** queues up a JSON-format error response. |
|
19f2753…
|
stephan
|
901 |
** |
|
19f2753…
|
stephan
|
902 |
** Output JSON format: |
|
19f2753…
|
stephan
|
903 |
** |
|
19f2753…
|
stephan
|
904 |
** { name: "page name", |
|
19f2753…
|
stephan
|
905 |
** type: "normal" | "tag" | "checkin" | "branch" | "sandbox", |
|
4cb50c4…
|
stephan
|
906 |
** mimetype: "mimetype", |
|
19f2753…
|
stephan
|
907 |
** version: UUID string or null for a sandbox page, |
|
19f2753…
|
stephan
|
908 |
** parent: "parent uuid" or null if no parent, |
|
424baf1…
|
stephan
|
909 |
** isDeleted: true if the page has no content (is "deleted") |
|
424baf1…
|
stephan
|
910 |
** else not set (making it "falsy" in JS), |
|
ce15e35…
|
stephan
|
911 |
** attachments: see wiki_ajax_emit_page_attachments(), |
|
424baf1…
|
stephan
|
912 |
** content: "page content" (only if includeContent is true) |
|
19f2753…
|
stephan
|
913 |
** } |
|
19f2753…
|
stephan
|
914 |
** |
|
19f2753…
|
stephan
|
915 |
** If includeContent is false then the content member is elided. |
|
19f2753…
|
stephan
|
916 |
*/ |
|
19f2753…
|
stephan
|
917 |
static int wiki_ajax_emit_page_object(const char *zPageName, |
|
a7d8c58…
|
stephan
|
918 |
int includeContent){ |
|
19f2753…
|
stephan
|
919 |
Manifest * pWiki = 0; |
|
19f2753…
|
stephan
|
920 |
char * zUuid; |
|
19f2753…
|
stephan
|
921 |
|
|
19f2753…
|
stephan
|
922 |
if( is_sandbox(zPageName) ){ |
|
19f2753…
|
stephan
|
923 |
char * zMimetype = |
|
19f2753…
|
stephan
|
924 |
db_get("sandbox-mimetype","text/x-fossil-wiki"); |
|
19f2753…
|
stephan
|
925 |
char * zBody = db_get("sandbox",""); |
|
19f2753…
|
stephan
|
926 |
CX("{\"name\": %!j, \"type\": \"sandbox\", " |
|
19f2753…
|
stephan
|
927 |
"\"mimetype\": %!j, \"version\": null, \"parent\": null", |
|
19f2753…
|
stephan
|
928 |
zPageName, zMimetype); |
|
19f2753…
|
stephan
|
929 |
if(includeContent){ |
|
19f2753…
|
stephan
|
930 |
CX(", \"content\": %!j", |
|
19f2753…
|
stephan
|
931 |
zBody); |
|
19f2753…
|
stephan
|
932 |
} |
|
19f2753…
|
stephan
|
933 |
CX("}"); |
|
19f2753…
|
stephan
|
934 |
fossil_free(zMimetype); |
|
19f2753…
|
stephan
|
935 |
fossil_free(zBody); |
|
19f2753…
|
stephan
|
936 |
return 1; |
|
19f2753…
|
stephan
|
937 |
}else if( !wiki_fetch_by_name(zPageName, 0, 0, &pWiki) ){ |
|
a7d8c58…
|
stephan
|
938 |
ajax_route_error(404, "Wiki page could not be loaded: %s", |
|
a7d8c58…
|
stephan
|
939 |
zPageName); |
|
19f2753…
|
stephan
|
940 |
return 0; |
|
19f2753…
|
stephan
|
941 |
}else{ |
|
19f2753…
|
stephan
|
942 |
zUuid = rid_to_uuid(pWiki->rid); |
|
19f2753…
|
stephan
|
943 |
CX("{\"name\": %!j, \"type\": %!j, " |
|
19f2753…
|
stephan
|
944 |
"\"version\": %!j, " |
|
19f2753…
|
stephan
|
945 |
"\"mimetype\": %!j, ", |
|
19f2753…
|
stephan
|
946 |
pWiki->zWikiTitle, |
|
19f2753…
|
stephan
|
947 |
wiki_page_type_name(pWiki->zWikiTitle), |
|
19f2753…
|
stephan
|
948 |
zUuid, |
|
19f2753…
|
stephan
|
949 |
pWiki->zMimetype ? pWiki->zMimetype : "text/x-fossil-wiki"); |
|
19f2753…
|
stephan
|
950 |
CX("\"parent\": "); |
|
19f2753…
|
stephan
|
951 |
if(pWiki->nParent){ |
|
19f2753…
|
stephan
|
952 |
CX("%!j", pWiki->azParent[0]); |
|
19f2753…
|
stephan
|
953 |
}else{ |
|
19f2753…
|
stephan
|
954 |
CX("null"); |
|
19f2753…
|
stephan
|
955 |
} |
|
424baf1…
|
stephan
|
956 |
if(!pWiki->zWiki || !pWiki->zWiki[0]){ |
|
424baf1…
|
stephan
|
957 |
CX(", \"isEmpty\": true"); |
|
424baf1…
|
stephan
|
958 |
} |
|
19f2753…
|
stephan
|
959 |
if(includeContent){ |
|
19f2753…
|
stephan
|
960 |
CX(", \"content\": %!j", pWiki->zWiki); |
|
19f2753…
|
stephan
|
961 |
} |
|
ce15e35…
|
stephan
|
962 |
CX(", \"attachments\": "); |
|
ce15e35…
|
stephan
|
963 |
wiki_ajax_emit_page_attachments(pWiki, 0, 1); |
|
19f2753…
|
stephan
|
964 |
CX("}"); |
|
19f2753…
|
stephan
|
965 |
fossil_free(zUuid); |
|
19f2753…
|
stephan
|
966 |
manifest_destroy(pWiki); |
|
19f2753…
|
stephan
|
967 |
return 2; |
|
19f2753…
|
stephan
|
968 |
} |
|
19f2753…
|
stephan
|
969 |
} |
|
19f2753…
|
stephan
|
970 |
|
|
19f2753…
|
stephan
|
971 |
/* |
|
19f2753…
|
stephan
|
972 |
** Ajax route handler for /wikiajax/save. |
|
19f2753…
|
stephan
|
973 |
** |
|
19f2753…
|
stephan
|
974 |
** URL params: |
|
19f2753…
|
stephan
|
975 |
** |
|
19f2753…
|
stephan
|
976 |
** page = the wiki page name. |
|
4cb50c4…
|
stephan
|
977 |
** mimetype = content mimetype. |
|
19f2753…
|
stephan
|
978 |
** content = page content. Fossil considers an empty page to |
|
19f2753…
|
stephan
|
979 |
** be "deleted". |
|
19f2753…
|
stephan
|
980 |
** isnew = 1 if the page is to be newly-created, else 0 or |
|
19f2753…
|
stephan
|
981 |
** not send. |
|
19f2753…
|
stephan
|
982 |
** |
|
19f2753…
|
stephan
|
983 |
** Responds with JSON. On error, an object in the form documented by |
|
19f2753…
|
stephan
|
984 |
** ajax_route_error(). On success, an object in the form documented |
|
19f2753…
|
stephan
|
985 |
** for wiki_ajax_emit_page_object(). |
|
19f2753…
|
stephan
|
986 |
** |
|
19f2753…
|
stephan
|
987 |
** The wikiajax API disallows saving of a sandbox pseudo-page, and |
|
3dc4613…
|
stephan
|
988 |
** will respond with an error if asked to save one. Should we want to |
|
3dc4613…
|
stephan
|
989 |
** enable it, it's implemented like this for any saved page for which |
|
3dc4613…
|
stephan
|
990 |
** is_sandbox(zPageName) is true: |
|
19f2753…
|
stephan
|
991 |
** |
|
19f2753…
|
stephan
|
992 |
** db_set("sandbox",zBody,0); |
|
19f2753…
|
stephan
|
993 |
** db_set("sandbox-mimetype",zMimetype,0); |
|
19f2753…
|
stephan
|
994 |
** |
|
19f2753…
|
stephan
|
995 |
*/ |
|
19f2753…
|
stephan
|
996 |
static void wiki_ajax_route_save(void){ |
|
19f2753…
|
stephan
|
997 |
const char *zPageName = P("page"); |
|
19f2753…
|
stephan
|
998 |
const char *zMimetype = P("mimetype"); |
|
19f2753…
|
stephan
|
999 |
const char *zContent = P("content"); |
|
3dc4613…
|
stephan
|
1000 |
const int isNew = ajax_p_bool("isnew"); |
|
19f2753…
|
stephan
|
1001 |
Blob content = empty_blob; |
|
19f2753…
|
stephan
|
1002 |
int parentRid = 0; |
|
19f2753…
|
stephan
|
1003 |
int rollback = 0; |
|
19f2753…
|
stephan
|
1004 |
|
|
19f2753…
|
stephan
|
1005 |
if(!wiki_ajax_can_write(zPageName, &parentRid)){ |
|
19f2753…
|
stephan
|
1006 |
return; |
|
19f2753…
|
stephan
|
1007 |
}else if(is_sandbox(zPageName)){ |
|
19f2753…
|
stephan
|
1008 |
ajax_route_error(403,"Saving a sandbox page is prohibited."); |
|
19f2753…
|
stephan
|
1009 |
return; |
|
19f2753…
|
stephan
|
1010 |
} |
|
3dc4613…
|
stephan
|
1011 |
/* These isNew checks are just me being pedantic. We could just as |
|
3dc4613…
|
stephan
|
1012 |
easily derive isNew based on whether or not the page already |
|
3dc4613…
|
stephan
|
1013 |
exists. */ |
|
19f2753…
|
stephan
|
1014 |
if(isNew){ |
|
19f2753…
|
stephan
|
1015 |
if(parentRid>0){ |
|
19f2753…
|
stephan
|
1016 |
ajax_route_error(403,"Requested a new page, " |
|
19f2753…
|
stephan
|
1017 |
"but it already exists with RID %d: %s", |
|
19f2753…
|
stephan
|
1018 |
parentRid, zPageName); |
|
19f2753…
|
stephan
|
1019 |
return; |
|
19f2753…
|
stephan
|
1020 |
} |
|
19f2753…
|
stephan
|
1021 |
}else if(parentRid==0){ |
|
19f2753…
|
stephan
|
1022 |
ajax_route_error(403,"Creating new page [%s] requires passing " |
|
19f2753…
|
stephan
|
1023 |
"isnew=1.", zPageName); |
|
19f2753…
|
stephan
|
1024 |
return; |
|
19f2753…
|
stephan
|
1025 |
} |
|
19f2753…
|
stephan
|
1026 |
blob_init(&content, zContent ? zContent : "", -1); |
|
bc66513…
|
stephan
|
1027 |
cgi_set_content_type("application/json"); |
|
19f2753…
|
stephan
|
1028 |
db_begin_transaction(); |
|
19f2753…
|
stephan
|
1029 |
wiki_cmd_commit(zPageName, parentRid, &content, zMimetype, 0); |
|
a7d8c58…
|
stephan
|
1030 |
rollback = wiki_ajax_emit_page_object(zPageName, 1) ? 0 : 1; |
|
19f2753…
|
stephan
|
1031 |
db_end_transaction(rollback); |
|
19f2753…
|
stephan
|
1032 |
} |
|
19f2753…
|
stephan
|
1033 |
|
|
19f2753…
|
stephan
|
1034 |
/* |
|
19f2753…
|
stephan
|
1035 |
** Ajax route handler for /wikiajax/fetch. |
|
19f2753…
|
stephan
|
1036 |
** |
|
19f2753…
|
stephan
|
1037 |
** URL params: |
|
19f2753…
|
stephan
|
1038 |
** |
|
19f2753…
|
stephan
|
1039 |
** page = the wiki page name |
|
19f2753…
|
stephan
|
1040 |
** |
|
19f2753…
|
stephan
|
1041 |
** Responds with JSON. On error, an object in the form documented by |
|
19f2753…
|
stephan
|
1042 |
** ajax_route_error(). On success, an object in the form documented |
|
19f2753…
|
stephan
|
1043 |
** for wiki_ajax_emit_page_object(). |
|
19f2753…
|
stephan
|
1044 |
*/ |
|
19f2753…
|
stephan
|
1045 |
static void wiki_ajax_route_fetch(void){ |
|
19f2753…
|
stephan
|
1046 |
const char * zPageName = P("page"); |
|
275da70…
|
danield
|
1047 |
|
|
a7d8c58…
|
stephan
|
1048 |
if( zPageName==0 || zPageName[0]==0 ){ |
|
a7d8c58…
|
stephan
|
1049 |
ajax_route_error(400,"Missing page name."); |
|
a7d8c58…
|
stephan
|
1050 |
return; |
|
a7d8c58…
|
stephan
|
1051 |
} |
|
a7d8c58…
|
stephan
|
1052 |
cgi_set_content_type("application/json"); |
|
a7d8c58…
|
stephan
|
1053 |
wiki_ajax_emit_page_object(zPageName, 1); |
|
ce15e35…
|
stephan
|
1054 |
} |
|
ce15e35…
|
stephan
|
1055 |
|
|
ce15e35…
|
stephan
|
1056 |
/* |
|
ce15e35…
|
stephan
|
1057 |
** Ajax route handler for /wikiajax/attachments. |
|
ce15e35…
|
stephan
|
1058 |
** |
|
ce15e35…
|
stephan
|
1059 |
** URL params: |
|
ce15e35…
|
stephan
|
1060 |
** |
|
ce15e35…
|
stephan
|
1061 |
** page = the wiki page name |
|
ce15e35…
|
stephan
|
1062 |
** latestOnly = if set, only latest version of each attachment |
|
ce15e35…
|
stephan
|
1063 |
** is emitted. |
|
ce15e35…
|
stephan
|
1064 |
** |
|
ce15e35…
|
stephan
|
1065 |
** Responds with JSON: see wiki_ajax_emit_page_attachments() |
|
ce15e35…
|
stephan
|
1066 |
** |
|
ce15e35…
|
stephan
|
1067 |
** If there are no attachments it emits an empty array instead of null |
|
ce15e35…
|
stephan
|
1068 |
** so that the output can be used as a top-level JSON response. |
|
ce15e35…
|
stephan
|
1069 |
** |
|
ce15e35…
|
stephan
|
1070 |
** On error, an object in the form documented by |
|
ce15e35…
|
stephan
|
1071 |
** ajax_route_error(). On success, an object in the form documented |
|
ce15e35…
|
stephan
|
1072 |
** for wiki_ajax_emit_page_attachments(). |
|
ce15e35…
|
stephan
|
1073 |
*/ |
|
ce15e35…
|
stephan
|
1074 |
static void wiki_ajax_route_attachments(void){ |
|
ce15e35…
|
stephan
|
1075 |
const char * zPageName = P("page"); |
|
ce15e35…
|
stephan
|
1076 |
const int fLatestOnly = P("latestOnly")!=0; |
|
ce15e35…
|
stephan
|
1077 |
if( zPageName==0 || zPageName[0]==0 ){ |
|
ce15e35…
|
stephan
|
1078 |
ajax_route_error(400,"Missing page name."); |
|
ce15e35…
|
stephan
|
1079 |
return; |
|
ce15e35…
|
stephan
|
1080 |
} |
|
ce15e35…
|
stephan
|
1081 |
cgi_set_content_type("application/json"); |
|
ce15e35…
|
stephan
|
1082 |
wiki_ajax_emit_page_attachments2(zPageName, fLatestOnly, 0); |
|
19f2753…
|
stephan
|
1083 |
} |
|
19f2753…
|
stephan
|
1084 |
|
|
19f2753…
|
stephan
|
1085 |
/* |
|
19f2753…
|
stephan
|
1086 |
** Ajax route handler for /wikiajax/diff. |
|
19f2753…
|
stephan
|
1087 |
** |
|
19f2753…
|
stephan
|
1088 |
** URL params: |
|
19f2753…
|
stephan
|
1089 |
** |
|
19f2753…
|
stephan
|
1090 |
** page = the wiki page name |
|
19f2753…
|
stephan
|
1091 |
** content = the new/edited wiki page content |
|
19f2753…
|
stephan
|
1092 |
** |
|
19f2753…
|
stephan
|
1093 |
** Requires that the user have write access solely to avoid some |
|
19f2753…
|
stephan
|
1094 |
** potential abuse cases. It does not actually write anything. |
|
19f2753…
|
stephan
|
1095 |
*/ |
|
19f2753…
|
stephan
|
1096 |
static void wiki_ajax_route_diff(void){ |
|
19f2753…
|
stephan
|
1097 |
const char * zPageName = P("page"); |
|
19f2753…
|
stephan
|
1098 |
Blob contentNew = empty_blob, contentOrig = empty_blob; |
|
19f2753…
|
stephan
|
1099 |
Manifest * pParent = 0; |
|
19f2753…
|
stephan
|
1100 |
const char * zContent = P("content"); |
|
19f2753…
|
stephan
|
1101 |
u64 diffFlags = DIFF_HTML | DIFF_NOTTOOBIG | DIFF_STRIP_EOLCR; |
|
ef69044…
|
stephan
|
1102 |
char * zParentUuid = 0; |
|
19f2753…
|
stephan
|
1103 |
|
|
19f2753…
|
stephan
|
1104 |
if( zPageName==0 || zPageName[0]==0 ){ |
|
19f2753…
|
stephan
|
1105 |
ajax_route_error(400,"Missing page name."); |
|
19f2753…
|
stephan
|
1106 |
return; |
|
19f2753…
|
stephan
|
1107 |
}else if(!wiki_ajax_can_write(zPageName, 0)){ |
|
19f2753…
|
stephan
|
1108 |
return; |
|
19f2753…
|
stephan
|
1109 |
} |
|
19f2753…
|
stephan
|
1110 |
switch(atoi(PD("sbs","0"))){ |
|
19f2753…
|
stephan
|
1111 |
case 0: diffFlags |= DIFF_LINENO; break; |
|
19f2753…
|
stephan
|
1112 |
default: diffFlags |= DIFF_SIDEBYSIDE; |
|
19f2753…
|
stephan
|
1113 |
} |
|
19f2753…
|
stephan
|
1114 |
switch(atoi(PD("ws","2"))){ |
|
19f2753…
|
stephan
|
1115 |
case 1: diffFlags |= DIFF_IGNORE_EOLWS; break; |
|
19f2753…
|
stephan
|
1116 |
case 2: diffFlags |= DIFF_IGNORE_ALLWS; break; |
|
19f2753…
|
stephan
|
1117 |
default: break; |
|
19f2753…
|
stephan
|
1118 |
} |
|
19f2753…
|
stephan
|
1119 |
wiki_fetch_by_name( zPageName, 0, 0, &pParent ); |
|
ef69044…
|
stephan
|
1120 |
if( pParent ){ |
|
ef69044…
|
stephan
|
1121 |
zParentUuid = rid_to_uuid(pParent->rid); |
|
ef69044…
|
stephan
|
1122 |
} |
|
19f2753…
|
stephan
|
1123 |
if( pParent && pParent->zWiki && *pParent->zWiki ){ |
|
19f2753…
|
stephan
|
1124 |
blob_init(&contentOrig, pParent->zWiki, -1); |
|
19f2753…
|
stephan
|
1125 |
}else{ |
|
19f2753…
|
stephan
|
1126 |
blob_init(&contentOrig, "", 0); |
|
19f2753…
|
stephan
|
1127 |
} |
|
19f2753…
|
stephan
|
1128 |
blob_init(&contentNew, zContent ? zContent : "", -1); |
|
19f2753…
|
stephan
|
1129 |
cgi_set_content_type("text/html"); |
|
ef69044…
|
stephan
|
1130 |
ajax_render_diff(&contentOrig, zParentUuid, &contentNew, diffFlags); |
|
19f2753…
|
stephan
|
1131 |
blob_reset(&contentNew); |
|
19f2753…
|
stephan
|
1132 |
blob_reset(&contentOrig); |
|
ef69044…
|
stephan
|
1133 |
fossil_free(zParentUuid); |
|
19f2753…
|
stephan
|
1134 |
manifest_destroy(pParent); |
|
19f2753…
|
stephan
|
1135 |
} |
|
19f2753…
|
stephan
|
1136 |
|
|
19f2753…
|
stephan
|
1137 |
/* |
|
19f2753…
|
stephan
|
1138 |
** Ajax route handler for /wikiajax/preview. |
|
19f2753…
|
stephan
|
1139 |
** |
|
19f2753…
|
stephan
|
1140 |
** URL params: |
|
19f2753…
|
stephan
|
1141 |
** |
|
19f2753…
|
stephan
|
1142 |
** mimetype = the wiki page mimetype (determines rendering style) |
|
19f2753…
|
stephan
|
1143 |
** content = the wiki page content |
|
19f2753…
|
stephan
|
1144 |
*/ |
|
19f2753…
|
stephan
|
1145 |
static void wiki_ajax_route_preview(void){ |
|
19f2753…
|
stephan
|
1146 |
const char * zContent = P("content"); |
|
19f2753…
|
stephan
|
1147 |
|
|
18dee26…
|
stephan
|
1148 |
if( zContent==0 ){ |
|
19f2753…
|
stephan
|
1149 |
ajax_route_error(400,"Missing content to preview."); |
|
19f2753…
|
stephan
|
1150 |
return; |
|
19f2753…
|
stephan
|
1151 |
}else{ |
|
19f2753…
|
stephan
|
1152 |
Blob content = empty_blob; |
|
19f2753…
|
stephan
|
1153 |
const char * zMimetype = PD("mimetype","text/x-fossil-wiki"); |
|
19f2753…
|
stephan
|
1154 |
|
|
19f2753…
|
stephan
|
1155 |
blob_init(&content, zContent, -1); |
|
19f2753…
|
stephan
|
1156 |
cgi_set_content_type("text/html"); |
|
19f2753…
|
stephan
|
1157 |
wiki_render_by_mimetype(&content, zMimetype); |
|
19f2753…
|
stephan
|
1158 |
blob_reset(&content); |
|
19f2753…
|
stephan
|
1159 |
} |
|
19f2753…
|
stephan
|
1160 |
} |
|
19f2753…
|
stephan
|
1161 |
|
|
19f2753…
|
stephan
|
1162 |
/* |
|
bc66513…
|
stephan
|
1163 |
** Outputs the wiki page list in JSON form. If verbose is false then |
|
bc66513…
|
stephan
|
1164 |
** it emits an array of strings (page names). If verbose is true it outputs |
|
bc66513…
|
stephan
|
1165 |
** an array of objects in this form: |
|
bc66513…
|
stephan
|
1166 |
** |
|
bc66513…
|
stephan
|
1167 |
** { name: string, version: string or null of sandbox box, |
|
bc66513…
|
stephan
|
1168 |
** parent: uuid or null for first version or sandbox, |
|
bc66513…
|
stephan
|
1169 |
** mimetype: string, |
|
bc36fdc…
|
danield
|
1170 |
** type: string (normal, branch, tag, check-in, or sandbox) |
|
bc66513…
|
stephan
|
1171 |
** } |
|
bc66513…
|
stephan
|
1172 |
** |
|
bc66513…
|
stephan
|
1173 |
** If includeContent is true, the object contains a "content" member |
|
bc66513…
|
stephan
|
1174 |
** with the raw page content. includeContent is ignored if verbose is |
|
bc66513…
|
stephan
|
1175 |
** false. |
|
bc66513…
|
stephan
|
1176 |
** |
|
bc66513…
|
stephan
|
1177 |
*/ |
|
bc66513…
|
stephan
|
1178 |
static void wiki_render_page_list_json(int verbose, int includeContent){ |
|
19f2753…
|
stephan
|
1179 |
Stmt q = empty_Stmt; |
|
19f2753…
|
stephan
|
1180 |
int n = 0; |
|
19f2753…
|
stephan
|
1181 |
db_begin_transaction(); |
|
19f2753…
|
stephan
|
1182 |
db_prepare(&q, "SELECT" |
|
19f2753…
|
stephan
|
1183 |
" substr(tagname,6) AS name" |
|
5244a54…
|
drh
|
1184 |
" FROM tag JOIN tagxref USING('tagid')" |
|
5244a54…
|
drh
|
1185 |
" WHERE tagname GLOB 'wiki-*'" |
|
4218b20…
|
stephan
|
1186 |
" AND TYPEOF(tagxref.value+0)='integer'" |
|
4218b20…
|
stephan
|
1187 |
/* ^^^ elide wiki- tags which are not wiki pages */ |
|
19f2753…
|
stephan
|
1188 |
" UNION SELECT 'Sandbox' AS name" |
|
19f2753…
|
stephan
|
1189 |
" ORDER BY name COLLATE NOCASE"); |
|
19f2753…
|
stephan
|
1190 |
CX("["); |
|
19f2753…
|
stephan
|
1191 |
while( SQLITE_ROW==db_step(&q) ){ |
|
19f2753…
|
stephan
|
1192 |
char const * zName = db_column_text(&q,0); |
|
a7d8c58…
|
stephan
|
1193 |
if(n++){ |
|
a7d8c58…
|
stephan
|
1194 |
CX(","); |
|
a7d8c58…
|
stephan
|
1195 |
} |
|
19f2753…
|
stephan
|
1196 |
if(verbose==0){ |
|
19f2753…
|
stephan
|
1197 |
CX("%!j", zName); |
|
19f2753…
|
stephan
|
1198 |
}else{ |
|
a7d8c58…
|
stephan
|
1199 |
wiki_ajax_emit_page_object(zName, includeContent); |
|
19f2753…
|
stephan
|
1200 |
} |
|
19f2753…
|
stephan
|
1201 |
} |
|
bc66513…
|
stephan
|
1202 |
CX("]"); |
|
19f2753…
|
stephan
|
1203 |
db_finalize(&q); |
|
19f2753…
|
stephan
|
1204 |
db_end_transaction(0); |
|
bc66513…
|
stephan
|
1205 |
} |
|
bc66513…
|
stephan
|
1206 |
|
|
bc66513…
|
stephan
|
1207 |
/* |
|
bc66513…
|
stephan
|
1208 |
** Ajax route handler for /wikiajax/list. |
|
bc66513…
|
stephan
|
1209 |
** |
|
bc66513…
|
stephan
|
1210 |
** Optional parameters: verbose, includeContent (see below). |
|
bc66513…
|
stephan
|
1211 |
** |
|
bc66513…
|
stephan
|
1212 |
** Responds with JSON. On error, an object in the form documented by |
|
bc66513…
|
stephan
|
1213 |
** ajax_route_error(). |
|
bc66513…
|
stephan
|
1214 |
** |
|
bc66513…
|
stephan
|
1215 |
** On success, it emits an array of strings (page names) sorted |
|
bc66513…
|
stephan
|
1216 |
** case-insensitively. If the "verbose" parameter is passed in then |
|
bc66513…
|
stephan
|
1217 |
** the result list contains objects in the format documented for |
|
bc66513…
|
stephan
|
1218 |
** wiki_ajax_emit_page_object(). The content of each object is elided |
|
bc66513…
|
stephan
|
1219 |
** unless the "includeContent" parameter is passed on with a |
|
bc66513…
|
stephan
|
1220 |
** "non-false" value.. |
|
bc66513…
|
stephan
|
1221 |
** |
|
bc66513…
|
stephan
|
1222 |
** The result list always contains an entry named "Sandbox" which |
|
bc66513…
|
stephan
|
1223 |
** represents the sandbox pseudo-page. |
|
bc66513…
|
stephan
|
1224 |
*/ |
|
bc66513…
|
stephan
|
1225 |
static void wiki_ajax_route_list(void){ |
|
bc66513…
|
stephan
|
1226 |
const int verbose = ajax_p_bool("verbose"); |
|
bc66513…
|
stephan
|
1227 |
const int includeContent = ajax_p_bool("includeContent"); |
|
bc66513…
|
stephan
|
1228 |
|
|
bc66513…
|
stephan
|
1229 |
cgi_set_content_type("application/json"); |
|
bc66513…
|
stephan
|
1230 |
wiki_render_page_list_json(verbose, includeContent); |
|
19f2753…
|
stephan
|
1231 |
} |
|
19f2753…
|
stephan
|
1232 |
|
|
19f2753…
|
stephan
|
1233 |
/* |
|
701c6dc…
|
stephan
|
1234 |
** WEBPAGE: wikiajax hidden |
|
19f2753…
|
stephan
|
1235 |
** |
|
19f2753…
|
stephan
|
1236 |
** An internal dispatcher for wiki AJAX operations. Not for direct |
|
19f2753…
|
stephan
|
1237 |
** client use. All routes defined by this interface are app-internal, |
|
275da70…
|
danield
|
1238 |
** subject to change |
|
19f2753…
|
stephan
|
1239 |
*/ |
|
19f2753…
|
stephan
|
1240 |
void wiki_ajax_page(void){ |
|
19f2753…
|
stephan
|
1241 |
const char * zName = P("name"); |
|
19f2753…
|
stephan
|
1242 |
AjaxRoute routeName = {0,0,0,0}; |
|
19f2753…
|
stephan
|
1243 |
const AjaxRoute * pRoute = 0; |
|
19f2753…
|
stephan
|
1244 |
const AjaxRoute routes[] = { |
|
19f2753…
|
stephan
|
1245 |
/* Keep these sorted by zName (for bsearch()) */ |
|
ce15e35…
|
stephan
|
1246 |
{"attachments", wiki_ajax_route_attachments, 0, 0}, |
|
19f2753…
|
stephan
|
1247 |
{"diff", wiki_ajax_route_diff, 1, 1}, |
|
19f2753…
|
stephan
|
1248 |
{"fetch", wiki_ajax_route_fetch, 0, 0}, |
|
19f2753…
|
stephan
|
1249 |
{"list", wiki_ajax_route_list, 0, 0}, |
|
18dee26…
|
stephan
|
1250 |
{"preview", wiki_ajax_route_preview, 0, 1}, |
|
19f2753…
|
stephan
|
1251 |
{"save", wiki_ajax_route_save, 1, 1} |
|
19f2753…
|
stephan
|
1252 |
}; |
|
19f2753…
|
stephan
|
1253 |
|
|
19f2753…
|
stephan
|
1254 |
if(zName==0 || zName[0]==0){ |
|
19f2753…
|
stephan
|
1255 |
ajax_route_error(400,"Missing required [route] 'name' parameter."); |
|
19f2753…
|
stephan
|
1256 |
return; |
|
19f2753…
|
stephan
|
1257 |
} |
|
19f2753…
|
stephan
|
1258 |
routeName.zName = zName; |
|
19f2753…
|
stephan
|
1259 |
pRoute = (const AjaxRoute *)bsearch(&routeName, routes, |
|
19f2753…
|
stephan
|
1260 |
count(routes), sizeof routes[0], |
|
19f2753…
|
stephan
|
1261 |
cmp_ajax_route_name); |
|
19f2753…
|
stephan
|
1262 |
if(pRoute==0){ |
|
19f2753…
|
stephan
|
1263 |
ajax_route_error(404,"Ajax route not found."); |
|
19f2753…
|
stephan
|
1264 |
return; |
|
19f2753…
|
stephan
|
1265 |
} |
|
19f2753…
|
stephan
|
1266 |
login_check_credentials(); |
|
19f2753…
|
stephan
|
1267 |
if( pRoute->bWriteMode!=0 && g.perm.WrWiki==0 ){ |
|
19f2753…
|
stephan
|
1268 |
ajax_route_error(403,"Write permissions required."); |
|
19f2753…
|
stephan
|
1269 |
return; |
|
bff3df0…
|
stephan
|
1270 |
}else if( pRoute->bWriteMode==0 && g.perm.RdWiki==0 ){ |
|
bff3df0…
|
stephan
|
1271 |
ajax_route_error(403,"Read-Wiki permissions required."); |
|
bff3df0…
|
stephan
|
1272 |
return; |
|
19f2753…
|
stephan
|
1273 |
}else if(0==cgi_csrf_safe(pRoute->bPost)){ |
|
19f2753…
|
stephan
|
1274 |
ajax_route_error(403, |
|
19f2753…
|
stephan
|
1275 |
"CSRF violation (make sure sending of HTTP " |
|
19f2753…
|
stephan
|
1276 |
"Referer headers is enabled for XHR " |
|
19f2753…
|
stephan
|
1277 |
"connections)."); |
|
19f2753…
|
stephan
|
1278 |
return; |
|
19f2753…
|
stephan
|
1279 |
} |
|
19f2753…
|
stephan
|
1280 |
pRoute->xCallback(); |
|
4bb5515…
|
stephan
|
1281 |
} |
|
4bb5515…
|
stephan
|
1282 |
|
|
4bb5515…
|
stephan
|
1283 |
/* |
|
4bb5515…
|
stephan
|
1284 |
** Emits a preview-toggle option widget for /wikiedit and /fileedit. |
|
4bb5515…
|
stephan
|
1285 |
*/ |
|
4bb5515…
|
stephan
|
1286 |
void wikiedit_emit_toggle_preview(void){ |
|
4bb5515…
|
stephan
|
1287 |
CX("<div class='input-with-label'>" |
|
4bb5515…
|
stephan
|
1288 |
"<input type='checkbox' id='edit-shift-enter-preview' " |
|
4bb5515…
|
stephan
|
1289 |
"></input><label for='edit-shift-enter-preview'>" |
|
4bb5515…
|
stephan
|
1290 |
"Shift-enter previews</label>" |
|
4bb5515…
|
stephan
|
1291 |
"<div class='help-buttonlet'>" |
|
4bb5515…
|
stephan
|
1292 |
"When enabled, shift-enter switches between preview and edit modes. " |
|
4bb5515…
|
stephan
|
1293 |
"Some software-based keyboards misinteract with this, so it can be " |
|
4bb5515…
|
stephan
|
1294 |
"disabled when needed." |
|
4bb5515…
|
stephan
|
1295 |
"</div>" |
|
4bb5515…
|
stephan
|
1296 |
"</div>"); |
|
2f78b2c…
|
danield
|
1297 |
} |
|
2f78b2c…
|
danield
|
1298 |
|
|
2f78b2c…
|
danield
|
1299 |
/* |
|
5602385…
|
drh
|
1300 |
** WEBPAGE: wikiedit |
|
19f2753…
|
stephan
|
1301 |
** URL: /wikedit?name=PAGENAME |
|
19f2753…
|
stephan
|
1302 |
** |
|
19f2753…
|
stephan
|
1303 |
** The main front-end for the Ajax-based wiki editor app. Passing |
|
19f2753…
|
stephan
|
1304 |
** in the name of an unknown page will trigger the creation |
|
19f2753…
|
stephan
|
1305 |
** of a new page (which is not actually created in the database |
|
19f2753…
|
stephan
|
1306 |
** until the user explicitly saves it). If passed no page name, |
|
19f2753…
|
stephan
|
1307 |
** the user may select a page from the list on the first UI tab. |
|
5602385…
|
drh
|
1308 |
** |
|
19f2753…
|
stephan
|
1309 |
** When creating a new page, the mimetype URL parameter may optionally |
|
19f2753…
|
stephan
|
1310 |
** be used to set its mimetype to one of text/x-fossil-wiki, |
|
2f78b2c…
|
danield
|
1311 |
** text/x-markdown, or text/plain, defaulting to the former. |
|
5602385…
|
drh
|
1312 |
*/ |
|
5602385…
|
drh
|
1313 |
void wikiedit_page(void){ |
|
19f2753…
|
stephan
|
1314 |
const char *zPageName; |
|
19f2753…
|
stephan
|
1315 |
const char * zMimetype = P("mimetype"); |
|
5602385…
|
drh
|
1316 |
int isSandbox; |
|
19f2753…
|
stephan
|
1317 |
int found = 0; |
|
19f2753…
|
stephan
|
1318 |
|
|
5602385…
|
drh
|
1319 |
login_check_credentials(); |
|
5602385…
|
drh
|
1320 |
zPageName = PD("name",""); |
|
19f2753…
|
stephan
|
1321 |
if(zPageName && *zPageName){ |
|
19f2753…
|
stephan
|
1322 |
if( check_name(zPageName) ) return; |
|
19f2753…
|
stephan
|
1323 |
} |
|
5602385…
|
drh
|
1324 |
isSandbox = is_sandbox(zPageName); |
|
5602385…
|
drh
|
1325 |
if( isSandbox ){ |
|
bff3df0…
|
stephan
|
1326 |
if( !g.perm.RdWiki ){ |
|
bff3df0…
|
stephan
|
1327 |
login_needed(g.anon.RdWiki); |
|
5602385…
|
drh
|
1328 |
return; |
|
5602385…
|
drh
|
1329 |
} |
|
19f2753…
|
stephan
|
1330 |
found = 1; |
|
bff3df0…
|
stephan
|
1331 |
}else if( zPageName!=0 && zPageName[0]!=0){ |
|
19f2753…
|
stephan
|
1332 |
int rid = 0; |
|
5602385…
|
drh
|
1333 |
if( !wiki_special_permission(zPageName) ){ |
|
5602385…
|
drh
|
1334 |
login_needed(0); |
|
5602385…
|
drh
|
1335 |
return; |
|
5602385…
|
drh
|
1336 |
} |
|
19f2753…
|
stephan
|
1337 |
found = wiki_fetch_by_name(zPageName, 0, &rid, 0); |
|
bff3df0…
|
stephan
|
1338 |
if( (rid && !g.perm.RdWiki) || (!rid && !g.perm.NewWiki) ){ |
|
bff3df0…
|
stephan
|
1339 |
login_needed(rid ? g.anon.RdWiki : g.anon.NewWiki); |
|
bff3df0…
|
stephan
|
1340 |
return; |
|
bff3df0…
|
stephan
|
1341 |
} |
|
bff3df0…
|
stephan
|
1342 |
}else{ |
|
bff3df0…
|
stephan
|
1343 |
if( !g.perm.RdWiki ){ |
|
bff3df0…
|
stephan
|
1344 |
login_needed(g.anon.RdWiki); |
|
19f2753…
|
stephan
|
1345 |
return; |
|
19f2753…
|
stephan
|
1346 |
} |
|
19f2753…
|
stephan
|
1347 |
} |
|
112c713…
|
drh
|
1348 |
style_set_current_feature("wiki"); |
|
19f2753…
|
stephan
|
1349 |
style_header("Wiki Editor"); |
|
070716d…
|
stephan
|
1350 |
style_emit_noscript_for_js_page(); |
|
19f2753…
|
stephan
|
1351 |
|
|
19f2753…
|
stephan
|
1352 |
/* Status bar */ |
|
19f2753…
|
stephan
|
1353 |
CX("<div id='fossil-status-bar' " |
|
19f2753…
|
stephan
|
1354 |
"title='Status message area. Double-click to clear them.'>" |
|
19f2753…
|
stephan
|
1355 |
"Status messages will go here.</div>\n" |
|
19f2753…
|
stephan
|
1356 |
/* will be moved into the tab container via JS */); |
|
19f2753…
|
stephan
|
1357 |
|
|
f96a3ba…
|
stephan
|
1358 |
CX("<div id='wikiedit-edit-status'>" |
|
fb77abd…
|
stephan
|
1359 |
"<span class='name'></span>" |
|
fb77abd…
|
stephan
|
1360 |
"<span class='links'></span>" |
|
fb77abd…
|
stephan
|
1361 |
"</div>"); |
|
4bb5515…
|
stephan
|
1362 |
|
|
19f2753…
|
stephan
|
1363 |
/* Main tab container... */ |
|
19f2753…
|
stephan
|
1364 |
CX("<div id='wikiedit-tabs' class='tab-container'>Loading...</div>"); |
|
19f2753…
|
stephan
|
1365 |
/* The .hidden class on the following tab elements is to help lessen |
|
19f2753…
|
stephan
|
1366 |
the FOUC effect of the tabs before JS re-assembles them. */ |
|
19f2753…
|
stephan
|
1367 |
|
|
19f2753…
|
stephan
|
1368 |
/******* Page list *******/ |
|
19f2753…
|
stephan
|
1369 |
{ |
|
19f2753…
|
stephan
|
1370 |
CX("<div id='wikiedit-tab-pages' " |
|
19f2753…
|
stephan
|
1371 |
"data-tab-parent='wikiedit-tabs' " |
|
19f2753…
|
stephan
|
1372 |
"data-tab-label='Wiki Page List' " |
|
19f2753…
|
stephan
|
1373 |
"class='hidden'" |
|
19f2753…
|
stephan
|
1374 |
">"); |
|
19f2753…
|
stephan
|
1375 |
CX("<div>Loading wiki pages list...</div>"); |
|
19f2753…
|
stephan
|
1376 |
CX("</div>"/*#wikiedit-tab-pages*/); |
|
19f2753…
|
stephan
|
1377 |
} |
|
275da70…
|
danield
|
1378 |
|
|
19f2753…
|
stephan
|
1379 |
/******* Content tab *******/ |
|
19f2753…
|
stephan
|
1380 |
{ |
|
19f2753…
|
stephan
|
1381 |
CX("<div id='wikiedit-tab-content' " |
|
19f2753…
|
stephan
|
1382 |
"data-tab-parent='wikiedit-tabs' " |
|
19f2753…
|
stephan
|
1383 |
"data-tab-label='Editor' " |
|
19f2753…
|
stephan
|
1384 |
"class='hidden'" |
|
19f2753…
|
stephan
|
1385 |
">"); |
|
f83c4fa…
|
stephan
|
1386 |
CX("<div class='" |
|
f83c4fa…
|
stephan
|
1387 |
"wikiedit-options flex-container flex-row child-gap-small'>"); |
|
34f7fd7…
|
stephan
|
1388 |
CX("<div class='input-with-label'>" |
|
19f2753…
|
stephan
|
1389 |
"<label>Mime type</label>"); |
|
fcf17b2…
|
drh
|
1390 |
mimetype_option_menu("text/x-markdown", "mimetype"); |
|
34f7fd7…
|
stephan
|
1391 |
CX("</div>"); |
|
19f2753…
|
stephan
|
1392 |
style_select_list_int("select-font-size", |
|
19f2753…
|
stephan
|
1393 |
"editor_font_size", "Editor font size", |
|
19f2753…
|
stephan
|
1394 |
NULL/*tooltip*/, |
|
19f2753…
|
stephan
|
1395 |
100, |
|
19f2753…
|
stephan
|
1396 |
"100%", 100, "125%", 125, |
|
19f2753…
|
stephan
|
1397 |
"150%", 150, "175%", 175, |
|
19f2753…
|
stephan
|
1398 |
"200%", 200, NULL); |
|
34f7fd7…
|
stephan
|
1399 |
CX("<div class='input-with-label'>" |
|
f83c4fa…
|
stephan
|
1400 |
/*will get moved around dynamically*/ |
|
34f7fd7…
|
stephan
|
1401 |
"<button class='wikiedit-save'>" |
|
f94a553…
|
stephan
|
1402 |
"Save</button>" |
|
34f7fd7…
|
stephan
|
1403 |
"<button class='wikiedit-save-close'>" |
|
34f7fd7…
|
stephan
|
1404 |
"Save & Close</button>" |
|
34f7fd7…
|
stephan
|
1405 |
"<div class='help-buttonlet'>" |
|
f83c4fa…
|
stephan
|
1406 |
"Save edits to this page and optionally return " |
|
f83c4fa…
|
stephan
|
1407 |
"to the wiki page viewer." |
|
34f7fd7…
|
stephan
|
1408 |
"</div>" |
|
34f7fd7…
|
stephan
|
1409 |
"</div>" /*will get moved around dynamically*/); |
|
f94a553…
|
stephan
|
1410 |
CX("<span class='save-button-slot'></span>"); |
|
34f7fd7…
|
stephan
|
1411 |
|
|
34f7fd7…
|
stephan
|
1412 |
CX("<div class='input-with-label'>" |
|
34f7fd7…
|
stephan
|
1413 |
"<button class='wikiedit-content-reload' " |
|
34f7fd7…
|
stephan
|
1414 |
">Discard & Reload</button>" |
|
34f7fd7…
|
stephan
|
1415 |
"<div class='help-buttonlet'>" |
|
34f7fd7…
|
stephan
|
1416 |
"Reload the file from the server, discarding " |
|
19f2753…
|
stephan
|
1417 |
"any local edits. To help avoid accidental loss of " |
|
19f2753…
|
stephan
|
1418 |
"edits, it requires confirmation (a second click) within " |
|
34f7fd7…
|
stephan
|
1419 |
"a few seconds or it will not reload." |
|
34f7fd7…
|
stephan
|
1420 |
"</div>" |
|
34f7fd7…
|
stephan
|
1421 |
"</div>"); |
|
4bb5515…
|
stephan
|
1422 |
wikiedit_emit_toggle_preview(); |
|
19f2753…
|
stephan
|
1423 |
CX("</div>"); |
|
19f2753…
|
stephan
|
1424 |
CX("<div class='flex-container flex-column stretch'>"); |
|
19f2753…
|
stephan
|
1425 |
CX("<textarea name='content' id='wikiedit-content-editor' " |
|
3814c9f…
|
stephan
|
1426 |
"class='wikiedit' rows='25'>"); |
|
19f2753…
|
stephan
|
1427 |
CX("</textarea>"); |
|
19f2753…
|
stephan
|
1428 |
CX("</div>"/*textarea wrapper*/); |
|
19f2753…
|
stephan
|
1429 |
CX("</div>"/*#tab-file-content*/); |
|
19f2753…
|
stephan
|
1430 |
} |
|
19f2753…
|
stephan
|
1431 |
/****** Preview tab ******/ |
|
19f2753…
|
stephan
|
1432 |
{ |
|
19f2753…
|
stephan
|
1433 |
CX("<div id='wikiedit-tab-preview' " |
|
19f2753…
|
stephan
|
1434 |
"data-tab-parent='wikiedit-tabs' " |
|
19f2753…
|
stephan
|
1435 |
"data-tab-label='Preview' " |
|
19f2753…
|
stephan
|
1436 |
"class='hidden'" |
|
19f2753…
|
stephan
|
1437 |
">"); |
|
f94a553…
|
stephan
|
1438 |
CX("<div class='wikiedit-options flex-container " |
|
f94a553…
|
stephan
|
1439 |
"flex-row child-gap-small'>"); |
|
19f2753…
|
stephan
|
1440 |
CX("<button id='btn-preview-refresh' " |
|
19f2753…
|
stephan
|
1441 |
"data-f-preview-from='wikiContent' " |
|
19f2753…
|
stephan
|
1442 |
/* ^^^ fossil.page[methodName]() OR text source elem ID, |
|
19f2753…
|
stephan
|
1443 |
** but we need a method in order to support clients swapping out |
|
19f2753…
|
stephan
|
1444 |
** the text editor with their own. */ |
|
19f2753…
|
stephan
|
1445 |
"data-f-preview-via='_postPreview' " |
|
19f2753…
|
stephan
|
1446 |
/* ^^^ fossil.page[methodName](content, callback) */ |
|
052d374…
|
stephan
|
1447 |
"data-f-preview-to='_previewTo' " |
|
052d374…
|
stephan
|
1448 |
/* ^^^ dest elem ID or fossil.page[methodName]*/ |
|
19f2753…
|
stephan
|
1449 |
">Refresh</button>"); |
|
19f2753…
|
stephan
|
1450 |
/* Toggle auto-update of preview when the Preview tab is selected. */ |
|
34f7fd7…
|
stephan
|
1451 |
CX("<div class='input-with-label'>" |
|
34f7fd7…
|
stephan
|
1452 |
"<input type='checkbox' value='1' " |
|
34f7fd7…
|
stephan
|
1453 |
"id='cb-preview-autorefresh' checked>" |
|
34f7fd7…
|
stephan
|
1454 |
"<label for='cb-preview-autorefresh'>Auto-refresh?</label>" |
|
34f7fd7…
|
stephan
|
1455 |
"</div>"); |
|
19f2753…
|
stephan
|
1456 |
CX("<span class='save-button-slot'></span>"); |
|
19f2753…
|
stephan
|
1457 |
CX("</div>"/*.wikiedit-options*/); |
|
19f2753…
|
stephan
|
1458 |
CX("<div id='wikiedit-tab-preview-wrapper'></div>"); |
|
19f2753…
|
stephan
|
1459 |
CX("</div>"/*#wikiedit-tab-preview*/); |
|
19f2753…
|
stephan
|
1460 |
} |
|
19f2753…
|
stephan
|
1461 |
|
|
19f2753…
|
stephan
|
1462 |
/****** Diff tab ******/ |
|
19f2753…
|
stephan
|
1463 |
{ |
|
19f2753…
|
stephan
|
1464 |
CX("<div id='wikiedit-tab-diff' " |
|
19f2753…
|
stephan
|
1465 |
"data-tab-parent='wikiedit-tabs' " |
|
19f2753…
|
stephan
|
1466 |
"data-tab-label='Diff' " |
|
19f2753…
|
stephan
|
1467 |
"class='hidden'" |
|
19f2753…
|
stephan
|
1468 |
">"); |
|
19f2753…
|
stephan
|
1469 |
|
|
f94a553…
|
stephan
|
1470 |
CX("<div class='wikiedit-options flex-container " |
|
f94a553…
|
stephan
|
1471 |
"flex-row child-gap-small' " |
|
19f2753…
|
stephan
|
1472 |
"id='wikiedit-tab-diff-buttons'>"); |
|
f83c4fa…
|
stephan
|
1473 |
CX("<div class='input-with-label'>" |
|
f83c4fa…
|
stephan
|
1474 |
"<button class='sbs'>Side-by-side</button>" |
|
f83c4fa…
|
stephan
|
1475 |
"<button class='unified'>Unified</button>" |
|
f83c4fa…
|
stephan
|
1476 |
"</div>"); |
|
19f2753…
|
stephan
|
1477 |
CX("<span class='save-button-slot'></span>"); |
|
19f2753…
|
stephan
|
1478 |
CX("</div>"); |
|
19f2753…
|
stephan
|
1479 |
CX("<div id='wikiedit-tab-diff-wrapper'>" |
|
19f2753…
|
stephan
|
1480 |
"Diffs will be shown here." |
|
19f2753…
|
stephan
|
1481 |
"</div>"); |
|
19f2753…
|
stephan
|
1482 |
CX("</div>"/*#wikiedit-tab-diff*/); |
|
19f2753…
|
stephan
|
1483 |
} |
|
19f2753…
|
stephan
|
1484 |
|
|
19f2753…
|
stephan
|
1485 |
/****** The obligatory "Misc" tab ******/ |
|
19f2753…
|
stephan
|
1486 |
{ |
|
19f2753…
|
stephan
|
1487 |
CX("<div id='wikiedit-tab-misc' " |
|
19f2753…
|
stephan
|
1488 |
"data-tab-parent='wikiedit-tabs' " |
|
ce15e35…
|
stephan
|
1489 |
"data-tab-label='Misc.' " |
|
19f2753…
|
stephan
|
1490 |
"class='hidden'" |
|
19f2753…
|
stephan
|
1491 |
">"); |
|
ce15e35…
|
stephan
|
1492 |
CX("<fieldset id='attachment-wrapper'>"); |
|
ce15e35…
|
stephan
|
1493 |
CX("<legend>Attachments</legend>"); |
|
ce15e35…
|
stephan
|
1494 |
CX("<div>No attachments for the current page.</div>"); |
|
ce15e35…
|
stephan
|
1495 |
CX("</fieldset>"); |
|
fb77abd…
|
stephan
|
1496 |
CX("<h2>Wiki formatting rules</h2>"); |
|
19f2753…
|
stephan
|
1497 |
CX("<ul>"); |
|
19f2753…
|
stephan
|
1498 |
CX("<li><a href='%R/wiki_rules'>Fossil wiki format</a></li>"); |
|
19f2753…
|
stephan
|
1499 |
CX("<li><a href='%R/md_rules'>Markdown format</a></li>"); |
|
19f2753…
|
stephan
|
1500 |
CX("<li>Plain-text pages use no special formatting.</li>"); |
|
19f2753…
|
stephan
|
1501 |
CX("</ul>"); |
|
fb77abd…
|
stephan
|
1502 |
CX("<h2>The \"Sandbox\" Page</h2>"); |
|
19f2753…
|
stephan
|
1503 |
CX("<p>The page named \"Sandbox\" is not a real wiki page. " |
|
19f2753…
|
stephan
|
1504 |
"It provides a place where users may test out wiki syntax " |
|
19f2753…
|
stephan
|
1505 |
"without having to actually save anything, nor pollute " |
|
19f2753…
|
stephan
|
1506 |
"the repo with endless test runs. Any attempt to save the " |
|
19f2753…
|
stephan
|
1507 |
"sandbox page will fail.</p>"); |
|
fb77abd…
|
stephan
|
1508 |
CX("<h2>Wiki Name Rules</h2>"); |
|
19f2753…
|
stephan
|
1509 |
well_formed_wiki_name_rules(); |
|
19f2753…
|
stephan
|
1510 |
CX("</div>"/*#wikiedit-tab-save*/); |
|
19f2753…
|
stephan
|
1511 |
} |
|
58d86b1…
|
stephan
|
1512 |
builtin_fossil_js_bundle_or("fetch", "dom", "tabs", "confirmer", |
|
58d86b1…
|
stephan
|
1513 |
"storage", "popupwidget", "copybutton", |
|
815b4fc…
|
wyoung
|
1514 |
"pikchr", NULL); |
|
51c1efd…
|
stephan
|
1515 |
builtin_fossil_js_bundle_or("diff", NULL); |
|
34f7fd7…
|
stephan
|
1516 |
builtin_request_js("fossil.page.wikiedit.js"); |
|
19f2753…
|
stephan
|
1517 |
builtin_fulfill_js_requests(); |
|
19f2753…
|
stephan
|
1518 |
/* Dynamically populate the editor... */ |
|
6854244…
|
drh
|
1519 |
style_script_begin(__FILE__,__LINE__); |
|
bc66513…
|
stephan
|
1520 |
{ |
|
bc66513…
|
stephan
|
1521 |
/* Render the current page list to save us an XHR request |
|
bc66513…
|
stephan
|
1522 |
during page initialization. This must be OUTSIDE of |
|
bc66513…
|
stephan
|
1523 |
an onPageLoad() handler or else it does not get applied |
|
bc66513…
|
stephan
|
1524 |
until after the wiki list widget is initialized. Similarly, |
|
bc66513…
|
stephan
|
1525 |
it must come *after* window.fossil is initialized. */ |
|
bc66513…
|
stephan
|
1526 |
CX("\nfossil.page.initialPageList = "); |
|
bc66513…
|
stephan
|
1527 |
wiki_render_page_list_json(1, 0); |
|
bc66513…
|
stephan
|
1528 |
CX(";\n"); |
|
bc66513…
|
stephan
|
1529 |
} |
|
bc66513…
|
stephan
|
1530 |
CX("fossil.onPageLoad(function(){\n"); |
|
19f2753…
|
stephan
|
1531 |
CX("const P = fossil.page;\n" |
|
19f2753…
|
stephan
|
1532 |
"try{\n"); |
|
19f2753…
|
stephan
|
1533 |
if(!found && zPageName && *zPageName){ |
|
19f2753…
|
stephan
|
1534 |
/* For a new page, stick a dummy entry in the JS-side stash |
|
19f2753…
|
stephan
|
1535 |
and "load" it from there. */ |
|
19f2753…
|
stephan
|
1536 |
CX("const winfo = {" |
|
19f2753…
|
stephan
|
1537 |
"\"name\": %!j, \"mimetype\": %!j, " |
|
19f2753…
|
stephan
|
1538 |
"\"type\": %!j, " |
|
19f2753…
|
stephan
|
1539 |
"\"parent\": null, \"version\": null" |
|
19f2753…
|
stephan
|
1540 |
"};\n", |
|
19f2753…
|
stephan
|
1541 |
zPageName, |
|
19f2753…
|
stephan
|
1542 |
zMimetype ? zMimetype : "text/x-fossil-wiki", |
|
19f2753…
|
stephan
|
1543 |
wiki_page_type_name(zPageName)); |
|
19f2753…
|
stephan
|
1544 |
/* If the JS-side stash already has this page, load that |
|
19f2753…
|
stephan
|
1545 |
copy from the stash, otherwise inject a new stash entry |
|
19f2753…
|
stephan
|
1546 |
for it and load *that* one... */ |
|
19f2753…
|
stephan
|
1547 |
CX("if(!P.$stash.getWinfo(winfo)){" |
|
19f2753…
|
stephan
|
1548 |
"P.$stash.updateWinfo(winfo,'');" |
|
19f2753…
|
stephan
|
1549 |
"}\n"); |
|
19f2753…
|
stephan
|
1550 |
} |
|
19f2753…
|
stephan
|
1551 |
if(zPageName && *zPageName){ |
|
19f2753…
|
stephan
|
1552 |
CX("P.loadPage(%!j);\n", zPageName); |
|
19f2753…
|
stephan
|
1553 |
} |
|
19f2753…
|
stephan
|
1554 |
CX("}catch(e){" |
|
19f2753…
|
stephan
|
1555 |
"fossil.error(e); console.error('Exception:',e);" |
|
19f2753…
|
stephan
|
1556 |
"}\n"); |
|
19f2753…
|
stephan
|
1557 |
CX("});\n"/*fossil.onPageLoad()*/); |
|
6854244…
|
drh
|
1558 |
style_script_end(); |
|
112c713…
|
drh
|
1559 |
style_finish_page(); |
|
aa57354…
|
drh
|
1560 |
} |
|
aa57354…
|
drh
|
1561 |
|
|
aa57354…
|
drh
|
1562 |
/* |
|
aa57354…
|
drh
|
1563 |
** WEBPAGE: wikinew |
|
aa57354…
|
drh
|
1564 |
** URL /wikinew |
|
aa57354…
|
drh
|
1565 |
** |
|
aa57354…
|
drh
|
1566 |
** Prompt the user to enter the name of a new wiki page. Then redirect |
|
aa57354…
|
drh
|
1567 |
** to the wikiedit screen for that new page. |
|
aa57354…
|
drh
|
1568 |
*/ |
|
aa57354…
|
drh
|
1569 |
void wikinew_page(void){ |
|
aa57354…
|
drh
|
1570 |
const char *zName; |
|
a5a5524…
|
drh
|
1571 |
const char *zMimetype; |
|
aa57354…
|
drh
|
1572 |
login_check_credentials(); |
|
b344d3c…
|
drh
|
1573 |
if( !g.perm.NewWiki ){ |
|
653dd40…
|
drh
|
1574 |
login_needed(g.anon.NewWiki); |
|
aa57354…
|
drh
|
1575 |
return; |
|
52b35c8…
|
jan.nijtmans
|
1576 |
} |
|
aa57354…
|
drh
|
1577 |
zName = PD("name",""); |
|
a5a5524…
|
drh
|
1578 |
zMimetype = wiki_filter_mimetypes(P("mimetype")); |
|
1942d58…
|
drh
|
1579 |
if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){ |
|
19f2753…
|
stephan
|
1580 |
cgi_redirectf("wikiedit?name=%T&mimetype=%s", zName, zMimetype); |
|
19f2753…
|
stephan
|
1581 |
} |
|
112c713…
|
drh
|
1582 |
style_set_current_feature("wiki"); |
|
aa57354…
|
drh
|
1583 |
style_header("Create A New Wiki Page"); |
|
6714c94…
|
drh
|
1584 |
wiki_standard_submenu(W_ALL_BUT(W_NEW)); |
|
3243e63…
|
drh
|
1585 |
@ <p>Rules for wiki page names:</p> |
|
aa57354…
|
drh
|
1586 |
well_formed_wiki_name_rules(); |
|
dfa3579…
|
drh
|
1587 |
form_begin(0, "%R/wikinew"); |
|
aa57354…
|
drh
|
1588 |
@ <p>Name of new wiki page: |
|
f5482a0…
|
wyoung
|
1589 |
@ <input style="width: 35;" type="text" name="name" value="%h(zName)"><br> |
|
18a84ed…
|
drh
|
1590 |
@ %z(href("%R/markup_help"))Markup style</a>: |
|
fcf17b2…
|
drh
|
1591 |
mimetype_option_menu("text/x-markdown", "mimetype"); |
|
f5482a0…
|
wyoung
|
1592 |
@ <br><input type="submit" value="Create"> |
|
aa57354…
|
drh
|
1593 |
@ </p></form> |
|
aa57354…
|
drh
|
1594 |
if( zName[0] ){ |
|
3243e63…
|
drh
|
1595 |
@ <p><span class="wikiError"> |
|
3243e63…
|
drh
|
1596 |
@ "%h(zName)" is not a valid wiki page name!</span></p> |
|
aa57354…
|
drh
|
1597 |
} |
|
112c713…
|
drh
|
1598 |
style_finish_page(); |
|
aa57354…
|
drh
|
1599 |
} |
|
aa57354…
|
drh
|
1600 |
|
|
aa57354…
|
drh
|
1601 |
|
|
aa57354…
|
drh
|
1602 |
/* |
|
aa57354…
|
drh
|
1603 |
** Append the wiki text for an remark to the end of the given BLOB. |
|
aa57354…
|
drh
|
1604 |
*/ |
|
a5a5524…
|
drh
|
1605 |
static void appendRemark(Blob *p, const char *zMimetype){ |
|
e01aa8c…
|
drh
|
1606 |
char *zDate; |
|
e01aa8c…
|
drh
|
1607 |
const char *zUser; |
|
e01aa8c…
|
drh
|
1608 |
const char *zRemark; |
|
e01aa8c…
|
drh
|
1609 |
char *zId; |
|
e01aa8c…
|
drh
|
1610 |
|
|
e01aa8c…
|
drh
|
1611 |
zDate = db_text(0, "SELECT datetime('now')"); |
|
a5a5524…
|
drh
|
1612 |
zRemark = PD("r",""); |
|
e01aa8c…
|
drh
|
1613 |
zUser = PD("u",g.zLogin); |
|
a5a5524…
|
drh
|
1614 |
if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ |
|
a5a5524…
|
drh
|
1615 |
zId = db_text(0, "SELECT lower(hex(randomblob(8)))"); |
|
f5482a0…
|
wyoung
|
1616 |
blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h", |
|
840b762…
|
drh
|
1617 |
zId, zDate, login_name()); |
|
840b762…
|
drh
|
1618 |
if( zUser[0] && fossil_strcmp(zUser,login_name()) ){ |
|
a5a5524…
|
drh
|
1619 |
blob_appendf(p, " (claiming to be %h)", zUser); |
|
a5a5524…
|
drh
|
1620 |
} |
|
f5482a0…
|
wyoung
|
1621 |
blob_appendf(p, " added:</i><br>\n%s</div id=\"%s\">", zRemark, zId); |
|
a5a5524…
|
drh
|
1622 |
}else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
|
840b762…
|
drh
|
1623 |
blob_appendf(p, "\n\n------\n*On %s UTC %h", zDate, login_name()); |
|
840b762…
|
drh
|
1624 |
if( zUser[0] && fossil_strcmp(zUser,login_name()) ){ |
|
a5a5524…
|
drh
|
1625 |
blob_appendf(p, " (claiming to be %h)", zUser); |
|
a5a5524…
|
drh
|
1626 |
} |
|
a5a5524…
|
drh
|
1627 |
blob_appendf(p, " added:*\n\n%s\n", zRemark); |
|
a5a5524…
|
drh
|
1628 |
}else{ |
|
a5a5524…
|
drh
|
1629 |
blob_appendf(p, "\n\n------------------------------------------------\n" |
|
840b762…
|
drh
|
1630 |
"On %s UTC %s", zDate, login_name()); |
|
840b762…
|
drh
|
1631 |
if( zUser[0] && fossil_strcmp(zUser,login_name()) ){ |
|
a5a5524…
|
drh
|
1632 |
blob_appendf(p, " (claiming to be %s)", zUser); |
|
a5a5524…
|
drh
|
1633 |
} |
|
a5a5524…
|
drh
|
1634 |
blob_appendf(p, " added:\n\n%s\n", zRemark); |
|
e01aa8c…
|
drh
|
1635 |
} |
|
a5a5524…
|
drh
|
1636 |
fossil_free(zDate); |
|
e01aa8c…
|
drh
|
1637 |
} |
|
e01aa8c…
|
drh
|
1638 |
|
|
e01aa8c…
|
drh
|
1639 |
/* |
|
e01aa8c…
|
drh
|
1640 |
** WEBPAGE: wikiappend |
|
a5a5524…
|
drh
|
1641 |
** URL: /wikiappend?name=PAGENAME&mimetype=MIMETYPE |
|
7ab0328…
|
drh
|
1642 |
** |
|
7ab0328…
|
drh
|
1643 |
** Append text to the end of a wiki page. |
|
e01aa8c…
|
drh
|
1644 |
*/ |
|
e01aa8c…
|
drh
|
1645 |
void wikiappend_page(void){ |
|
e01aa8c…
|
drh
|
1646 |
char *zTag; |
|
e01aa8c…
|
drh
|
1647 |
int rid = 0; |
|
e01aa8c…
|
drh
|
1648 |
const char *zPageName; |
|
e01aa8c…
|
drh
|
1649 |
const char *zUser; |
|
a5a5524…
|
drh
|
1650 |
const char *zMimetype; |
|
82b8587…
|
drh
|
1651 |
int goodCaptcha = 1; |
|
a5a5524…
|
drh
|
1652 |
const char *zFormat; |
|
202cbcf…
|
stephan
|
1653 |
Manifest *pWiki = 0; |
|
202cbcf…
|
stephan
|
1654 |
int isSandbox; |
|
e01aa8c…
|
drh
|
1655 |
|
|
e01aa8c…
|
drh
|
1656 |
login_check_credentials(); |
|
202cbcf…
|
stephan
|
1657 |
if( !g.perm.ApndWiki ){ |
|
202cbcf…
|
stephan
|
1658 |
login_needed(g.anon.ApndWiki); |
|
202cbcf…
|
stephan
|
1659 |
return; |
|
202cbcf…
|
stephan
|
1660 |
} |
|
e01aa8c…
|
drh
|
1661 |
zPageName = PD("name",""); |
|
a5a5524…
|
drh
|
1662 |
zMimetype = wiki_filter_mimetypes(P("mimetype")); |
|
e01aa8c…
|
drh
|
1663 |
if( check_name(zPageName) ) return; |
|
e01aa8c…
|
drh
|
1664 |
isSandbox = is_sandbox(zPageName); |
|
202cbcf…
|
stephan
|
1665 |
if(!isSandbox){ |
|
e01aa8c…
|
drh
|
1666 |
zTag = mprintf("wiki-%s", zPageName); |
|
52b35c8…
|
jan.nijtmans
|
1667 |
rid = db_int(0, |
|
e01aa8c…
|
drh
|
1668 |
"SELECT rid FROM tagxref" |
|
e01aa8c…
|
drh
|
1669 |
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
|
e01aa8c…
|
drh
|
1670 |
" ORDER BY mtime DESC", zTag |
|
e01aa8c…
|
drh
|
1671 |
); |
|
e01aa8c…
|
drh
|
1672 |
free(zTag); |
|
202cbcf…
|
stephan
|
1673 |
pWiki = rid ? manifest_get(rid, CFTYPE_WIKI, 0) : 0; |
|
202cbcf…
|
stephan
|
1674 |
if( !pWiki ){ |
|
0a4193b…
|
drh
|
1675 |
fossil_redirect_home(); |
|
0a4193b…
|
drh
|
1676 |
return; |
|
0a4193b…
|
drh
|
1677 |
} |
|
202cbcf…
|
stephan
|
1678 |
zMimetype = wiki_filter_mimetypes(pWiki->zMimetype) |
|
202cbcf…
|
stephan
|
1679 |
/* see https://fossil-scm.org/forum/forumpost/0acfdaac80 */; |
|
0a4193b…
|
drh
|
1680 |
} |
|
202cbcf…
|
stephan
|
1681 |
if( !isSandbox && P("submit")!=0 && P("r")!=0 && P("u")!=0 |
|
b77f1aa…
|
drh
|
1682 |
&& (goodCaptcha = captcha_is_correct(0)) |
|
920ace1…
|
drh
|
1683 |
&& cgi_csrf_safe(2) |
|
82b8587…
|
drh
|
1684 |
){ |
|
0a4193b…
|
drh
|
1685 |
char *zDate; |
|
0a4193b…
|
drh
|
1686 |
Blob cksum; |
|
0a4193b…
|
drh
|
1687 |
Blob body; |
|
0a4193b…
|
drh
|
1688 |
Blob wiki; |
|
0a4193b…
|
drh
|
1689 |
|
|
0a4193b…
|
drh
|
1690 |
blob_zero(&body); |
|
202cbcf…
|
stephan
|
1691 |
blob_append(&body, pWiki->zWiki, -1); |
|
202cbcf…
|
stephan
|
1692 |
blob_zero(&wiki); |
|
202cbcf…
|
stephan
|
1693 |
db_begin_transaction(); |
|
202cbcf…
|
stephan
|
1694 |
zDate = date_in_standard_format("now"); |
|
202cbcf…
|
stephan
|
1695 |
blob_appendf(&wiki, "D %s\n", zDate); |
|
202cbcf…
|
stephan
|
1696 |
blob_appendf(&wiki, "L %F\n", zPageName); |
|
202cbcf…
|
stephan
|
1697 |
if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")!=0 ){ |
|
202cbcf…
|
stephan
|
1698 |
blob_appendf(&wiki, "N %s\n", zMimetype); |
|
202cbcf…
|
stephan
|
1699 |
} |
|
202cbcf…
|
stephan
|
1700 |
if( rid ){ |
|
202cbcf…
|
stephan
|
1701 |
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
|
202cbcf…
|
stephan
|
1702 |
blob_appendf(&wiki, "P %s\n", zUuid); |
|
202cbcf…
|
stephan
|
1703 |
free(zUuid); |
|
202cbcf…
|
stephan
|
1704 |
} |
|
202cbcf…
|
stephan
|
1705 |
if( !login_is_nobody() ){ |
|
202cbcf…
|
stephan
|
1706 |
blob_appendf(&wiki, "U %F\n", login_name()); |
|
202cbcf…
|
stephan
|
1707 |
} |
|
202cbcf…
|
stephan
|
1708 |
appendRemark(&body, zMimetype); |
|
202cbcf…
|
stephan
|
1709 |
blob_appendf(&wiki, "W %d\n%s\n", blob_size(&body), blob_str(&body)); |
|
202cbcf…
|
stephan
|
1710 |
md5sum_blob(&wiki, &cksum); |
|
202cbcf…
|
stephan
|
1711 |
blob_appendf(&wiki, "Z %b\n", &cksum); |
|
202cbcf…
|
stephan
|
1712 |
blob_reset(&cksum); |
|
202cbcf…
|
stephan
|
1713 |
wiki_put(&wiki, rid, wiki_need_moderation(0)); |
|
202cbcf…
|
stephan
|
1714 |
db_end_transaction(0); |
|
202cbcf…
|
stephan
|
1715 |
manifest_destroy(pWiki); |
|
0a4193b…
|
drh
|
1716 |
cgi_redirectf("wiki?name=%T", zPageName); |
|
202cbcf…
|
stephan
|
1717 |
return; |
|
0a4193b…
|
drh
|
1718 |
} |
|
202cbcf…
|
stephan
|
1719 |
if( !isSandbox && P("cancel")!=0 ){ |
|
202cbcf…
|
stephan
|
1720 |
manifest_destroy(pWiki); |
|
0a4193b…
|
drh
|
1721 |
cgi_redirectf("wiki?name=%T", zPageName); |
|
0a4193b…
|
drh
|
1722 |
return; |
|
0a4193b…
|
drh
|
1723 |
} |
|
5d44004…
|
drh
|
1724 |
style_set_current_page("%T?name=%T", g.zPath, zPageName); |
|
112c713…
|
drh
|
1725 |
style_set_current_feature("wiki"); |
|
0a4193b…
|
drh
|
1726 |
style_header("Append Comment To: %s", zPageName); |
|
82b8587…
|
drh
|
1727 |
if( !goodCaptcha ){ |
|
82b8587…
|
drh
|
1728 |
@ <p class="generalError">Error: Incorrect security code.</p> |
|
82b8587…
|
drh
|
1729 |
} |
|
202cbcf…
|
stephan
|
1730 |
if( isSandbox ){ |
|
202cbcf…
|
stephan
|
1731 |
@ <p class="generalError">Error: the Sandbox page may not |
|
202cbcf…
|
stephan
|
1732 |
@ be appended to.</p> |
|
202cbcf…
|
stephan
|
1733 |
} |
|
202cbcf…
|
stephan
|
1734 |
if( !isSandbox && P("preview")!=0 ){ |
|
0a4193b…
|
drh
|
1735 |
Blob preview; |
|
0a4193b…
|
drh
|
1736 |
blob_zero(&preview); |
|
a5a5524…
|
drh
|
1737 |
appendRemark(&preview, zMimetype); |
|
f5482a0…
|
wyoung
|
1738 |
@ Preview:<hr> |
|
89b6dda…
|
drh
|
1739 |
safe_html_context(DOCSRC_WIKI); |
|
89b6dda…
|
drh
|
1740 |
wiki_render_by_mimetype(&preview, zMimetype); |
|
f5482a0…
|
wyoung
|
1741 |
@ <hr> |
|
b9897bb…
|
jeremy_c
|
1742 |
blob_reset(&preview); |
|
b9897bb…
|
jeremy_c
|
1743 |
} |
|
b9897bb…
|
jeremy_c
|
1744 |
zUser = PD("u", g.zLogin); |
|
dfa3579…
|
drh
|
1745 |
form_begin(0, "%R/wikiappend"); |
|
f5482a0…
|
wyoung
|
1746 |
@ <input type="hidden" name="name" value="%h(zPageName)"> |
|
f5482a0…
|
wyoung
|
1747 |
@ <input type="hidden" name="mimetype" value="%h(zMimetype)"> |
|
b9897bb…
|
jeremy_c
|
1748 |
@ Your Name: |
|
f5482a0…
|
wyoung
|
1749 |
@ <input type="text" name="u" size="20" value="%h(zUser)"><br> |
|
a5a5524…
|
drh
|
1750 |
zFormat = mimetype_common_name(zMimetype); |
|
f5482a0…
|
wyoung
|
1751 |
@ Comment to append (formatted as %s(zFormat)):<br> |
|
52b35c8…
|
jan.nijtmans
|
1752 |
@ <textarea name="r" class="wikiedit" cols="80" |
|
e01aa8c…
|
drh
|
1753 |
@ rows="10" wrap="virtual">%h(PD("r",""))</textarea> |
|
f5482a0…
|
wyoung
|
1754 |
@ <br> |
|
f5482a0…
|
wyoung
|
1755 |
@ <input type="submit" name="preview" value="Preview Your Comment"> |
|
f5482a0…
|
wyoung
|
1756 |
@ <input type="submit" name="submit" value="Append Your Changes"> |
|
f5482a0…
|
wyoung
|
1757 |
@ <input type="submit" name="cancel" value="Cancel"> |
|
f8a2aa0…
|
drh
|
1758 |
captcha_generate(0); |
|
e01aa8c…
|
drh
|
1759 |
@ </form> |
|
202cbcf…
|
stephan
|
1760 |
manifest_destroy(pWiki); |
|
112c713…
|
drh
|
1761 |
style_finish_page(); |
|
d0305b3…
|
aku
|
1762 |
} |
|
d0305b3…
|
aku
|
1763 |
|
|
d0305b3…
|
aku
|
1764 |
/* |
|
d0305b3…
|
aku
|
1765 |
** WEBPAGE: whistory |
|
d0305b3…
|
aku
|
1766 |
** URL: /whistory?name=PAGENAME |
|
81c22bc…
|
drh
|
1767 |
** |
|
c7a7a56…
|
drh
|
1768 |
** Additional parameters: |
|
c7a7a56…
|
drh
|
1769 |
** |
|
c7a7a56…
|
drh
|
1770 |
** showid Show RID values |
|
c7a7a56…
|
drh
|
1771 |
** |
|
d0305b3…
|
aku
|
1772 |
** Show the complete change history for a single wiki page. |
|
d0305b3…
|
aku
|
1773 |
*/ |
|
d0305b3…
|
aku
|
1774 |
void whistory_page(void){ |
|
6ebf5c7…
|
george
|
1775 |
Stmt q; |
|
d0305b3…
|
aku
|
1776 |
const char *zPageName; |
|
6ebf5c7…
|
george
|
1777 |
double rNow; |
|
6ebf5c7…
|
george
|
1778 |
int showRid; |
|
da9f362…
|
ashepilko
|
1779 |
char zAuthor[64]; |
|
d0305b3…
|
aku
|
1780 |
login_check_credentials(); |
|
cef8425…
|
drh
|
1781 |
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
|
d0305b3…
|
aku
|
1782 |
zPageName = PD("name",""); |
|
112c713…
|
drh
|
1783 |
style_set_current_feature("wiki"); |
|
49b0ff1…
|
drh
|
1784 |
style_header("History Of %s", zPageName); |
|
6ebf5c7…
|
george
|
1785 |
showRid = P("showid")!=0; |
|
6ebf5c7…
|
george
|
1786 |
db_prepare(&q, |
|
6ebf5c7…
|
george
|
1787 |
"SELECT" |
|
6ebf5c7…
|
george
|
1788 |
" event.mtime," |
|
6ebf5c7…
|
george
|
1789 |
" blob.uuid," |
|
6ebf5c7…
|
george
|
1790 |
" coalesce(event.euser,event.user)," |
|
6ebf5c7…
|
george
|
1791 |
" event.objid," |
|
6ebf5c7…
|
george
|
1792 |
" datetime(event.mtime)" |
|
6ebf5c7…
|
george
|
1793 |
" FROM event, blob, tag, tagxref" |
|
6ebf5c7…
|
george
|
1794 |
" WHERE event.type='w' AND blob.rid=event.objid" |
|
6ebf5c7…
|
george
|
1795 |
" AND tag.tagname='wiki-%q'" |
|
6ebf5c7…
|
george
|
1796 |
" AND tagxref.tagid=tag.tagid AND tagxref.srcid=event.objid" |
|
6ebf5c7…
|
george
|
1797 |
" ORDER BY event.mtime DESC", |
|
6ebf5c7…
|
george
|
1798 |
zPageName |
|
734e1ea…
|
drh
|
1799 |
); |
|
6ebf5c7…
|
george
|
1800 |
@ <h2>History of <a href="%R/wiki?name=%T(zPageName)">%h(zPageName)</a></h2> |
|
6ebf5c7…
|
george
|
1801 |
form_begin( "id='wh-form'", "%R/wdiff" ); |
|
f5482a0…
|
wyoung
|
1802 |
@ <input id="wh-pid" name="pid" type="radio" hidden> |
|
f5482a0…
|
wyoung
|
1803 |
@ <input id="wh-id" name="id" type="hidden"> |
|
6ebf5c7…
|
george
|
1804 |
@ </form> |
|
6ebf5c7…
|
george
|
1805 |
@ <style> .wh-clickable { cursor: pointer; } </style> |
|
6ebf5c7…
|
george
|
1806 |
@ <div class="brlist"> |
|
6ebf5c7…
|
george
|
1807 |
@ <table> |
|
6ebf5c7…
|
george
|
1808 |
@ <thead><tr> |
|
6ebf5c7…
|
george
|
1809 |
@ <th>Age</th> |
|
6ebf5c7…
|
george
|
1810 |
@ <th>Hash</th> |
|
6ebf5c7…
|
george
|
1811 |
@ <th><span title="Baseline from which diffs are computed (click to unset)" |
|
6ebf5c7…
|
george
|
1812 |
@ id="wh-cleaner" class="wh-clickable">⚓</span></th> |
|
6ebf5c7…
|
george
|
1813 |
@ <th>User<span hidden class="wh-clickable" |
|
6ebf5c7…
|
george
|
1814 |
@ id="wh-collapser"> ♲</span></th> |
|
6ebf5c7…
|
george
|
1815 |
if( showRid ){ |
|
6ebf5c7…
|
george
|
1816 |
@ <th>RID</th> |
|
6ebf5c7…
|
george
|
1817 |
} |
|
6ebf5c7…
|
george
|
1818 |
@ <th> </th> |
|
6ebf5c7…
|
george
|
1819 |
@ </tr></thead><tbody> |
|
6ebf5c7…
|
george
|
1820 |
rNow = db_double(0.0, "SELECT julianday('now')"); |
|
da9f362…
|
ashepilko
|
1821 |
memset( zAuthor, 0, sizeof(zAuthor) ); |
|
6ebf5c7…
|
george
|
1822 |
while( db_step(&q)==SQLITE_ROW ){ |
|
6ebf5c7…
|
george
|
1823 |
double rMtime = db_column_double(&q, 0); |
|
6ebf5c7…
|
george
|
1824 |
const char *zUuid = db_column_text(&q, 1); |
|
6ebf5c7…
|
george
|
1825 |
const char *zUser = db_column_text(&q, 2); |
|
6ebf5c7…
|
george
|
1826 |
int wrid = db_column_int(&q, 3); |
|
6ebf5c7…
|
george
|
1827 |
const char *zWhen = db_column_text(&q, 4); |
|
6ebf5c7…
|
george
|
1828 |
/* sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0); */ |
|
6ebf5c7…
|
george
|
1829 |
char *zAge = human_readable_age(rNow - rMtime); |
|
6ebf5c7…
|
george
|
1830 |
if( strncmp( zAuthor, zUser, sizeof(zAuthor) - 1 ) == 0 ) { |
|
6ebf5c7…
|
george
|
1831 |
@ <tr class="wh-intermediate" title="%s(zWhen)"> |
|
6ebf5c7…
|
george
|
1832 |
} |
|
6ebf5c7…
|
george
|
1833 |
else { |
|
6ebf5c7…
|
george
|
1834 |
strncpy( zAuthor, zUser, sizeof(zAuthor) - 1 ); |
|
6ebf5c7…
|
george
|
1835 |
@ <tr class="wh-major" title="%s(zWhen)"> |
|
6ebf5c7…
|
george
|
1836 |
} |
|
6ebf5c7…
|
george
|
1837 |
/* @ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td> */ |
|
6ebf5c7…
|
george
|
1838 |
@ <td>%s(zAge)</td> |
|
6ebf5c7…
|
george
|
1839 |
fossil_free(zAge); |
|
6ebf5c7…
|
george
|
1840 |
@ <td>%z(href("%R/info/%s",zUuid))%S(zUuid)</a></td> |
|
6ebf5c7…
|
george
|
1841 |
@ <td><input disabled type="radio" name="baseline" value="%S(zUuid)"/></td> |
|
f5482a0…
|
wyoung
|
1842 |
@ <td>%h(zUser)<span class="wh-iterations" hidden></td> |
|
6ebf5c7…
|
george
|
1843 |
if( showRid ){ |
|
6ebf5c7…
|
george
|
1844 |
@ <td>%z(href("%R/artifact/%S",zUuid))%d(wrid)</a></td> |
|
6ebf5c7…
|
george
|
1845 |
} |
|
6ebf5c7…
|
george
|
1846 |
@ <td>%z(chref("wh-difflink","%R/wdiff?id=%S",zUuid))diff</a></td> |
|
6ebf5c7…
|
george
|
1847 |
@ </tr> |
|
6ebf5c7…
|
george
|
1848 |
} |
|
6ebf5c7…
|
george
|
1849 |
@ </tbody></table></div> |
|
d0305b3…
|
aku
|
1850 |
db_finalize(&q); |
|
6ebf5c7…
|
george
|
1851 |
builtin_request_js("fossil.page.whistory.js"); |
|
6ebf5c7…
|
george
|
1852 |
/* style_table_sorter(); */ |
|
112c713…
|
drh
|
1853 |
style_finish_page(); |
|
8e3b7fa…
|
drh
|
1854 |
} |
|
8e3b7fa…
|
drh
|
1855 |
|
|
8e3b7fa…
|
drh
|
1856 |
/* |
|
8e3b7fa…
|
drh
|
1857 |
** WEBPAGE: wdiff |
|
b695e97…
|
drh
|
1858 |
** |
|
b695e97…
|
drh
|
1859 |
** Show the changes to a wiki page. |
|
b695e97…
|
drh
|
1860 |
** |
|
b695e97…
|
drh
|
1861 |
** Query parameters: |
|
b695e97…
|
drh
|
1862 |
** |
|
b695e97…
|
drh
|
1863 |
** id=HASH Hash prefix for the child version to be diffed. |
|
b695e97…
|
drh
|
1864 |
** rid=INTEGER RecordID for the child version |
|
b695e97…
|
drh
|
1865 |
** pid=HASH Hash prefix for the parent. |
|
8e3b7fa…
|
drh
|
1866 |
** |
|
b695e97…
|
drh
|
1867 |
** The "id" query parameter is required. "pid" is optional. If "pid" |
|
b695e97…
|
drh
|
1868 |
** is omitted, then the diff is against the first parent of the child. |
|
8e3b7fa…
|
drh
|
1869 |
*/ |
|
8e3b7fa…
|
drh
|
1870 |
void wdiff_page(void){ |
|
b695e97…
|
drh
|
1871 |
const char *zId; |
|
8839378…
|
drh
|
1872 |
const char *zIdFull; |
|
b695e97…
|
drh
|
1873 |
const char *zPid; |
|
d13054c…
|
drh
|
1874 |
Manifest *pW1, *pW2 = 0; |
|
19eaa3c…
|
drh
|
1875 |
int rid1, rid2, nextRid; |
|
8e3b7fa…
|
drh
|
1876 |
Blob w1, w2, d; |
|
1347a1d…
|
drh
|
1877 |
DiffConfig DCfg; |
|
8e3b7fa…
|
drh
|
1878 |
|
|
8e3b7fa…
|
drh
|
1879 |
login_check_credentials(); |
|
b695e97…
|
drh
|
1880 |
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
|
b695e97…
|
drh
|
1881 |
zId = P("id"); |
|
b695e97…
|
drh
|
1882 |
if( zId==0 ){ |
|
b695e97…
|
drh
|
1883 |
rid1 = atoi(PD("rid","0")); |
|
b695e97…
|
drh
|
1884 |
}else{ |
|
b695e97…
|
drh
|
1885 |
rid1 = name_to_typed_rid(zId, "w"); |
|
b695e97…
|
drh
|
1886 |
} |
|
8839378…
|
drh
|
1887 |
zIdFull = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid1); |
|
8839378…
|
drh
|
1888 |
if( zIdFull==0 ){ |
|
8839378…
|
drh
|
1889 |
if( zId ){ |
|
8839378…
|
drh
|
1890 |
webpage_notfound_error("No such wiki page: \"%s\"", zId); |
|
8839378…
|
drh
|
1891 |
}else{ |
|
8839378…
|
drh
|
1892 |
webpage_notfound_error("No such wiki page: %d", rid1); |
|
8839378…
|
drh
|
1893 |
} |
|
8839378…
|
drh
|
1894 |
return; |
|
8839378…
|
drh
|
1895 |
} |
|
8839378…
|
drh
|
1896 |
zId = zIdFull; |
|
ec81aee…
|
jan.nijtmans
|
1897 |
pW1 = manifest_get(rid1, CFTYPE_WIKI, 0); |
|
d13054c…
|
drh
|
1898 |
if( pW1==0 ) fossil_redirect_home(); |
|
d13054c…
|
drh
|
1899 |
blob_init(&w1, pW1->zWiki, -1); |
|
b695e97…
|
drh
|
1900 |
zPid = P("pid"); |
|
6ebf5c7…
|
george
|
1901 |
if( ( zPid==0 || zPid[0] == 0 ) && pW1->nParent ){ |
|
b695e97…
|
drh
|
1902 |
zPid = pW1->azParent[0]; |
|
b695e97…
|
drh
|
1903 |
} |
|
57f1e87…
|
drh
|
1904 |
cgi_check_for_malice(); |
|
6ebf5c7…
|
george
|
1905 |
if( zPid && zPid[0] != 0 ){ |
|
33f1c74…
|
drh
|
1906 |
char *zDate; |
|
b695e97…
|
drh
|
1907 |
rid2 = name_to_typed_rid(zPid, "w"); |
|
b695e97…
|
drh
|
1908 |
pW2 = manifest_get(rid2, CFTYPE_WIKI, 0); |
|
d13054c…
|
drh
|
1909 |
blob_init(&w2, pW2->zWiki, -1); |
|
b695e97…
|
drh
|
1910 |
@ <h2>Changes to \ |
|
b695e97…
|
drh
|
1911 |
@ "%z(href("%R/whistory?name=%s",pW1->zWikiTitle))%h(pW1->zWikiTitle)</a>" \ |
|
46de798…
|
george
|
1912 |
zDate = db_text(0, "SELECT datetime(%.16g,toLocal())",pW2->rDate); |
|
b695e97…
|
drh
|
1913 |
@ between %z(href("%R/info/%s",zPid))%z(zDate)</a> \ |
|
46de798…
|
george
|
1914 |
zDate = db_text(0, "SELECT datetime(%.16g,toLocal())",pW1->rDate); |
|
b695e97…
|
drh
|
1915 |
@ and %z(href("%R/info/%s",zId))%z(zDate)</a></h2> |
|
19eaa3c…
|
drh
|
1916 |
style_submenu_element("Previous", "%R/wdiff?id=%S", zPid); |
|
b695e97…
|
drh
|
1917 |
}else{ |
|
b695e97…
|
drh
|
1918 |
blob_zero(&w2); |
|
b695e97…
|
drh
|
1919 |
@ <h2>Initial version of \ |
|
b695e97…
|
drh
|
1920 |
@ "%z(href("%R/whistory?name=%s",pW1->zWikiTitle))%h(pW1->zWikiTitle)</a>"\ |
|
b695e97…
|
drh
|
1921 |
@ </h2> |
|
b695e97…
|
drh
|
1922 |
} |
|
19eaa3c…
|
drh
|
1923 |
nextRid = wiki_next(wiki_tagid(pW1->zWikiTitle),pW1->rDate); |
|
19eaa3c…
|
drh
|
1924 |
if( nextRid ){ |
|
19eaa3c…
|
drh
|
1925 |
style_submenu_element("Next", "%R/wdiff?rid=%d", nextRid); |
|
19eaa3c…
|
drh
|
1926 |
} |
|
112c713…
|
drh
|
1927 |
style_set_current_feature("wiki"); |
|
b695e97…
|
drh
|
1928 |
style_header("Changes To %s", pW1->zWikiTitle); |
|
8e3b7fa…
|
drh
|
1929 |
blob_zero(&d); |
|
1347a1d…
|
drh
|
1930 |
construct_diff_flags(1, &DCfg); |
|
1347a1d…
|
drh
|
1931 |
DCfg.diffFlags |= DIFF_HTML | DIFF_LINENO; |
|
1347a1d…
|
drh
|
1932 |
text_diff(&w2, &w1, &d, &DCfg); |
|
96f1975…
|
drh
|
1933 |
@ %s(blob_str(&d)) |
|
d13054c…
|
drh
|
1934 |
manifest_destroy(pW1); |
|
d13054c…
|
drh
|
1935 |
manifest_destroy(pW2); |
|
112c713…
|
drh
|
1936 |
style_finish_page(); |
|
d0305b3…
|
aku
|
1937 |
} |
|
796dcfe…
|
drh
|
1938 |
|
|
796dcfe…
|
drh
|
1939 |
/* |
|
81c22bc…
|
drh
|
1940 |
** A query that returns information about all wiki pages. |
|
796dcfe…
|
drh
|
1941 |
** |
|
81c22bc…
|
drh
|
1942 |
** wname Name of the wiki page |
|
81c22bc…
|
drh
|
1943 |
** wsort Sort names by this label |
|
81c22bc…
|
drh
|
1944 |
** wrid rid of the most recent version of the page |
|
81c22bc…
|
drh
|
1945 |
** wmtime time most recent version was created |
|
81c22bc…
|
drh
|
1946 |
** wcnt Number of versions of this wiki page |
|
796dcfe…
|
drh
|
1947 |
** |
|
81c22bc…
|
drh
|
1948 |
** The wrid value is zero for deleted wiki pages. |
|
796dcfe…
|
drh
|
1949 |
*/ |
|
275da70…
|
danield
|
1950 |
static const char listAllWikiPages[] = |
|
81c22bc…
|
drh
|
1951 |
@ SELECT |
|
81c22bc…
|
drh
|
1952 |
@ substr(tag.tagname, 6) AS wname, |
|
81c22bc…
|
drh
|
1953 |
@ lower(substr(tag.tagname, 6)) AS sortname, |
|
81c22bc…
|
drh
|
1954 |
@ tagxref.value+0 AS wrid, |
|
81c22bc…
|
drh
|
1955 |
@ max(tagxref.mtime) AS wmtime, |
|
81c22bc…
|
drh
|
1956 |
@ count(*) AS wcnt |
|
81c22bc…
|
drh
|
1957 |
@ FROM |
|
81c22bc…
|
drh
|
1958 |
@ tag, |
|
81c22bc…
|
drh
|
1959 |
@ tagxref |
|
81c22bc…
|
drh
|
1960 |
@ WHERE |
|
81c22bc…
|
drh
|
1961 |
@ tag.tagname GLOB 'wiki-*' |
|
81c22bc…
|
drh
|
1962 |
@ AND tagxref.tagid=tag.tagid |
|
4218b20…
|
stephan
|
1963 |
@ AND TYPEOF(wrid)='integer' -- only wiki- tags which are wiki pages |
|
81c22bc…
|
drh
|
1964 |
@ GROUP BY 1 |
|
81c22bc…
|
drh
|
1965 |
@ ORDER BY 2; |
|
81c22bc…
|
drh
|
1966 |
; |
|
81c22bc…
|
drh
|
1967 |
|
|
d0305b3…
|
aku
|
1968 |
/* |
|
d0305b3…
|
aku
|
1969 |
** WEBPAGE: wcontent |
|
7c2577b…
|
drh
|
1970 |
** |
|
7c2577b…
|
drh
|
1971 |
** all=1 Show deleted pages |
|
c7a7a56…
|
drh
|
1972 |
** showid Show rid values for each page. |
|
d0305b3…
|
aku
|
1973 |
** |
|
d0305b3…
|
aku
|
1974 |
** List all available wiki pages with date created and last modified. |
|
d0305b3…
|
aku
|
1975 |
*/ |
|
d0305b3…
|
aku
|
1976 |
void wcontent_page(void){ |
|
d0305b3…
|
aku
|
1977 |
Stmt q; |
|
81c22bc…
|
drh
|
1978 |
double rNow; |
|
7c2577b…
|
drh
|
1979 |
int showAll = P("all")!=0; |
|
c7a7a56…
|
drh
|
1980 |
int showRid = P("showid")!=0; |
|
e3c8aad…
|
george
|
1981 |
int showCkBr; |
|
7c2577b…
|
drh
|
1982 |
|
|
cde6e7a…
|
stephan
|
1983 |
login_check_credentials(); |
|
653dd40…
|
drh
|
1984 |
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
|
112c713…
|
drh
|
1985 |
style_set_current_feature("wiki"); |
|
cde6e7a…
|
stephan
|
1986 |
style_header("Available Wiki Pages"); |
|
7c2577b…
|
drh
|
1987 |
if( showAll ){ |
|
a40e8a0…
|
drh
|
1988 |
style_submenu_element("Active", "%R/wcontent"); |
|
7c2577b…
|
drh
|
1989 |
}else{ |
|
a40e8a0…
|
drh
|
1990 |
style_submenu_element("All", "%R/wcontent?all=1"); |
|
29a2494…
|
danield
|
1991 |
} |
|
57f1e87…
|
drh
|
1992 |
cgi_check_for_malice(); |
|
e3c8aad…
|
george
|
1993 |
showCkBr = db_exists( |
|
e3c8aad…
|
george
|
1994 |
"SELECT tag.tagname AS tn FROM tag JOIN tagxref USING(tagid) " |
|
3c2aba7…
|
george
|
1995 |
"WHERE ( tn GLOB 'wiki-checkin/*' OR tn GLOB 'wiki-branch/*' OR " |
|
3c2aba7…
|
george
|
1996 |
" tn GLOB 'wiki-tag/*' OR tn GLOB 'wiki-ticket/*' ) " |
|
e3c8aad…
|
george
|
1997 |
" AND TYPEOF(tagxref.value+0)='integer'" ); |
|
e3c8aad…
|
george
|
1998 |
if( showCkBr ){ |
|
e3c8aad…
|
george
|
1999 |
showCkBr = P("showckbr")!=0; |
|
e3c8aad…
|
george
|
2000 |
style_submenu_checkbox("showckbr", "Show associated wikis", 0, 0); |
|
e3c8aad…
|
george
|
2001 |
} |
|
6714c94…
|
drh
|
2002 |
wiki_standard_submenu(W_ALL_BUT(W_LIST)); |
|
81c22bc…
|
drh
|
2003 |
db_prepare(&q, listAllWikiPages/*works-like:""*/); |
|
81c22bc…
|
drh
|
2004 |
@ <div class="brlist"> |
|
81c22bc…
|
drh
|
2005 |
@ <table class='sortable' data-column-types='tKN' data-init-sort='1'> |
|
81c22bc…
|
drh
|
2006 |
@ <thead><tr> |
|
81c22bc…
|
drh
|
2007 |
@ <th>Name</th> |
|
81c22bc…
|
drh
|
2008 |
@ <th>Last Change</th> |
|
81c22bc…
|
drh
|
2009 |
@ <th>Versions</th> |
|
c7a7a56…
|
drh
|
2010 |
if( showRid ){ |
|
c7a7a56…
|
drh
|
2011 |
@ <th>RID</th> |
|
c7a7a56…
|
drh
|
2012 |
} |
|
81c22bc…
|
drh
|
2013 |
@ </tr></thead><tbody> |
|
81c22bc…
|
drh
|
2014 |
rNow = db_double(0.0, "SELECT julianday('now')"); |
|
14c19fb…
|
drh
|
2015 |
while( db_step(&q)==SQLITE_ROW ){ |
|
81c22bc…
|
drh
|
2016 |
const char *zWName = db_column_text(&q, 0); |
|
81c22bc…
|
drh
|
2017 |
const char *zSort = db_column_text(&q, 1); |
|
81c22bc…
|
drh
|
2018 |
int wrid = db_column_int(&q, 2); |
|
81c22bc…
|
drh
|
2019 |
double rWmtime = db_column_double(&q, 3); |
|
81c22bc…
|
drh
|
2020 |
sqlite3_int64 iMtime = (sqlite3_int64)(rWmtime*86400.0); |
|
81c22bc…
|
drh
|
2021 |
char *zAge; |
|
81c22bc…
|
drh
|
2022 |
int wcnt = db_column_int(&q, 4); |
|
5602385…
|
drh
|
2023 |
char *zWDisplayName; |
|
5602385…
|
drh
|
2024 |
|
|
e3c8aad…
|
george
|
2025 |
if( !showCkBr && |
|
3c2aba7…
|
george
|
2026 |
(has_prefix("checkin/", zWName) || |
|
3c2aba7…
|
george
|
2027 |
has_prefix("branch/", zWName) || |
|
3c2aba7…
|
george
|
2028 |
has_prefix("tag/", zWName) || |
|
3c2aba7…
|
george
|
2029 |
has_prefix("ticket/", zWName) )){ |
|
e3c8aad…
|
george
|
2030 |
continue; |
|
e3c8aad…
|
george
|
2031 |
} |
|
3c2aba7…
|
george
|
2032 |
if( has_prefix("checkin/",zWName) || has_prefix("ticket/",zWName) ){ |
|
5602385…
|
drh
|
2033 |
zWDisplayName = mprintf("%.25s...", zWName); |
|
5602385…
|
drh
|
2034 |
}else{ |
|
3c2aba7…
|
george
|
2035 |
zWDisplayName = fossil_strdup(zWName); |
|
5602385…
|
drh
|
2036 |
} |
|
81c22bc…
|
drh
|
2037 |
if( wrid==0 ){ |
|
81c22bc…
|
drh
|
2038 |
if( !showAll ) continue; |
|
81c22bc…
|
drh
|
2039 |
@ <tr><td data-sortkey="%h(zSort)">\ |
|
5602385…
|
drh
|
2040 |
@ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td> |
|
81c22bc…
|
drh
|
2041 |
}else{ |
|
88b8df3…
|
ashepilko
|
2042 |
@ <tr><td data-sortkey="%h(zSort)">\ |
|
50e7a31…
|
drh
|
2043 |
@ %z(href("%R/wiki?name=%T&p",zWName))%h(zWDisplayName)</a></td> |
|
81c22bc…
|
drh
|
2044 |
} |
|
81c22bc…
|
drh
|
2045 |
zAge = human_readable_age(rNow - rWmtime); |
|
81c22bc…
|
drh
|
2046 |
@ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td> |
|
81c22bc…
|
drh
|
2047 |
fossil_free(zAge); |
|
81c22bc…
|
drh
|
2048 |
@ <td>%z(href("%R/whistory?name=%T",zWName))%d(wcnt)</a></td> |
|
c7a7a56…
|
drh
|
2049 |
if( showRid ){ |
|
c7a7a56…
|
drh
|
2050 |
@ <td>%d(wrid)</td> |
|
c7a7a56…
|
drh
|
2051 |
} |
|
81c22bc…
|
drh
|
2052 |
@ </tr> |
|
5602385…
|
drh
|
2053 |
fossil_free(zWDisplayName); |
|
81c22bc…
|
drh
|
2054 |
} |
|
81c22bc…
|
drh
|
2055 |
@ </tbody></table></div> |
|
14c19fb…
|
drh
|
2056 |
db_finalize(&q); |
|
81c22bc…
|
drh
|
2057 |
style_table_sorter(); |
|
112c713…
|
drh
|
2058 |
style_finish_page(); |
|
14c19fb…
|
drh
|
2059 |
} |
|
14c19fb…
|
drh
|
2060 |
|
|
14c19fb…
|
drh
|
2061 |
/* |
|
14c19fb…
|
drh
|
2062 |
** WEBPAGE: wfind |
|
14c19fb…
|
drh
|
2063 |
** |
|
14c19fb…
|
drh
|
2064 |
** URL: /wfind?title=TITLE |
|
14c19fb…
|
drh
|
2065 |
** List all wiki pages whose titles contain the search text |
|
14c19fb…
|
drh
|
2066 |
*/ |
|
14c19fb…
|
drh
|
2067 |
void wfind_page(void){ |
|
14c19fb…
|
drh
|
2068 |
Stmt q; |
|
4e18dba…
|
jan.nijtmans
|
2069 |
const char *zTitle; |
|
14c19fb…
|
drh
|
2070 |
login_check_credentials(); |
|
653dd40…
|
drh
|
2071 |
if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } |
|
14c19fb…
|
drh
|
2072 |
zTitle = PD("title","*"); |
|
57f1e87…
|
drh
|
2073 |
cgi_check_for_malice(); |
|
112c713…
|
drh
|
2074 |
style_set_current_feature("wiki"); |
|
14c19fb…
|
drh
|
2075 |
style_header("Wiki Pages Found"); |
|
cde6e7a…
|
stephan
|
2076 |
@ <ul> |
|
52b35c8…
|
jan.nijtmans
|
2077 |
db_prepare(&q, |
|
6454153…
|
drh
|
2078 |
"SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'" |
|
6454153…
|
drh
|
2079 |
" ORDER BY lower(tagname) /*sort*/" , |
|
b3e32c8…
|
jan.nijtmans
|
2080 |
zTitle); |
|
cde6e7a…
|
stephan
|
2081 |
while( db_step(&q)==SQLITE_ROW ){ |
|
cde6e7a…
|
stephan
|
2082 |
const char *zName = db_column_text(&q, 0); |
|
433cde1…
|
drh
|
2083 |
@ <li>%z(href("%R/wiki?name=%T",zName))%h(zName)</a></li> |
|
cde6e7a…
|
stephan
|
2084 |
} |
|
cde6e7a…
|
stephan
|
2085 |
db_finalize(&q); |
|
cde6e7a…
|
stephan
|
2086 |
@ </ul> |
|
112c713…
|
drh
|
2087 |
style_finish_page(); |
|
7c0f4ec…
|
jan.nijtmans
|
2088 |
} |
|
7c0f4ec…
|
jan.nijtmans
|
2089 |
|
|
7c0f4ec…
|
jan.nijtmans
|
2090 |
/* |
|
7c0f4ec…
|
jan.nijtmans
|
2091 |
** Add a new wiki page to the repository. The page name is |
|
05cd9fa…
|
rberteig
|
2092 |
** given by the zPageName parameter. rid must be zero to create |
|
05cd9fa…
|
rberteig
|
2093 |
** a new page otherwise the page identified by rid is updated. |
|
e03d1be…
|
drh
|
2094 |
** |
|
e03d1be…
|
drh
|
2095 |
** The content of the new page is given by the blob pContent. |
|
52d242a…
|
stephan
|
2096 |
** |
|
52d242a…
|
stephan
|
2097 |
** zMimeType specifies the N-card for the wiki page. If it is 0, |
|
52d242a…
|
stephan
|
2098 |
** empty, or "text/x-fossil-wiki" (the default format) then it is |
|
52d242a…
|
stephan
|
2099 |
** ignored. |
|
e03d1be…
|
drh
|
2100 |
*/ |
|
05cd9fa…
|
rberteig
|
2101 |
int wiki_cmd_commit(const char *zPageName, int rid, Blob *pContent, |
|
636c334…
|
mistachkin
|
2102 |
const char *zMimeType, int localUser){ |
|
cde6e7a…
|
stephan
|
2103 |
Blob wiki; /* Wiki page content */ |
|
cde6e7a…
|
stephan
|
2104 |
Blob cksum; /* wiki checksum */ |
|
e03d1be…
|
drh
|
2105 |
char *zDate; /* timestamp */ |
|
e03d1be…
|
drh
|
2106 |
char *zUuid; /* uuid for rid */ |
|
e03d1be…
|
drh
|
2107 |
|
|
cde6e7a…
|
stephan
|
2108 |
blob_zero(&wiki); |
|
722d7ca…
|
drh
|
2109 |
zDate = date_in_standard_format("now"); |
|
cde6e7a…
|
stephan
|
2110 |
blob_appendf(&wiki, "D %s\n", zDate); |
|
cde6e7a…
|
stephan
|
2111 |
free(zDate); |
|
cde6e7a…
|
stephan
|
2112 |
blob_appendf(&wiki, "L %F\n", zPageName ); |
|
52d242a…
|
stephan
|
2113 |
if( zMimeType && *zMimeType |
|
52d242a…
|
stephan
|
2114 |
&& 0!=fossil_strcmp(zMimeType,"text/x-fossil-wiki") ){ |
|
52d242a…
|
stephan
|
2115 |
blob_appendf(&wiki, "N %F\n", zMimeType); |
|
52d242a…
|
stephan
|
2116 |
} |
|
e03d1be…
|
drh
|
2117 |
if( rid ){ |
|
e03d1be…
|
drh
|
2118 |
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
|
e03d1be…
|
drh
|
2119 |
blob_appendf(&wiki, "P %s\n", zUuid); |
|
e03d1be…
|
drh
|
2120 |
free(zUuid); |
|
e03d1be…
|
drh
|
2121 |
} |
|
cde6e7a…
|
stephan
|
2122 |
user_select(); |
|
840b762…
|
drh
|
2123 |
if( !login_is_nobody() ){ |
|
840b762…
|
drh
|
2124 |
blob_appendf(&wiki, "U %F\n", login_name()); |
|
cde6e7a…
|
stephan
|
2125 |
} |
|
e03d1be…
|
drh
|
2126 |
blob_appendf( &wiki, "W %d\n%s\n", blob_size(pContent), |
|
e03d1be…
|
drh
|
2127 |
blob_str(pContent) ); |
|
cde6e7a…
|
stephan
|
2128 |
md5sum_blob(&wiki, &cksum); |
|
cde6e7a…
|
stephan
|
2129 |
blob_appendf(&wiki, "Z %b\n", &cksum); |
|
cde6e7a…
|
stephan
|
2130 |
blob_reset(&cksum); |
|
e1962ef…
|
drh
|
2131 |
db_begin_transaction(); |
|
636c334…
|
mistachkin
|
2132 |
wiki_put(&wiki, 0, wiki_need_moderation(localUser)); |
|
cde6e7a…
|
stephan
|
2133 |
db_end_transaction(0); |
|
cde6e7a…
|
stephan
|
2134 |
return 1; |
|
636c334…
|
mistachkin
|
2135 |
} |
|
636c334…
|
mistachkin
|
2136 |
|
|
636c334…
|
mistachkin
|
2137 |
/* |
|
d71b648…
|
jamsek
|
2138 |
** Determine the rid for a tech note given either its id, its timestamp, |
|
d71b648…
|
jamsek
|
2139 |
** or its tag. Returns 0 if there is no such item and -1 if the details |
|
05cd9fa…
|
rberteig
|
2140 |
** are ambiguous and could refer to multiple items. |
|
05cd9fa…
|
rberteig
|
2141 |
*/ |
|
05cd9fa…
|
rberteig
|
2142 |
int wiki_technote_to_rid(const char *zETime) { |
|
05cd9fa…
|
rberteig
|
2143 |
int rid=0; /* Artifact ID of the tech note */ |
|
05cd9fa…
|
rberteig
|
2144 |
int nETime = strlen(zETime); |
|
05cd9fa…
|
rberteig
|
2145 |
Stmt q; |
|
6235284…
|
drh
|
2146 |
if( nETime>=4 && nETime<=HNAME_MAX && validate16(zETime, nETime) ){ |
|
fd9b7bd…
|
drh
|
2147 |
char zUuid[HNAME_MAX+1]; |
|
05cd9fa…
|
rberteig
|
2148 |
memcpy(zUuid, zETime, nETime+1); |
|
05cd9fa…
|
rberteig
|
2149 |
canonical16(zUuid, nETime); |
|
05cd9fa…
|
rberteig
|
2150 |
db_prepare(&q, |
|
05cd9fa…
|
rberteig
|
2151 |
"SELECT e.objid" |
|
05cd9fa…
|
rberteig
|
2152 |
" FROM event e, tag t" |
|
05cd9fa…
|
rberteig
|
2153 |
" WHERE e.type='e' AND e.tagid IS NOT NULL AND t.tagid=e.tagid" |
|
05cd9fa…
|
rberteig
|
2154 |
" AND t.tagname GLOB 'event-%q*'", |
|
05cd9fa…
|
rberteig
|
2155 |
zUuid |
|
05cd9fa…
|
rberteig
|
2156 |
); |
|
05cd9fa…
|
rberteig
|
2157 |
if( db_step(&q)==SQLITE_ROW ){ |
|
05cd9fa…
|
rberteig
|
2158 |
rid = db_column_int(&q, 0); |
|
05cd9fa…
|
rberteig
|
2159 |
if( db_step(&q)==SQLITE_ROW ) rid = -1; |
|
05cd9fa…
|
rberteig
|
2160 |
} |
|
05cd9fa…
|
rberteig
|
2161 |
db_finalize(&q); |
|
05cd9fa…
|
rberteig
|
2162 |
} |
|
05cd9fa…
|
rberteig
|
2163 |
if (!rid) { |
|
05cd9fa…
|
rberteig
|
2164 |
if (strlen(zETime)>4) { |
|
05cd9fa…
|
rberteig
|
2165 |
rid = db_int(0, "SELECT objid" |
|
05cd9fa…
|
rberteig
|
2166 |
" FROM event" |
|
05cd9fa…
|
rberteig
|
2167 |
" WHERE datetime(mtime)=datetime('%q')" |
|
05cd9fa…
|
rberteig
|
2168 |
" AND type='e'" |
|
05cd9fa…
|
rberteig
|
2169 |
" AND tagid IS NOT NULL" |
|
05cd9fa…
|
rberteig
|
2170 |
" ORDER BY objid DESC LIMIT 1", |
|
05cd9fa…
|
rberteig
|
2171 |
zETime); |
|
05cd9fa…
|
rberteig
|
2172 |
} |
|
05cd9fa…
|
rberteig
|
2173 |
} |
|
d71b648…
|
jamsek
|
2174 |
if( !rid ) { |
|
d71b648…
|
jamsek
|
2175 |
/* |
|
d71b648…
|
jamsek
|
2176 |
** At present, technote tags are prefixed with 'sym-', which shouldn't |
|
d71b648…
|
jamsek
|
2177 |
** be the case, so we check for both with and without the prefix until |
|
d71b648…
|
jamsek
|
2178 |
** such time as tags have the errant prefix dropped. |
|
d71b648…
|
jamsek
|
2179 |
*/ |
|
d71b648…
|
jamsek
|
2180 |
rid = db_int(0, "SELECT e.objid" |
|
9db696e…
|
danield
|
2181 |
" FROM event e, tag t, tagxref tx" |
|
9db696e…
|
danield
|
2182 |
" WHERE e.type='e'" |
|
9db696e…
|
danield
|
2183 |
" AND e.tagid IS NOT NULL" |
|
9db696e…
|
danield
|
2184 |
" AND e.objid IN" |
|
d71b648…
|
jamsek
|
2185 |
" (SELECT rid FROM tagxref" |
|
d71b648…
|
jamsek
|
2186 |
" WHERE tagid=(SELECT tagid FROM tag" |
|
d71b648…
|
jamsek
|
2187 |
" WHERE tagname GLOB '%q'))" |
|
9db696e…
|
danield
|
2188 |
" OR e.objid IN" |
|
d71b648…
|
jamsek
|
2189 |
" (SELECT rid FROM tagxref" |
|
d71b648…
|
jamsek
|
2190 |
" WHERE tagid=(SELECT tagid FROM tag" |
|
d71b648…
|
jamsek
|
2191 |
" WHERE tagname GLOB 'sym-%q'))" |
|
9db696e…
|
danield
|
2192 |
" ORDER BY e.mtime DESC LIMIT 1", |
|
9db696e…
|
danield
|
2193 |
zETime, zETime); |
|
d71b648…
|
jamsek
|
2194 |
} |
|
05cd9fa…
|
rberteig
|
2195 |
return rid; |
|
05cd9fa…
|
rberteig
|
2196 |
} |
|
05cd9fa…
|
rberteig
|
2197 |
|
|
05cd9fa…
|
rberteig
|
2198 |
/* |
|
841772c…
|
drh
|
2199 |
** COMMAND: wiki* |
|
92df474…
|
stephan
|
2200 |
** |
|
26eef7f…
|
rberteig
|
2201 |
** Usage: %fossil wiki (export|create|commit|list) WikiName |
|
92df474…
|
stephan
|
2202 |
** |
|
045deb2…
|
drh
|
2203 |
** Run various subcommands to work with wiki entries or tech notes. |
|
045deb2…
|
drh
|
2204 |
** |
|
e58c76a…
|
drh
|
2205 |
** > fossil wiki export ?OPTIONS? PAGENAME ?FILE? |
|
d71b648…
|
jamsek
|
2206 |
** > fossil wiki export ?OPTIONS? -t|--technote DATETIME|TECHNOTE-ID|TAG ?FILE? |
|
b23eb83…
|
stephan
|
2207 |
** |
|
fe86954…
|
stephan
|
2208 |
** Sends the latest version of either a wiki page or of a tech |
|
fe86954…
|
stephan
|
2209 |
** note to the given file or standard output. A filename of "-" |
|
fe86954…
|
stephan
|
2210 |
** writes the output to standard output. The directory parts of |
|
fe86954…
|
stephan
|
2211 |
** the output filename are created if needed. |
|
b23eb83…
|
stephan
|
2212 |
** If PAGENAME is provided, the named wiki page will be output. |
|
e58c76a…
|
drh
|
2213 |
** |
|
e58c76a…
|
drh
|
2214 |
** Options: |
|
d71b648…
|
jamsek
|
2215 |
** -t|--technote DATETIME|TECHNOTE-ID|TAG |
|
e58c76a…
|
drh
|
2216 |
** Specifies that a technote, rather than a wiki page, |
|
e58c76a…
|
drh
|
2217 |
** will be exported. If DATETIME is used, the most |
|
e58c76a…
|
drh
|
2218 |
** recently modified tech note with that DATETIME will |
|
d71b648…
|
jamsek
|
2219 |
** output. If TAG is used, the most recently modified |
|
d71b648…
|
jamsek
|
2220 |
** tech note with that TAG will be output. |
|
e58c76a…
|
drh
|
2221 |
** -h|--html The body (only) is rendered in HTML form, without |
|
e58c76a…
|
drh
|
2222 |
** any page header/foot or HTML/BODY tag wrappers. |
|
e58c76a…
|
drh
|
2223 |
** -H|--HTML Works like -h|-html but wraps the output in |
|
e58c76a…
|
drh
|
2224 |
** <html><body>...</body></html>. |
|
e58c76a…
|
drh
|
2225 |
** -p|--pre If -h|-H is used and the page or technote has |
|
e58c76a…
|
drh
|
2226 |
** the text/plain mimetype, its HTML-escaped output |
|
e58c76a…
|
drh
|
2227 |
** will be wrapped in <pre>...</pre>. |
|
b23eb83…
|
stephan
|
2228 |
** |
|
5129281…
|
danshearer
|
2229 |
** > fossil wiki (create|commit) (PAGENAME | TECHNOTE-COMMENT) ?FILE? ?OPTIONS? |
|
63220d9…
|
jan.nijtmans
|
2230 |
** |
|
63220d9…
|
jan.nijtmans
|
2231 |
** Create a new or commit changes to an existing wiki page or |
|
05cd9fa…
|
rberteig
|
2232 |
** technote from FILE or from standard input. PAGENAME is the |
|
5129281…
|
danshearer
|
2233 |
** name of the wiki entry. TECHNOTE-COMMENT is the timeline comment of |
|
5129281…
|
danshearer
|
2234 |
** the technote. |
|
045deb2…
|
drh
|
2235 |
** |
|
045deb2…
|
drh
|
2236 |
** Options: |
|
05cd9fa…
|
rberteig
|
2237 |
** -M|--mimetype TEXT-FORMAT The mime type of the update. |
|
05cd9fa…
|
rberteig
|
2238 |
** Defaults to the type used by |
|
05cd9fa…
|
rberteig
|
2239 |
** the previous version of the |
|
05cd9fa…
|
rberteig
|
2240 |
** page, or text/x-fossil-wiki. |
|
05cd9fa…
|
rberteig
|
2241 |
** Valid values are: text/x-fossil-wiki, |
|
19f2753…
|
stephan
|
2242 |
** text/x-markdown and text/plain. fossil, |
|
05cd9fa…
|
rberteig
|
2243 |
** markdown or plain can be specified as |
|
05cd9fa…
|
rberteig
|
2244 |
** synonyms of these values. |
|
05cd9fa…
|
rberteig
|
2245 |
** -t|--technote DATETIME Specifies the timestamp of |
|
05cd9fa…
|
rberteig
|
2246 |
** the technote to be created or |
|
ae70df7…
|
danshearer
|
2247 |
** updated. The timestamp specifies when |
|
ae70df7…
|
danshearer
|
2248 |
** this technote appears in the timeline |
|
ae70df7…
|
danshearer
|
2249 |
** and is its permanent handle although |
|
ae70df7…
|
danshearer
|
2250 |
** it may not be unique. When updating |
|
ae70df7…
|
danshearer
|
2251 |
** a technote the most recently modified |
|
ae70df7…
|
danshearer
|
2252 |
** tech note with the specified timestamp |
|
ae70df7…
|
danshearer
|
2253 |
** will be updated. |
|
05cd9fa…
|
rberteig
|
2254 |
** -t|--technote TECHNOTE-ID Specifies the technote to be |
|
ae70df7…
|
danshearer
|
2255 |
** updated by its technote id, which is |
|
ae70df7…
|
danshearer
|
2256 |
** its UUID. |
|
045deb2…
|
drh
|
2257 |
** --technote-tags TAGS The set of tags for a technote. |
|
05cd9fa…
|
rberteig
|
2258 |
** --technote-bgcolor COLOR The color used for the technote |
|
05cd9fa…
|
rberteig
|
2259 |
** on the timeline. |
|
045deb2…
|
drh
|
2260 |
** |
|
e58c76a…
|
drh
|
2261 |
** > fossil wiki list ?OPTIONS? |
|
e58c76a…
|
drh
|
2262 |
** > fossil wiki ls ?OPTIONS? |
|
045deb2…
|
drh
|
2263 |
** |
|
045deb2…
|
drh
|
2264 |
** Lists all wiki entries, one per line, ordered |
|
4877e77…
|
danield
|
2265 |
** case-insensitively by name. Wiki pages associated with |
|
4877e77…
|
danield
|
2266 |
** check-ins and branches are NOT shown, unless -a is given. |
|
05cd9fa…
|
rberteig
|
2267 |
** |
|
05cd9fa…
|
rberteig
|
2268 |
** Options: |
|
88e5336…
|
stephan
|
2269 |
** --all Include "deleted" pages in output. |
|
88e5336…
|
stephan
|
2270 |
** By default deleted pages are elided. |
|
05cd9fa…
|
rberteig
|
2271 |
** -t|--technote Technotes will be listed instead of |
|
05cd9fa…
|
rberteig
|
2272 |
** pages. The technotes will be in order |
|
05cd9fa…
|
rberteig
|
2273 |
** of timestamp with the most recent |
|
05cd9fa…
|
rberteig
|
2274 |
** first. |
|
4877e77…
|
danield
|
2275 |
** -a|--show-associated Show wiki pages associated with |
|
4877e77…
|
danield
|
2276 |
** check-ins and branches. |
|
05cd9fa…
|
rberteig
|
2277 |
** -s|--show-technote-ids The id of the tech note will be listed |
|
e2bdc10…
|
danield
|
2278 |
** alongside the timestamp. The tech note |
|
05cd9fa…
|
rberteig
|
2279 |
** id will be the first word on each line. |
|
05cd9fa…
|
rberteig
|
2280 |
** This option only applies if the |
|
05cd9fa…
|
rberteig
|
2281 |
** --technote option is also specified. |
|
05cd9fa…
|
rberteig
|
2282 |
** |
|
cd77e06…
|
rberteig
|
2283 |
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in |
|
cd77e06…
|
rberteig
|
2284 |
** year-month-day form, it may be truncated, the "T" may be replaced by |
|
cd77e06…
|
rberteig
|
2285 |
** a space, and it may also name a timezone offset from UTC as "-HH:MM" |
|
cd77e06…
|
rberteig
|
2286 |
** (westward) or "+HH:MM" (eastward). Either no timezone suffix or "Z" |
|
cd77e06…
|
rberteig
|
2287 |
** means UTC. |
|
cd77e06…
|
rberteig
|
2288 |
** |
|
7bc9427…
|
stephan
|
2289 |
** The "Sandbox" wiki pseudo-page is a special case. Its name is |
|
7bc9427…
|
stephan
|
2290 |
** checked case-insensitively and either "create" or "commit" may be |
|
7bc9427…
|
stephan
|
2291 |
** used to update its contents. |
|
cde6e7a…
|
stephan
|
2292 |
*/ |
|
cde6e7a…
|
stephan
|
2293 |
void wiki_cmd(void){ |
|
cde6e7a…
|
stephan
|
2294 |
int n; |
|
7bc9427…
|
stephan
|
2295 |
int isSandbox = 0; /* true if dealing with sandbox pseudo-page */ |
|
88e5336…
|
stephan
|
2296 |
const int showAll = find_option("all", 0, 0)!=0; |
|
7bc9427…
|
stephan
|
2297 |
|
|
c0c3d92…
|
drh
|
2298 |
db_find_and_open_repository(0, 0); |
|
5fb1152…
|
stephan
|
2299 |
if( g.argc<3 ){ |
|
5fb1152…
|
stephan
|
2300 |
goto wiki_cmd_usage; |
|
5fb1152…
|
stephan
|
2301 |
} |
|
5fb1152…
|
stephan
|
2302 |
n = strlen(g.argv[2]); |
|
5fb1152…
|
stephan
|
2303 |
if( n==0 ){ |
|
5fb1152…
|
stephan
|
2304 |
goto wiki_cmd_usage; |
|
5fb1152…
|
stephan
|
2305 |
} |
|
5fb1152…
|
stephan
|
2306 |
|
|
5fb1152…
|
stephan
|
2307 |
if( strncmp(g.argv[2],"export",n)==0 ){ |
|
7bc9427…
|
stephan
|
2308 |
const char *zPageName = 0; /* Name of the wiki page to export */ |
|
4e18dba…
|
jan.nijtmans
|
2309 |
const char *zFile; /* Name of the output file (0=stdout) */ |
|
045deb2…
|
drh
|
2310 |
const char *zETime; /* The name of the technote to export */ |
|
7bc9427…
|
stephan
|
2311 |
int rid = 0; /* Artifact ID of the wiki page */ |
|
d13054c…
|
drh
|
2312 |
int i; /* Loop counter */ |
|
d13054c…
|
drh
|
2313 |
char *zBody = 0; /* Wiki page content */ |
|
b23eb83…
|
stephan
|
2314 |
Blob body = empty_blob; /* Wiki page content */ |
|
d13054c…
|
drh
|
2315 |
Manifest *pWiki = 0; /* Parsed wiki page content */ |
|
b23eb83…
|
stephan
|
2316 |
int fHtml = 0; /* Export in HTML form */ |
|
fe86954…
|
stephan
|
2317 |
FILE * pFile = 0; /* Output file */ |
|
fe86954…
|
stephan
|
2318 |
int fPre = 0; /* Indicates that -h|-H should be |
|
fe86954…
|
stephan
|
2319 |
** wrapped in <pre>...</pre> if pWiki |
|
fe86954…
|
stephan
|
2320 |
** has the text/plain mimetype. */ |
|
fe86954…
|
stephan
|
2321 |
fHtml = find_option("HTML","H",0)!=0 |
|
fe86954…
|
stephan
|
2322 |
? 2 |
|
fe86954…
|
stephan
|
2323 |
: (find_option("html","h",0)!=0 ? 1 : 0) |
|
fe86954…
|
stephan
|
2324 |
/* 1 == -html, 2 == -HTML */; |
|
fe86954…
|
stephan
|
2325 |
fPre = fHtml==0 ? 0 : find_option("pre","p",0)!=0; |
|
045deb2…
|
drh
|
2326 |
zETime = find_option("technote","t",1); |
|
b23eb83…
|
stephan
|
2327 |
verify_all_options(); |
|
045deb2…
|
drh
|
2328 |
if( !zETime ){ |
|
045deb2…
|
drh
|
2329 |
if( (g.argc!=4) && (g.argc!=5) ){ |
|
b23eb83…
|
stephan
|
2330 |
usage("export ?-html? PAGENAME ?FILE?"); |
|
045deb2…
|
drh
|
2331 |
} |
|
045deb2…
|
drh
|
2332 |
zPageName = g.argv[3]; |
|
7bc9427…
|
stephan
|
2333 |
isSandbox = is_sandbox(zPageName); |
|
7bc9427…
|
stephan
|
2334 |
if(isSandbox){ |
|
7bc9427…
|
stephan
|
2335 |
zBody = db_get("sandbox", 0); |
|
7bc9427…
|
stephan
|
2336 |
}else{ |
|
7bc9427…
|
stephan
|
2337 |
wiki_fetch_by_name(zPageName, 0, &rid, &pWiki); |
|
7bc9427…
|
stephan
|
2338 |
if(pWiki){ |
|
7bc9427…
|
stephan
|
2339 |
zBody = pWiki->zWiki; |
|
7bc9427…
|
stephan
|
2340 |
} |
|
045deb2…
|
drh
|
2341 |
} |
|
045deb2…
|
drh
|
2342 |
if( zBody==0 ){ |
|
045deb2…
|
drh
|
2343 |
fossil_fatal("wiki page [%s] not found",zPageName); |
|
045deb2…
|
drh
|
2344 |
} |
|
045deb2…
|
drh
|
2345 |
zFile = (g.argc==4) ? "-" : g.argv[4]; |
|
045deb2…
|
drh
|
2346 |
}else{ |
|
045deb2…
|
drh
|
2347 |
if( (g.argc!=3) && (g.argc!=4) ){ |
|
b23eb83…
|
stephan
|
2348 |
usage("export ?-html? ?FILE? --technote " |
|
b23eb83…
|
stephan
|
2349 |
"DATETIME|TECHNOTE-ID"); |
|
045deb2…
|
drh
|
2350 |
} |
|
05cd9fa…
|
rberteig
|
2351 |
rid = wiki_technote_to_rid(zETime); |
|
d037468…
|
mistachkin
|
2352 |
if ( rid==-1 ){ |
|
05cd9fa…
|
rberteig
|
2353 |
fossil_fatal("ambiguous tech note id: %s", zETime); |
|
05cd9fa…
|
rberteig
|
2354 |
} |
|
045deb2…
|
drh
|
2355 |
if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){ |
|
045deb2…
|
drh
|
2356 |
zBody = pWiki->zWiki; |
|
045deb2…
|
drh
|
2357 |
} |
|
045deb2…
|
drh
|
2358 |
if( zBody==0 ){ |
|
05cd9fa…
|
rberteig
|
2359 |
fossil_fatal("technote [%s] not found",zETime); |
|
045deb2…
|
drh
|
2360 |
} |
|
045deb2…
|
drh
|
2361 |
zFile = (g.argc==3) ? "-" : g.argv[3]; |
|
2fac809…
|
drh
|
2362 |
} |
|
2fac809…
|
drh
|
2363 |
for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){} |
|
11f2f71…
|
drh
|
2364 |
zBody[i] = 0; |
|
11f2f71…
|
drh
|
2365 |
blob_init(&body, zBody, -1); |
|
b23eb83…
|
stephan
|
2366 |
if(fHtml==0){ |
|
b23eb83…
|
stephan
|
2367 |
blob_append(&body, "\n", 1); |
|
b23eb83…
|
stephan
|
2368 |
}else{ |
|
b23eb83…
|
stephan
|
2369 |
Blob html = empty_blob; /* HTML-ized content */ |
|
7bc9427…
|
stephan
|
2370 |
const char * zMimetype = isSandbox |
|
7bc9427…
|
stephan
|
2371 |
? db_get("sandbox-mimetype", "text/x-fossil-wiki") |
|
7bc9427…
|
stephan
|
2372 |
: wiki_filter_mimetypes(pWiki->zMimetype); |
|
b23eb83…
|
stephan
|
2373 |
if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ |
|
b23eb83…
|
stephan
|
2374 |
wiki_convert(&body,&html,0); |
|
b23eb83…
|
stephan
|
2375 |
}else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
|
7bc9427…
|
stephan
|
2376 |
markdown_to_html(&body,0,&html); |
|
89b6dda…
|
drh
|
2377 |
safe_html_context(DOCSRC_WIKI); |
|
89b6dda…
|
drh
|
2378 |
safe_html(&html); |
|
b23eb83…
|
stephan
|
2379 |
}else if( fossil_strcmp(zMimetype, "text/plain")==0 ){ |
|
b23eb83…
|
stephan
|
2380 |
htmlize_to_blob(&html,zBody,i); |
|
b23eb83…
|
stephan
|
2381 |
}else{ |
|
b23eb83…
|
stephan
|
2382 |
fossil_fatal("Unsupported MIME type '%s' for wiki page '%s'.", |
|
7bc9427…
|
stephan
|
2383 |
zMimetype, pWiki ? pWiki->zWikiTitle : zPageName ); |
|
b23eb83…
|
stephan
|
2384 |
} |
|
b23eb83…
|
stephan
|
2385 |
blob_reset(&body); |
|
b23eb83…
|
stephan
|
2386 |
body = html /* transfer memory */; |
|
b23eb83…
|
stephan
|
2387 |
} |
|
fe86954…
|
stephan
|
2388 |
pFile = fossil_fopen_for_output(zFile); |
|
fe86954…
|
stephan
|
2389 |
if(fHtml==2){ |
|
fe86954…
|
stephan
|
2390 |
fwrite("<html><body>", 1, 12, pFile); |
|
fe86954…
|
stephan
|
2391 |
} |
|
fe86954…
|
stephan
|
2392 |
if(fPre!=0){ |
|
fe86954…
|
stephan
|
2393 |
fwrite("<pre>", 1, 5, pFile); |
|
fe86954…
|
stephan
|
2394 |
} |
|
fe86954…
|
stephan
|
2395 |
fwrite(blob_buffer(&body), 1, blob_size(&body), pFile); |
|
fe86954…
|
stephan
|
2396 |
if(fPre!=0){ |
|
fe86954…
|
stephan
|
2397 |
fwrite("</pre>", 1, 6, pFile); |
|
fe86954…
|
stephan
|
2398 |
} |
|
fe86954…
|
stephan
|
2399 |
if(fHtml==2){ |
|
fe86954…
|
stephan
|
2400 |
fwrite("</body></html>\n", 1, 15, pFile); |
|
fe86954…
|
stephan
|
2401 |
} |
|
fe86954…
|
stephan
|
2402 |
fossil_fclose(pFile); |
|
11f2f71…
|
drh
|
2403 |
blob_reset(&body); |
|
d13054c…
|
drh
|
2404 |
manifest_destroy(pWiki); |
|
d13054c…
|
drh
|
2405 |
return; |
|
52d242a…
|
stephan
|
2406 |
}else if( strncmp(g.argv[2],"commit",n)==0 |
|
52d242a…
|
stephan
|
2407 |
|| strncmp(g.argv[2],"create",n)==0 ){ |
|
4e18dba…
|
jan.nijtmans
|
2408 |
const char *zPageName; /* page name */ |
|
52d242a…
|
stephan
|
2409 |
Blob content; /* Input content */ |
|
d037468…
|
mistachkin
|
2410 |
int rid = 0; |
|
52d242a…
|
stephan
|
2411 |
Manifest *pWiki = 0; /* Parsed wiki page content */ |
|
c0de97a…
|
stephan
|
2412 |
const int isCreate = 'r'==g.argv[2][1] /* else "commit" */; |
|
4e18dba…
|
jan.nijtmans
|
2413 |
const char *zMimeType = find_option("mimetype", "M", 1); |
|
045deb2…
|
drh
|
2414 |
const char *zETime = find_option("technote", "t", 1); |
|
045deb2…
|
drh
|
2415 |
const char *zTags = find_option("technote-tags", NULL, 1); |
|
045deb2…
|
drh
|
2416 |
const char *zClr = find_option("technote-bgcolor", NULL, 1); |
|
b23eb83…
|
stephan
|
2417 |
verify_all_options(); |
|
d13054c…
|
drh
|
2418 |
if( g.argc!=4 && g.argc!=5 ){ |
|
045deb2…
|
drh
|
2419 |
usage("commit|create PAGENAME ?FILE? [--mimetype TEXT-FORMAT]" |
|
045deb2…
|
drh
|
2420 |
" [--technote DATETIME] [--technote-tags TAGS]" |
|
045deb2…
|
drh
|
2421 |
" [--technote-bgcolor COLOR]"); |
|
e03d1be…
|
drh
|
2422 |
} |
|
e03d1be…
|
drh
|
2423 |
zPageName = g.argv[3]; |
|
e03d1be…
|
drh
|
2424 |
if( g.argc==4 ){ |
|
e03d1be…
|
drh
|
2425 |
blob_read_from_channel(&content, stdin, -1); |
|
e03d1be…
|
drh
|
2426 |
}else{ |
|
1772357…
|
drh
|
2427 |
blob_read_from_file(&content, g.argv[4], ExtFILE); |
|
1772357…
|
drh
|
2428 |
} |
|
7bc9427…
|
stephan
|
2429 |
isSandbox = is_sandbox(zPageName); |
|
c0de97a…
|
stephan
|
2430 |
if ( !zETime ){ |
|
7bc9427…
|
stephan
|
2431 |
if( !isSandbox ){ |
|
7bc9427…
|
stephan
|
2432 |
wiki_fetch_by_name(zPageName, 0, &rid, &pWiki); |
|
c0de97a…
|
stephan
|
2433 |
} |
|
c0de97a…
|
stephan
|
2434 |
}else{ |
|
c0de97a…
|
stephan
|
2435 |
rid = wiki_technote_to_rid(zETime); |
|
c0de97a…
|
stephan
|
2436 |
if( rid>0 ){ |
|
c0de97a…
|
stephan
|
2437 |
pWiki = manifest_get(rid, CFTYPE_EVENT, 0); |
|
c0de97a…
|
stephan
|
2438 |
} |
|
c0de97a…
|
stephan
|
2439 |
} |
|
d037468…
|
mistachkin
|
2440 |
if( !zMimeType || !*zMimeType ){ |
|
4cb50c4…
|
stephan
|
2441 |
/* Try to deduce the mimetype based on the prior version. */ |
|
7bc9427…
|
stephan
|
2442 |
if(isSandbox){ |
|
7bc9427…
|
stephan
|
2443 |
zMimeType = |
|
7bc9427…
|
stephan
|
2444 |
wiki_filter_mimetypes(db_get("sandbox-mimetype", |
|
7bc9427…
|
stephan
|
2445 |
"text/x-fossil-wiki")); |
|
7bc9427…
|
stephan
|
2446 |
}else if( pWiki!=0 && (pWiki->zMimetype && *pWiki->zMimetype) ){ |
|
c0de97a…
|
stephan
|
2447 |
zMimeType = pWiki->zMimetype; |
|
05cd9fa…
|
rberteig
|
2448 |
} |
|
05cd9fa…
|
rberteig
|
2449 |
}else{ |
|
05cd9fa…
|
rberteig
|
2450 |
zMimeType = wiki_filter_mimetypes(zMimeType); |
|
05cd9fa…
|
rberteig
|
2451 |
} |
|
c0de97a…
|
stephan
|
2452 |
if( isCreate && rid>0 ){ |
|
05cd9fa…
|
rberteig
|
2453 |
if ( !zETime ){ |
|
05cd9fa…
|
rberteig
|
2454 |
fossil_fatal("wiki page %s already exists", zPageName); |
|
05cd9fa…
|
rberteig
|
2455 |
}else{ |
|
05cd9fa…
|
rberteig
|
2456 |
/* Creating a tech note with same timestamp is permitted |
|
05cd9fa…
|
rberteig
|
2457 |
and should create a new tech note */ |
|
63220d9…
|
jan.nijtmans
|
2458 |
rid = 0; |
|
05cd9fa…
|
rberteig
|
2459 |
} |
|
7bc9427…
|
stephan
|
2460 |
}else if( !isCreate && rid==0 && isSandbox==0 ){ |
|
05cd9fa…
|
rberteig
|
2461 |
if ( !zETime ){ |
|
05cd9fa…
|
rberteig
|
2462 |
fossil_fatal("no such wiki page: %s", zPageName); |
|
05cd9fa…
|
rberteig
|
2463 |
}else{ |
|
05cd9fa…
|
rberteig
|
2464 |
fossil_fatal("no such tech note: %s", zETime); |
|
05cd9fa…
|
rberteig
|
2465 |
} |
|
05cd9fa…
|
rberteig
|
2466 |
} |
|
05cd9fa…
|
rberteig
|
2467 |
|
|
05cd9fa…
|
rberteig
|
2468 |
if( !zETime ){ |
|
7bc9427…
|
stephan
|
2469 |
if(isSandbox){ |
|
7bc9427…
|
stephan
|
2470 |
db_set("sandbox",blob_str(&content),0); |
|
7bc9427…
|
stephan
|
2471 |
db_set("sandbox-mimetype",zMimeType,0); |
|
7bc9427…
|
stephan
|
2472 |
fossil_print("Updated sandbox pseudo-page.\n"); |
|
05cd9fa…
|
rberteig
|
2473 |
}else{ |
|
7bc9427…
|
stephan
|
2474 |
wiki_cmd_commit(zPageName, rid, &content, zMimeType, 1); |
|
7bc9427…
|
stephan
|
2475 |
if( g.argv[2][1]=='r' ){ |
|
7bc9427…
|
stephan
|
2476 |
fossil_print("Created new wiki page %s.\n", zPageName); |
|
7bc9427…
|
stephan
|
2477 |
}else{ |
|
7bc9427…
|
stephan
|
2478 |
fossil_print("Updated wiki page %s.\n", zPageName); |
|
7bc9427…
|
stephan
|
2479 |
} |
|
05cd9fa…
|
rberteig
|
2480 |
} |
|
05cd9fa…
|
rberteig
|
2481 |
}else{ |
|
05cd9fa…
|
rberteig
|
2482 |
if( rid != -1 ){ |
|
05cd9fa…
|
rberteig
|
2483 |
char *zMETime; /* Normalized, mutable version of zETime */ |
|
05cd9fa…
|
rberteig
|
2484 |
zMETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", |
|
05cd9fa…
|
rberteig
|
2485 |
zETime); |
|
05cd9fa…
|
rberteig
|
2486 |
event_cmd_commit(zMETime, rid, &content, zMimeType, zPageName, |
|
05cd9fa…
|
rberteig
|
2487 |
zTags, zClr); |
|
05cd9fa…
|
rberteig
|
2488 |
if( g.argv[2][1]=='r' ){ |
|
05cd9fa…
|
rberteig
|
2489 |
fossil_print("Created new tech note %s.\n", zMETime); |
|
05cd9fa…
|
rberteig
|
2490 |
}else{ |
|
05cd9fa…
|
rberteig
|
2491 |
fossil_print("Updated tech note %s.\n", zMETime); |
|
05cd9fa…
|
rberteig
|
2492 |
} |
|
05cd9fa…
|
rberteig
|
2493 |
free(zMETime); |
|
05cd9fa…
|
rberteig
|
2494 |
}else{ |
|
05cd9fa…
|
rberteig
|
2495 |
fossil_fatal("ambiguous tech note id: %s", zETime); |
|
05cd9fa…
|
rberteig
|
2496 |
} |
|
e03d1be…
|
drh
|
2497 |
} |
|
52d242a…
|
stephan
|
2498 |
manifest_destroy(pWiki); |
|
e03d1be…
|
drh
|
2499 |
blob_reset(&content); |
|
52d242a…
|
stephan
|
2500 |
}else if( strncmp(g.argv[2],"delete",n)==0 ){ |
|
b23eb83…
|
stephan
|
2501 |
if( g.argc!=4 ){ |
|
decac09…
|
drh
|
2502 |
usage("delete PAGENAME"); |
|
decac09…
|
drh
|
2503 |
} |
|
decac09…
|
drh
|
2504 |
fossil_fatal("delete not yet implemented."); |
|
6714c94…
|
drh
|
2505 |
}else if(( strncmp(g.argv[2],"list",n)==0 ) |
|
6714c94…
|
drh
|
2506 |
|| ( strncmp(g.argv[2],"ls",n)==0 )){ |
|
decac09…
|
drh
|
2507 |
Stmt q; |
|
b23eb83…
|
stephan
|
2508 |
const int fTechnote = find_option("technote","t",0)!=0; |
|
b23eb83…
|
stephan
|
2509 |
const int showIds = find_option("show-technote-ids","s",0)!=0; |
|
4877e77…
|
danield
|
2510 |
const int showCkBr = find_option("show-associated","a",0)!=0; |
|
b23eb83…
|
stephan
|
2511 |
verify_all_options(); |
|
b23eb83…
|
stephan
|
2512 |
if (fTechnote==0){ |
|
88e5336…
|
stephan
|
2513 |
db_prepare(&q, listAllWikiPages/*works-like:""*/); |
|
045deb2…
|
drh
|
2514 |
}else{ |
|
045deb2…
|
drh
|
2515 |
db_prepare(&q, |
|
3a2b13b…
|
stephan
|
2516 |
"SELECT datetime(e.mtime), substr(t.tagname,7), e.objid" |
|
05cd9fa…
|
rberteig
|
2517 |
" FROM event e, tag t" |
|
05cd9fa…
|
rberteig
|
2518 |
" WHERE e.type='e'" |
|
05cd9fa…
|
rberteig
|
2519 |
" AND e.tagid IS NOT NULL" |
|
05cd9fa…
|
rberteig
|
2520 |
" AND t.tagid=e.tagid" |
|
05cd9fa…
|
rberteig
|
2521 |
" ORDER BY e.mtime DESC /*sort*/" |
|
045deb2…
|
drh
|
2522 |
); |
|
045deb2…
|
drh
|
2523 |
} |
|
decac09…
|
drh
|
2524 |
while( db_step(&q)==SQLITE_ROW ){ |
|
decac09…
|
drh
|
2525 |
const char *zName = db_column_text(&q, 0); |
|
88e5336…
|
stephan
|
2526 |
const int wrid = db_column_int(&q, 2); |
|
88e5336…
|
stephan
|
2527 |
if(!showAll && !wrid){ |
|
4877e77…
|
danield
|
2528 |
continue; |
|
4877e77…
|
danield
|
2529 |
} |
|
275da70…
|
danield
|
2530 |
if( !showCkBr && |
|
3c2aba7…
|
george
|
2531 |
(has_prefix("checkin/", zName) || |
|
3c2aba7…
|
george
|
2532 |
has_prefix("branch/", zName) || |
|
3c2aba7…
|
george
|
2533 |
has_prefix("tag/", zName) || |
|
3c2aba7…
|
george
|
2534 |
has_prefix("ticket/", zName) ) ){ |
|
88e5336…
|
stephan
|
2535 |
continue; |
|
88e5336…
|
stephan
|
2536 |
} |
|
d037468…
|
mistachkin
|
2537 |
if( showIds ){ |
|
05cd9fa…
|
rberteig
|
2538 |
const char *zUuid = db_column_text(&q, 1); |
|
05cd9fa…
|
rberteig
|
2539 |
fossil_print("%s ",zUuid); |
|
05cd9fa…
|
rberteig
|
2540 |
} |
|
d8ec765…
|
drh
|
2541 |
fossil_print( "%s\n",zName); |
|
decac09…
|
drh
|
2542 |
} |
|
decac09…
|
drh
|
2543 |
db_finalize(&q); |
|
52d242a…
|
stephan
|
2544 |
}else{ |
|
5fb1152…
|
stephan
|
2545 |
goto wiki_cmd_usage; |
|
5fb1152…
|
stephan
|
2546 |
} |
|
5fb1152…
|
stephan
|
2547 |
return; |
|
5fb1152…
|
stephan
|
2548 |
|
|
5fb1152…
|
stephan
|
2549 |
wiki_cmd_usage: |
|
92df474…
|
stephan
|
2550 |
usage("export|create|commit|list ..."); |
|
67f5df3…
|
mgagnon
|
2551 |
} |
|
67f5df3…
|
mgagnon
|
2552 |
|
|
67f5df3…
|
mgagnon
|
2553 |
/* |
|
5602385…
|
drh
|
2554 |
** Allowed flags for wiki_render_associated |
|
5602385…
|
drh
|
2555 |
*/ |
|
5602385…
|
drh
|
2556 |
#if INTERFACE |
|
5602385…
|
drh
|
2557 |
#define WIKIASSOC_FULL_TITLE 0x00001 /* Full title */ |
|
867fe0e…
|
drh
|
2558 |
#define WIKIASSOC_MENU_READ 0x00002 /* Add submenu link to read wiki */ |
|
867fe0e…
|
drh
|
2559 |
#define WIKIASSOC_MENU_WRITE 0x00004 /* Add submenu link to add wiki */ |
|
867fe0e…
|
drh
|
2560 |
#define WIKIASSOC_ALL 0x00007 /* All of the above */ |
|
5602385…
|
drh
|
2561 |
#endif |
|
5602385…
|
drh
|
2562 |
|
|
5602385…
|
drh
|
2563 |
/* |
|
5602385…
|
drh
|
2564 |
** Show the default Section label for an associated wiki page. |
|
5602385…
|
drh
|
2565 |
*/ |
|
5602385…
|
drh
|
2566 |
static void wiki_section_label( |
|
5602385…
|
drh
|
2567 |
const char *zPrefix, /* "branch", "tag", or "checkin" */ |
|
5602385…
|
drh
|
2568 |
const char *zName, /* Name of the object */ |
|
5602385…
|
drh
|
2569 |
unsigned int mFlags /* Zero or more WIKIASSOC_* flags */ |
|
5602385…
|
drh
|
2570 |
){ |
|
5602385…
|
drh
|
2571 |
if( (mFlags & WIKIASSOC_FULL_TITLE)==0 ){ |
|
22d2854…
|
drh
|
2572 |
@ <div class="section accordion">About</div> |
|
5602385…
|
drh
|
2573 |
}else if( zPrefix[0]=='c' ){ /* checkin/... */ |
|
bc36fdc…
|
danield
|
2574 |
@ <div class="section accordion">About check-in %.20h(zName)</div> |
|
5602385…
|
drh
|
2575 |
}else{ |
|
22d2854…
|
drh
|
2576 |
@ <div class="section accordion">About %s(zPrefix) %h(zName)</div> |
|
5602385…
|
drh
|
2577 |
} |
|
5602385…
|
drh
|
2578 |
} |
|
5602385…
|
drh
|
2579 |
|
|
5602385…
|
drh
|
2580 |
/* |
|
867fe0e…
|
drh
|
2581 |
** Add an "Wiki" button in a submenu that links to the read-wiki page. |
|
5602385…
|
drh
|
2582 |
*/ |
|
25f43cc…
|
stephan
|
2583 |
static void wiki_submenu_to_read_wiki( |
|
5602385…
|
drh
|
2584 |
const char *zPrefix, /* "branch", "tag", or "checkin" */ |
|
5602385…
|
drh
|
2585 |
const char *zName, /* Name of the object */ |
|
5602385…
|
drh
|
2586 |
unsigned int mFlags /* Zero or more WIKIASSOC_* flags */ |
|
5602385…
|
drh
|
2587 |
){ |
|
f15b634…
|
stephan
|
2588 |
if( g.perm.RdWiki && (mFlags & WIKIASSOC_MENU_READ)!=0 |
|
f15b634…
|
stephan
|
2589 |
&& 0!=fossil_strcmp("branch", zPrefix) |
|
f15b634…
|
stephan
|
2590 |
/* ^^^ https://fossil-scm.org/forum/forumpost/ff453de2f30791dd */ |
|
f15b634…
|
stephan
|
2591 |
){ |
|
25f43cc…
|
stephan
|
2592 |
style_submenu_element("Wiki", "%R/wiki?name=%s/%t", zPrefix, zName); |
|
25f43cc…
|
stephan
|
2593 |
} |
|
25f43cc…
|
stephan
|
2594 |
} |
|
25f43cc…
|
stephan
|
2595 |
|
|
25f43cc…
|
stephan
|
2596 |
/* |
|
25f43cc…
|
stephan
|
2597 |
** Add an "Edit Wiki" button in a submenu that links to the edit-wiki page. |
|
25f43cc…
|
stephan
|
2598 |
*/ |
|
25f43cc…
|
stephan
|
2599 |
static void wiki_submenu_to_edit_wiki( |
|
25f43cc…
|
stephan
|
2600 |
const char *zPrefix, /* "branch", "tag", or "checkin" */ |
|
25f43cc…
|
stephan
|
2601 |
const char *zName, /* Name of the object */ |
|
25f43cc…
|
stephan
|
2602 |
unsigned int mFlags /* Zero or more WIKIASSOC_* flags */ |
|
25f43cc…
|
stephan
|
2603 |
){ |
|
25f43cc…
|
stephan
|
2604 |
if( g.perm.WrWiki && (mFlags & WIKIASSOC_MENU_WRITE)!=0 ){ |
|
25f43cc…
|
stephan
|
2605 |
style_submenu_element("Edit Wiki", "%R/wikiedit?name=%s/%t", zPrefix, zName); |
|
5602385…
|
drh
|
2606 |
} |
|
5602385…
|
drh
|
2607 |
} |
|
5602385…
|
drh
|
2608 |
|
|
5602385…
|
drh
|
2609 |
/* |
|
5602385…
|
drh
|
2610 |
** Check to see if there exists a wiki page with a name zPrefix/zName. |
|
5602385…
|
drh
|
2611 |
** If there is, then render a <div class='section'>..</div> and |
|
5602385…
|
drh
|
2612 |
** return true. |
|
5602385…
|
drh
|
2613 |
** |
|
5602385…
|
drh
|
2614 |
** If there is no such wiki page, return false. |
|
5602385…
|
drh
|
2615 |
*/ |
|
5602385…
|
drh
|
2616 |
int wiki_render_associated( |
|
25f43cc…
|
stephan
|
2617 |
const char *zPrefix, /* "branch", "tag", "ticket", or "checkin" */ |
|
5602385…
|
drh
|
2618 |
const char *zName, /* Name of the object */ |
|
5602385…
|
drh
|
2619 |
unsigned int mFlags /* Zero or more WIKIASSOC_* flags */ |
|
5602385…
|
drh
|
2620 |
){ |
|
5602385…
|
drh
|
2621 |
int rid; |
|
5602385…
|
drh
|
2622 |
Manifest *pWiki; |
|
5602385…
|
drh
|
2623 |
if( !db_get_boolean("wiki-about",1) ) return 0; |
|
5602385…
|
drh
|
2624 |
rid = db_int(0, |
|
5602385…
|
drh
|
2625 |
"SELECT rid FROM tagxref" |
|
5602385…
|
drh
|
2626 |
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname='wiki-%q/%q')" |
|
5602385…
|
drh
|
2627 |
" ORDER BY mtime DESC LIMIT 1", |
|
5602385…
|
drh
|
2628 |
zPrefix, zName |
|
5602385…
|
drh
|
2629 |
); |
|
fb6b093…
|
andygoth
|
2630 |
pWiki = rid==0 ? 0 : manifest_get(rid, CFTYPE_WIKI, 0); |
|
fb6b093…
|
andygoth
|
2631 |
if( pWiki==0 || pWiki->zWiki==0 || pWiki->zWiki[0]==0 ){ |
|
867fe0e…
|
drh
|
2632 |
if( g.perm.WrWiki && g.perm.Write && (mFlags & WIKIASSOC_MENU_WRITE)!=0 ){ |
|
867fe0e…
|
drh
|
2633 |
style_submenu_element("Add Wiki", "%R/wikiedit?name=%s/%t", |
|
867fe0e…
|
drh
|
2634 |
zPrefix, zName); |
|
867fe0e…
|
drh
|
2635 |
} |
|
fb6b093…
|
andygoth
|
2636 |
return 0; |
|
867fe0e…
|
drh
|
2637 |
} |
|
5602385…
|
drh
|
2638 |
if( fossil_strcmp(pWiki->zMimetype, "text/x-markdown")==0 ){ |
|
5602385…
|
drh
|
2639 |
Blob tail = BLOB_INITIALIZER; |
|
5602385…
|
drh
|
2640 |
Blob title = BLOB_INITIALIZER; |
|
5602385…
|
drh
|
2641 |
Blob markdown; |
|
5602385…
|
drh
|
2642 |
blob_init(&markdown, pWiki->zWiki, -1); |
|
5602385…
|
drh
|
2643 |
markdown_to_html(&markdown, &title, &tail); |
|
5602385…
|
drh
|
2644 |
if( blob_size(&title) ){ |
|
b1c8e79…
|
drh
|
2645 |
@ <div class="section accordion">%h(blob_str(&title))</div> |
|
5602385…
|
drh
|
2646 |
}else{ |
|
5602385…
|
drh
|
2647 |
wiki_section_label(zPrefix, zName, mFlags); |
|
5602385…
|
drh
|
2648 |
} |
|
25f43cc…
|
stephan
|
2649 |
wiki_submenu_to_read_wiki(zPrefix, zName, mFlags); |
|
093943d…
|
drh
|
2650 |
wiki_submenu_to_edit_wiki(zPrefix, zName, mFlags); |
|
22d2854…
|
drh
|
2651 |
@ <div class="accordion_panel"> |
|
89b6dda…
|
drh
|
2652 |
safe_html_context(DOCSRC_WIKI); |
|
89b6dda…
|
drh
|
2653 |
safe_html(&tail); |
|
5602385…
|
drh
|
2654 |
convert_href_and_output(&tail); |
|
22d2854…
|
drh
|
2655 |
@ </div> |
|
5602385…
|
drh
|
2656 |
blob_reset(&tail); |
|
5602385…
|
drh
|
2657 |
blob_reset(&title); |
|
5602385…
|
drh
|
2658 |
blob_reset(&markdown); |
|
5602385…
|
drh
|
2659 |
}else if( fossil_strcmp(pWiki->zMimetype, "text/plain")==0 ){ |
|
5602385…
|
drh
|
2660 |
wiki_section_label(zPrefix, zName, mFlags); |
|
093943d…
|
drh
|
2661 |
wiki_submenu_to_edit_wiki(zPrefix, zName, mFlags); |
|
22d2854…
|
drh
|
2662 |
@ <div class="accordion_panel"><pre> |
|
5602385…
|
drh
|
2663 |
@ %h(pWiki->zWiki) |
|
22d2854…
|
drh
|
2664 |
@ </pre></div> |
|
5602385…
|
drh
|
2665 |
}else{ |
|
5602385…
|
drh
|
2666 |
Blob tail = BLOB_INITIALIZER; |
|
5602385…
|
drh
|
2667 |
Blob title = BLOB_INITIALIZER; |
|
5602385…
|
drh
|
2668 |
Blob wiki; |
|
5602385…
|
drh
|
2669 |
Blob *pBody; |
|
5602385…
|
drh
|
2670 |
blob_init(&wiki, pWiki->zWiki, -1); |
|
5602385…
|
drh
|
2671 |
if( wiki_find_title(&wiki, &title, &tail) ){ |
|
22d2854…
|
drh
|
2672 |
@ <div class="section accordion">%h(blob_str(&title))</div> |
|
5602385…
|
drh
|
2673 |
pBody = &tail; |
|
5602385…
|
drh
|
2674 |
}else{ |
|
5602385…
|
drh
|
2675 |
wiki_section_label(zPrefix, zName, mFlags); |
|
5602385…
|
drh
|
2676 |
pBody = &wiki; |
|
5602385…
|
drh
|
2677 |
} |
|
093943d…
|
drh
|
2678 |
wiki_submenu_to_edit_wiki(zPrefix, zName, mFlags); |
|
22d2854…
|
drh
|
2679 |
@ <div class="accordion_panel"><div class="wiki"> |
|
5602385…
|
drh
|
2680 |
wiki_convert(pBody, 0, WIKI_BUTTONS); |
|
22d2854…
|
drh
|
2681 |
@ </div></div> |
|
5602385…
|
drh
|
2682 |
blob_reset(&tail); |
|
5602385…
|
drh
|
2683 |
blob_reset(&title); |
|
5602385…
|
drh
|
2684 |
blob_reset(&wiki); |
|
5602385…
|
drh
|
2685 |
} |
|
5602385…
|
drh
|
2686 |
manifest_destroy(pWiki); |
|
036a9d5…
|
drh
|
2687 |
builtin_request_js("accordion.js"); |
|
5602385…
|
drh
|
2688 |
return 1; |
|
decac09…
|
drh
|
2689 |
} |