Fossil

Check-in [24e015de71]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add the "default-skin" setting which defines which built-in skin to use if no skin is otherwise specified. On the /skins page, show how the current skin is selected, if that is relevant. Add the /fdscookie page that shows just the "fossil_display_settings" cookie rather than all cookies.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 24e015de71cfdc79789b5cdbad2675f3133ca932f7b99499ac9198625df04d5a
User & Date: drh 2024-02-23 15:24:53.973
References
2024-02-23
17:30
Remove the "default-skin" setting that was added by [24e015de71cfdc79]. check-in: 1975bfd279 user: drh tags: trunk
Context
2024-02-23
17:25
Improvements to the Skin setup interface. check-in: 33cc83ffb8 user: drh tags: trunk
15:24
Add the "default-skin" setting which defines which built-in skin to use if no skin is otherwise specified. On the /skins page, show how the current skin is selected, if that is relevant. Add the /fdscookie page that shows just the "fossil_display_settings" cookie rather than all cookies. check-in: 24e015de71 user: drh tags: trunk
01:56
Extended [b272004b] to cover the "classic" timeline mode as well. check-in: ef4a1e817f user: wyoung tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/cgi.c.
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
  char * z = (char*)P("QUERY_STRING");
  if( z ){
    ++rc;
    z = fossil_strdup(z);
    add_param_list(z, '&');
    z = (char*)P("skin");
    if( z ){
      char *zErr = skin_use_alternative(z, 2);
      ++rc;
      if( !zErr && P("once")==0 ){
        cookie_write_parameter("skin","skin",z);
        /* Per /chat discussion, passing ?skin=... without "once"
        ** implies the "udc" argument, so we force that into the
        ** environment here. */
        cgi_set_parameter_nocopy("udc", "1", 1);







|







1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
  char * z = (char*)P("QUERY_STRING");
  if( z ){
    ++rc;
    z = fossil_strdup(z);
    add_param_list(z, '&');
    z = (char*)P("skin");
    if( z ){
      char *zErr = skin_use_alternative(z, 2, SKIN_FROM_QPARAM);
      ++rc;
      if( !zErr && P("once")==0 ){
        cookie_write_parameter("skin","skin",z);
        /* Per /chat discussion, passing ?skin=... without "once"
        ** implies the "udc" argument, so we force that into the
        ** environment here. */
        cgi_set_parameter_nocopy("udc", "1", 1);
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
#endif
  z = (char*)P("HTTP_COOKIE");
  if( z ){
    z = fossil_strdup(z);
    add_param_list(z, ';');
    z = (char*)cookie_value("skin",0);
    if(z){
      skin_use_alternative(z, 2);
    }
  }

  cgi_setup_query_string();

  z = (char*)P("REMOTE_ADDR");
  if( z ){







|







1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
#endif
  z = (char*)P("HTTP_COOKIE");
  if( z ){
    z = fossil_strdup(z);
    add_param_list(z, ';');
    z = (char*)cookie_value("skin",0);
    if(z){
      skin_use_alternative(z, 2, SKIN_FROM_COOKIE);
    }
  }

  cgi_setup_query_string();

  z = (char*)P("REMOTE_ADDR");
  if( z ){
Changes to src/cookies.c.
211
212
213
214
215
216
217






218
219
220
221
222
223
224
225
226
227
228

229



230

231
232
233
234
235
236

237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257


258



259
260
261
  assert( zPName!=0 );
  cookie_parse();
  for(i=0; i<cookies.nParam && strcmp(zPName,cookies.aParam[i].zPName); i++){}
  return i<cookies.nParam ? cookies.aParam[i].zPValue : zDefault;
}

/*






** WEBPAGE:  cookies
**
** Show the current display settings contained in the
** "fossil_display_settings" cookie.
*/
void cookie_page(void){
  int i;
  int nCookie = 0;
  const char *zName = 0;
  const char *zValue = 0;
  int isQP = 0;

  cookie_parse();



  style_header("Cookies");

  @ <form method="POST">
  @ <ol>
  for(i=0; cgi_param_info(i, &zName, &zValue, &isQP); i++){
    char *zDel;
    if( isQP ) continue;
    if( fossil_isupper(zName[0]) ) continue;

    zDel = mprintf("del%s",zName);
    if( P(zDel)!=0 ){
      cgi_set_cookie(zName, "", 0, -1);
      cgi_redirect("cookies");
    }
    nCookie++;
    @ <li><p><b>%h(zName)</b>: %h(zValue)
    @ <input type="submit" name="%h(zDel)" value="Delete">
    if( fossil_strcmp(zName, DISPLAY_SETTINGS_COOKIE)==0  && cookies.nParam>0 ){
      int j;
      @ <ul>
      for(j=0; j<cookies.nParam; j++){
        @ <li>%h(cookies.aParam[j].zPName): "%h(cookies.aParam[j].zPValue)"
      }
      @ </ul>
    }
    fossil_free(zDel);
  }
  @ </ol>
  @ </form>
  if( nCookie==0 ){


    @ <p><i>No cookies for this website</i></p>



  }
  style_finish_page();
}







>
>
>
>
>
>
|










>

>
>
>
|
>






>



|

















>
>
|
>
>
>



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
  assert( zPName!=0 );
  cookie_parse();
  for(i=0; i<cookies.nParam && strcmp(zPName,cookies.aParam[i].zPName); i++){}
  return i<cookies.nParam ? cookies.aParam[i].zPValue : zDefault;
}

/*
** WEBPAGE: cookies
**
** Show all cookies associated with Fossil.  This shows the text of the
** login cookie and is hence dangerous if an adversary is looking over
** your shoulder and is able to read and reproduce that cookie.
**
** WEBPAGE: fdscookie
**
** Show the current display settings contained in the
** "fossil_display_settings" cookie.
*/
void cookie_page(void){
  int i;
  int nCookie = 0;
  const char *zName = 0;
  const char *zValue = 0;
  int isQP = 0;
  int bFDSonly = strstr(g.zPath, "fdscookie")!=0;
  cookie_parse();
  if( bFDSonly ){
    style_header("Display Preferences Cookie");
  }else{
    style_header("All Cookies");
  }
  @ <form method="POST">
  @ <ol>
  for(i=0; cgi_param_info(i, &zName, &zValue, &isQP); i++){
    char *zDel;
    if( isQP ) continue;
    if( fossil_isupper(zName[0]) ) continue;
    if( bFDSonly && strcmp(zName, "fossil_display_settings")!=0 ) continue;
    zDel = mprintf("del%s",zName);
    if( P(zDel)!=0 ){
      cgi_set_cookie(zName, "", 0, -1);
      cgi_redirect(g.zPath);
    }
    nCookie++;
    @ <li><p><b>%h(zName)</b>: %h(zValue)
    @ <input type="submit" name="%h(zDel)" value="Delete">
    if( fossil_strcmp(zName, DISPLAY_SETTINGS_COOKIE)==0  && cookies.nParam>0 ){
      int j;
      @ <ul>
      for(j=0; j<cookies.nParam; j++){
        @ <li>%h(cookies.aParam[j].zPName): "%h(cookies.aParam[j].zPValue)"
      }
      @ </ul>
    }
    fossil_free(zDel);
  }
  @ </ol>
  @ </form>
  if( nCookie==0 ){
    if( bFDSonly ){
      @ <p><i>Your browser is not holding a "fossil_display_setting" cookie
      @ for this website</i></p>
    }else{
      @ <p><i>Your browser is not holding any cookies for this website</i></p>
    }
  }
  style_finish_page();
}
Changes to src/main.c.
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
      ** the elements of the built-in skin.  If LABEL does not match,
      ** this directive is a silent no-op. It may alternately be
      ** an absolute path to a directory which holds skin definition
      ** files (header.txt, footer.txt, etc.). If LABEL is empty,
      ** the skin stored in the CONFIG db table is used.
      */
      blob_token(&line, &value);
      fossil_free(skin_use_alternative(blob_str(&value), 1));
      blob_reset(&value);
      continue;
    }
    if( blob_eq(&key, "jsmode:") && blob_token(&line, &value) ){
      /* jsmode: MODE
      **
      ** Change how JavaScript resources are delivered with each HTML







|







2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
      ** the elements of the built-in skin.  If LABEL does not match,
      ** this directive is a silent no-op. It may alternately be
      ** an absolute path to a directory which holds skin definition
      ** files (header.txt, footer.txt, etc.). If LABEL is empty,
      ** the skin stored in the CONFIG db table is used.
      */
      blob_token(&line, &value);
      fossil_free(skin_use_alternative(blob_str(&value), 1, SKIN_FROM_CGI));
      blob_reset(&value);
      continue;
    }
    if( blob_eq(&key, "jsmode:") && blob_token(&line, &value) ){
      /* jsmode: MODE
      **
      ** Change how JavaScript resources are delivered with each HTML
Changes to src/skins.c.
17
18
19
20
21
22
23







24
25
26
27
28
29
30
**
** Implementation of the Setup page for "skins".
*/
#include "config.h"
#include <assert.h>
#include "skins.h"








/*
** An array of available built-in skins.
**
** To add new built-in skins:
**
**    1.  Pick a name for the new skin.  (Here we use "xyzzy").
**







>
>
>
>
>
>
>







17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
**
** Implementation of the Setup page for "skins".
*/
#include "config.h"
#include <assert.h>
#include "skins.h"

/*
** SETTING: default-skin width=16
**
** If the text value if this setting is the name of a built-in skin
** then the named skin becomes the default skin for the repository.
*/

/*
** An array of available built-in skins.
**
** To add new built-in skins:
**
**    1.  Pick a name for the new skin.  (Here we use "xyzzy").
**
74
75
76
77
78
79
80
81

















82
83
84
85
86
87
88
static char *zAltSkinDir = 0;
static int iDraftSkin = 0;
/*
** Used by skin_use_alternative() to store the current skin rank skin
** so that the /skins page can, if warranted, warn the user that skin
** changes won't have any effect.
*/
static int nSkinRank = 5;


















/*
** Skin details are a set of key/value pairs that define display
** attributes of the skin that cannot be easily specified using CSS
** or that need to be known on the server-side.
**
** The following array holds the value for all known skin details.







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
static char *zAltSkinDir = 0;
static int iDraftSkin = 0;
/*
** Used by skin_use_alternative() to store the current skin rank skin
** so that the /skins page can, if warranted, warn the user that skin
** changes won't have any effect.
*/
static int nSkinRank = 6;

/*
** How the specific skin being used was chosen
*/
#if INTERFACE
#define SKIN_FROM_DRAFT     0   /* The "draftN" prefix on the PATH_INFO */
#define SKIN_FROM_CMDLINE   1   /* --skin option to server command-line */
#define SKIN_FROM_CGI       2   /* skin: parameter in CGI script */
#define SKIN_FROM_QPARAM    3   /* skin= query parameter */
#define SKIN_FROM_COOKIE    4   /* skin= from fossil_display_settings cookie*/
#define SKIN_FROM_SETTING   5   /* Built-in named by "default-skin" setting */
#define SKIN_FROM_CUSTOM    6   /* Skin values in CONFIG table */
#define SKIN_FROM_DEFAULT   7   /* The built-in named "default" */
#define SKIN_FROM_UNKNOWN   8   /* Do not yet know which skin to use */
#endif /* INTERFACE */
static int iSkinSource = SKIN_FROM_UNKNOWN;


/*
** Skin details are a set of key/value pairs that define display
** attributes of the skin that cannot be easily specified using CSS
** or that need to be known on the server-side.
**
** The following array holds the value for all known skin details.
123
124
125
126
127
128
129
130
131
132

133
134

135
136
137
138
139
140




141
142

143

144
145
146
147
148
149
150
151
152
153
154
155

156
157
158
159

160
161
162
163
164
165
166
167
168
169

170
171
172
173
174
175
176
177
178
179
180
181
182
183



184
185
186
187
188
189
190
191
192
193
194
195
196
197

198
199
200
201
202
203
204
** preferred ranking, making it otherwise more invasive to tell the
** internals "the --skin flag ranks higher than a URL parameter" (the
** former gets initialized before both URL parameters and the /draft
** path determination).
**
** The rankings were initially defined in
** https://fossil-scm.org/forum/forumpost/caf8c9a8bb
** and are:
**
** 0) A skin name matching the glob draft[1-9] trumps everything else.

**
** 1) The --skin flag or skin: CGI config setting.

**
** 2) The "skin" display setting cookie or URL argument, in that
** order. If the "skin" URL argument is provided and refers to a legal
** skin then that will update the display cookie. If the skin name is
** illegal it is silently ignored.
**




** 3) Skin properties from the CONFIG db table
**

** 4) Default skin.

**
** As a special case, a NULL or empty name resets zAltSkinDir and
** pAltSkin to 0 to indicate that the current config-side skin should
** be used (rank 3, above), then returns 0.
*/
char *skin_use_alternative(const char *zName, int rank){
  int i;
  Blob err = BLOB_INITIALIZER;
  if(rank > nSkinRank) return 0;
  nSkinRank = rank;
  if( zName && 1==rank && strchr(zName, '/')!=0 ){
    zAltSkinDir = fossil_strdup(zName);

    return 0;
  }
  if( zName && sqlite3_strglob("draft[1-9]", zName)==0 ){
    skin_use_draft(zName[5] - '0');

    return 0;
  }
  if(!zName || !*zName){
    pAltSkin = 0;
    zAltSkinDir = 0;
    return 0;
  }
  for(i=0; i<count(aBuiltinSkin); i++){
    if( fossil_strcmp(aBuiltinSkin[i].zLabel, zName)==0 ){
      pAltSkin = &aBuiltinSkin[i];

      return 0;
    }
  }
  blob_appendf(&err, "available skins: %s", aBuiltinSkin[0].zLabel);
  for(i=1; i<count(aBuiltinSkin); i++){
    blob_append(&err, " ", 1);
    blob_append(&err, aBuiltinSkin[i].zLabel, -1);
  }
  return blob_str(&err);
}

/*
** Look for the --skin command-line option and process it.  Or
** call fossil_fatal() if an unknown skin is specified.



*/
void skin_override(void){
  const char *zSkin = find_option("skin",0,1);
  if( zSkin ){
    char *zErr = skin_use_alternative(zSkin, 1);
    if( zErr ) fossil_fatal("%s", zErr);
  }
}

/*
** Use one of the draft skins.
*/
void skin_use_draft(int i){
  iDraftSkin = i;

}

/*
** The following routines return the various components of the skin
** that should be used for the current run.
**
** zWhat is one of:  "css", "header", "footer", "details", "js"







|

|
>

|
>


|
|
|

>
>
>
>
|

>
|
>





|






>




>










>














>
>
>




|









>







147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
** preferred ranking, making it otherwise more invasive to tell the
** internals "the --skin flag ranks higher than a URL parameter" (the
** former gets initialized before both URL parameters and the /draft
** path determination).
**
** The rankings were initially defined in
** https://fossil-scm.org/forum/forumpost/caf8c9a8bb
** but where subsequently revised:
**
** 0) A skin name matching the glob pattern "draft[1-9]" at the start of
**    the PATH_INFO.
**
** 1) The --skin flag for commands like "fossil ui", "fossil server", or
**    "fossil http", or  the "skin:" CGI config setting.
**
** 2) The "skin" display setting cookie or URL argument, in that
**    order. If the "skin" URL argument is provided and refers to a legal
**    skin then that will update the display cookie. If the skin name is
**    illegal it is silently ignored.
**
** 3) The built-in skin identfied by the "default-skin" setting, if such
**    a setting exists and matches one of the built-in skin names.
**
** 4) Skin properties (settings "css", "details", "footer", "header",
**    and "js") from the CONFIG db table
**
** 5) The built-in skin named "default"
**
** The iSource integer privides additional detail about where the skin
**
** As a special case, a NULL or empty name resets zAltSkinDir and
** pAltSkin to 0 to indicate that the current config-side skin should
** be used (rank 3, above), then returns 0.
*/
char *skin_use_alternative(const char *zName, int rank, int iSource){
  int i;
  Blob err = BLOB_INITIALIZER;
  if(rank > nSkinRank) return 0;
  nSkinRank = rank;
  if( zName && 1==rank && strchr(zName, '/')!=0 ){
    zAltSkinDir = fossil_strdup(zName);
    iSkinSource = iSource;
    return 0;
  }
  if( zName && sqlite3_strglob("draft[1-9]", zName)==0 ){
    skin_use_draft(zName[5] - '0');
    iSkinSource = iSource;
    return 0;
  }
  if(!zName || !*zName){
    pAltSkin = 0;
    zAltSkinDir = 0;
    return 0;
  }
  for(i=0; i<count(aBuiltinSkin); i++){
    if( fossil_strcmp(aBuiltinSkin[i].zLabel, zName)==0 ){
      pAltSkin = &aBuiltinSkin[i];
      iSkinSource = iSource;
      return 0;
    }
  }
  blob_appendf(&err, "available skins: %s", aBuiltinSkin[0].zLabel);
  for(i=1; i<count(aBuiltinSkin); i++){
    blob_append(&err, " ", 1);
    blob_append(&err, aBuiltinSkin[i].zLabel, -1);
  }
  return blob_str(&err);
}

/*
** Look for the --skin command-line option and process it.  Or
** call fossil_fatal() if an unknown skin is specified.
**
** This routine is called during command-line parsing for commands
** like "fossil ui" and "fossil http".
*/
void skin_override(void){
  const char *zSkin = find_option("skin",0,1);
  if( zSkin ){
    char *zErr = skin_use_alternative(zSkin, 1, SKIN_FROM_CMDLINE);
    if( zErr ) fossil_fatal("%s", zErr);
  }
}

/*
** Use one of the draft skins.
*/
void skin_use_draft(int i){
  iDraftSkin = i;
  iSkinSource = SKIN_FROM_DRAFT;
}

/*
** The following routines return the various components of the skin
** that should be used for the current run.
**
** zWhat is one of:  "css", "header", "footer", "details", "js"
217
218
219
220
221
222
223














224
225
226
227
228
229
230
231
232
233
234


235
236
237
238
239
240
241
    if( file_isfile(z, ExtFILE) ){
      Blob x;
      blob_read_from_file(&x, z, ExtFILE);
      fossil_free(z);
      return blob_str(&x);
    }
    fossil_free(z);














  }
  if( pAltSkin ){
    z = mprintf("skins/%s/%s.txt", pAltSkin->zLabel, zWhat);
    zOut = builtin_text(z);
    fossil_free(z);
  }else{
    zOut = db_get(zWhat, 0);
    if( zOut==0 ){
      z = mprintf("skins/default/%s.txt", zWhat);
      zOut = builtin_text(z);
      fossil_free(z);


    }
  }
  return zOut;
}

/*
** Return the command-line option used to set the skin, or return NULL







>
>
>
>
>
>
>
>
>
>
>
>
>
>











>
>







256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
    if( file_isfile(z, ExtFILE) ){
      Blob x;
      blob_read_from_file(&x, z, ExtFILE);
      fossil_free(z);
      return blob_str(&x);
    }
    fossil_free(z);
  }
  if( iSkinSource==SKIN_FROM_UNKNOWN ){
    const char *zDflt = db_get("default-skin", 0);
    iSkinSource = SKIN_FROM_DEFAULT;
    if( zDflt!=0 ){
      int i;
      for(i=0; i<count(aBuiltinSkin); i++){
        if( fossil_strcmp(aBuiltinSkin[i].zLabel, zDflt)==0 ){
          pAltSkin = &aBuiltinSkin[i];
          iSkinSource = SKIN_FROM_SETTING;
          break;
        }
      }
    }
  }
  if( pAltSkin ){
    z = mprintf("skins/%s/%s.txt", pAltSkin->zLabel, zWhat);
    zOut = builtin_text(z);
    fossil_free(z);
  }else{
    zOut = db_get(zWhat, 0);
    if( zOut==0 ){
      z = mprintf("skins/default/%s.txt", zWhat);
      zOut = builtin_text(z);
      fossil_free(z);
    }else{
      iSkinSource = SKIN_FROM_CUSTOM;
    }
  }
  return zOut;
}

/*
** Return the command-line option used to set the skin, or return NULL
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224

1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
























1245
1246
1247
  style_header("Skins");
  if( iDraftSkin || nSkinRank<=1 ){
    @ <p class="warning">Warning:
    if( iDraftSkin>0 ){
      @ you are using a draft skin,
    }else{
      @ this fossil instance was started with a hard-coded skin
      @ value,
    }
    @ which trumps any option selected below. A skin selected
    @ below will be recorded in your preference cookie

    @ but will not be used so long as the site has a
    @ higher-priority skin in place.
    @ </p>
  }
  @ <p>The following skins are available for this repository:</p>
  @ <ul>
  if( pAltSkin==0 && zAltSkinDir==0 && iDraftSkin==0 ){
    @ <li> Standard skin for this repository &larr; <i>Currently in use</i>
  }else{
    @ <li> %z(href("%R/skins?skin="))Standard skin for this repository</a>
  }
  for(i=0; i<count(aBuiltinSkin); i++){
    if( pAltSkin==&aBuiltinSkin[i] ){
      @ <li> %h(aBuiltinSkin[i].zDesc) &larr; <i>Currently in use</i>
    }else{
      char *zUrl = href("%R/skins?skin=%T", aBuiltinSkin[i].zLabel);
      @ <li> %z(zUrl)%h(aBuiltinSkin[i].zDesc)</a>
    }
  }
  @ </ul>
























  style_finish_page();
  fossil_free(zBase);
}







|

|
|
>







|

|










>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
  style_header("Skins");
  if( iDraftSkin || nSkinRank<=1 ){
    @ <p class="warning">Warning:
    if( iDraftSkin>0 ){
      @ you are using a draft skin,
    }else{
      @ this fossil instance was started with a hard-coded skin
      @ value
    }
    @ which supercedes any option selected below. A skin selected
    @ below will be recorded in your 
    @ "%z(href("%R/fdscookie"))fossil_display_settings</a>" cookie
    @ but will not be used so long as the site has a
    @ higher-priority skin in place.
    @ </p>
  }
  @ <p>The following skins are available for this repository:</p>
  @ <ul>
  if( pAltSkin==0 && zAltSkinDir==0 && iDraftSkin==0 ){
    @ <li> Custom skin for this repository &larr; <i>Currently in use</i>
  }else{
    @ <li> %z(href("%R/skins?skin="))Custom skin for this repository</a>
  }
  for(i=0; i<count(aBuiltinSkin); i++){
    if( pAltSkin==&aBuiltinSkin[i] ){
      @ <li> %h(aBuiltinSkin[i].zDesc) &larr; <i>Currently in use</i>
    }else{
      char *zUrl = href("%R/skins?skin=%T", aBuiltinSkin[i].zLabel);
      @ <li> %z(zUrl)%h(aBuiltinSkin[i].zDesc)</a>
    }
  }
  @ </ul>
  if( iSkinSource<SKIN_FROM_CUSTOM ){
    @ <p>The current skin is selected by
    switch( iSkinSource ){
      case SKIN_FROM_DRAFT:
         @ the "debugN" prefix on the PATH_INFO portion of the URL.
         break;
      case SKIN_FROM_CMDLINE:
         @ the "--skin" command-line option on the Fossil server.
         break;
      case SKIN_FROM_CGI:
         @ the "skin:" property in the CGI script that runs the Fossil server.
         break;
      case SKIN_FROM_QPARAM:
         @ the "skin=NAME" query parameter on the URL.
         break;
      case SKIN_FROM_COOKIE:
         @ the "skin" property in the
         @ "%z(href("%R/fdscookie"))fossil_display_settings</a>" cookie.
         break;
      case SKIN_FROM_SETTING:
         @ the "default-skin" setting on the repository.
         break;
    }
  }
  style_finish_page();
  fossil_free(zBase);
}