aboutsummaryrefslogtreecommitdiff
path: root/doc/todo/Moving_Pages.mdwn
blob: 7485f06fde9d2a74c75c007f258d7fa7979886b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
113
114
115
116
117
118
119
120
121
122
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
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
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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
I thought I'd draw attention to a desire of mine for **ikiwiki**.  I'm no power-user, and mostly I do fairly simple stuff with my [wiki](http://kitenet.net/~kyle/family/wiki).

However, I would like the ability (now) to **rename/move/delete** pages.  As part of having a genealogy wiki, I've put name and dates of birth/death as part of the title of each article (so to avoid cases where people have the same name, but are children/cousins/etc of others with that name).  However, some of this information changes.  For instance, I didn't know a date of death and now I do, or I had it wrong originally, or it turns out someone is still alive I didn't know about.  All of these cases leave me with bad article titles.

So, I can go ahead and move the file to a new page with the correct info, orphan that page, provide a link for the new page if desired, and otherwise ignore that page.  But then, it clutters up the wiki and serves no useful purpose.

Anyway to consider implementing **rename/move/delete** ?  I certainly lack the skills to appreciate what this would entail, but feel free to comment if it appears impossible, and then I'll go back to the aforementioned workaround.  I would prefer simple rename, however.

Thanks again to [Joey](http://kitenet.net/~joey) for putting ikiwiki together.  I love the program. 

*[Kyle](http://kitenet.net/~kyle/)=*

----

The MediaWiki moving/renaming mechanism is pretty nice.  It's easy to get a list of pages that point to the current page.  When renaming a page it sticks a forwarding page in the original place.  The larger the size of the wiki the more important organization tools become.

I see the need for:

* a new type of file to represent a forwarding page
* a rename tool that can
  * move the existing page to the new name
  * optionally drop a forwarding page
  * optionally rewrite incoming links to the new location

Brad

> This could be implemented through the use of an HTTP redirect to the 
> new page, but this has the downside that people may not know they're being 
> redirected.
>
> This could also be implemented using a combination of raw inline and meta
> to change the title (add a "redirected from etc." page. This could be done
> with a plugin. A redirect page would be [[!redirect page="newpage"]].
> But then if you click "edit" on this redirect page, you won't be able
> to edit the new page, only the call to redirect.
> --Ethan

-----

[[!tag patch]]

This is my second cut at a feature like that requested here.
It can also be found [here](http://ikidev.betacantrips.com/patches/move.patch).

A few shortcomings exist: 

* No precautions whatsoever are made to protect against race conditions or failures
  in the rcs\_move function. I didn't even do the `cgi_editpage` thing where I hold
  the lock and render afterwards (mostly because the copy I was editing was not
  up-to-date enough to have that code). Although FAILED_SAVE is in movepage.tmpl,
  no code activates it yet.
* Some code is duplicated between cgi\_movepage and cgi\_editpage, as well
  as rcs\_commit and rcs\_move.
* The user interface is pretty lame. I couldn't figure out a good way to let
  the user specify which directory to move things to without implementing a
  FileChooser thing.
* No redirect pages like those mentioned on [[todo/Moving_Pages]] exist yet, 
  so none are created.
* I added a Move link to page.tmpl but it may belong better someplace else --
  maybe editpage.tmpl? Not sure.
* from is redundant with page so far -- but since the Move links could someday
  come from someplace other than the page itself I kept it around.
* If I move foo.mdwn to bar.mdwn, foo/* should move too, probably.

> Looks like a good start, although I agree about many of the points above,
> and also feel that something needs to be done about rcses that don't
> implement a move operation -- falling back to an add and delete.
> --[[Joey]]

Hmm. Shouldn't that be done on a by-RCS basis, though? (i.e. implemented
by backends in the `rcs_move` function)

> Probably, yes, but maybe there's a way to avoid duplicating code for that
> in several of them.

Also, how should ikiwiki react if a page is edited (say, by another user)
before it is moved? Bail, or shrug and proceed?

> The important thing is to keep in mind that the page could be edited,
> moved, deleted, etc in between the user starting the move and the move
> happening. So, the code really needs to deal with all of these cases in
> some way. It seems fine to me to go ahead with the move even if the page
> was edited. If the page was deleted or moved, it seems reasonable to exit
> with an error.

    diff -urNX ignorepats ikiwiki/IkiWiki/CGI.pm ikidev/IkiWiki/CGI.pm
    --- ikiwiki/IkiWiki/CGI.pm  2007-02-14 18:17:12.000000000 -0800
    +++ ikidev/IkiWiki/CGI.pm   2007-02-22 18:54:23.194982000 -0800
    @@ -561,6 +561,106 @@
        }
     } #}}}

    +sub cgi_movepage($$) {
    +   my $q = shift;
    +   my $session = shift;
    +   eval q{use CGI::FormBuilder};
    +   error($@) if $@;
    +   my @fields=qw(do from rcsinfo page newdir newname comments);
    +   my @buttons=("Rename Page", "Cancel");
    +
    +   my $form = CGI::FormBuilder->new(
    +           fields => \@fields,
    +                header => 1,
    +                charset => "utf-8",
    +                method => 'POST',
    +           action => $config{cgiurl},
    +                template => (-e "$config{templatedir}/movepage.tmpl" ?
    +                        {template_params("movepage.tmpl")} : ""),
    +   );
    +   run_hooks(formbuilder_setup => sub {
    +           shift->(form => $form, cgi => $q, session => $session);
    +   });
    +
    +   decode_form_utf8($form);
    +
    +   # This untaint is safe because if the page doesn't exist, bail.
    +   my $page = $form->field('page');
    +   $page = possibly_foolish_untaint($page);
    +   if (! exists $pagesources{$page}) {
    +           error("page does not exist");
    +   }
    +   my $file=$pagesources{$page};
    +   my $type=pagetype($file);
    +
    +   my $from;
    +   if (defined $form->field('from')) {
    +           ($from)=$form->field('from')=~/$config{wiki_file_regexp}/;
    +   }
    +
    +   $form->field(name => "do", type => 'hidden');
    +   $form->field(name => "from", type => 'hidden');
    +   $form->field(name => "rcsinfo", type => 'hidden');
    +   $form->field(name => "newdir", type => 'text', size => 80);
    +   $form->field(name => "page", value => $page, force => 1);
    +   $form->field(name => "newname", type => "text", size => 80);
    +   $form->field(name => "comments", type => "text", size => 80);
    +   $form->tmpl_param("can_commit", $config{rcs});
    +   $form->tmpl_param("indexlink", indexlink());
    +   $form->tmpl_param("baseurl", baseurl());
    +
    +   if (! $form->submitted) {
    +           $form->field(name => "rcsinfo", value => rcs_prepedit($file),
    +                        force => 1);
    +   }
    +
    +   if ($form->submitted eq "Cancel") {
    +           redirect($q, "$config{url}/".htmlpage($page));
    +           return;
    +   }
    +
    +   if (! $form->submitted || ! $form->validate) {
    +           check_canedit($page, $q, $session);
    +           $form->tmpl_param("page_select", 0);
    +           $form->field(name => "page", type => 'hidden');
    +           $form->field(name => "type", type => 'hidden');
    +           $form->title(sprintf(gettext("moving %s"), pagetitle($page)));
    +           my $pname = basename($page);
    +           my $dname = dirname($page);
    +           if (! defined $form->field('newname') ||
    +               ! length $form->field('newname')) {
    +                   $form->field(name => "newname",
    +                                value => pagetitle($pname, 1), force => 1);
    +           }
    +           if (! defined $form->field('newdir') ||
    +               ! length $form->field('newdir')) {
    +                   $form->field(name => "newdir",
    +                                value => pagetitle($dname, 1), force => 1);
    +           }
    +           print $form->render(submit => \@buttons);
    +   }
    +   else{
    +           # This untaint is safe because titlepage removes any problematic
    +           # characters.
    +           my ($newname)=$form->field('newname');
    +           $newname=titlepage(possibly_foolish_untaint($newname));
    +           my ($newdir)=$form->field('newdir');
    +           $newdir=titlepage(possibly_foolish_untaint($newdir));
    +           if (! defined $newname || ! length $newname || file_pruned($newname, $config{srcdir}) || $newname=~/^\//) {
    +                   error("bad page name");
    +           }
    +           check_canedit($page, $q, $session);
    +
    +           my $newpage = ($newdir?"$newdir/":"") . $newname;
    +           my $newfile = $newpage . ".$type";
    +           my $message = $form->field('comments');
    +           unlockwiki();
    +           rcs_move($file, $newfile, $message, $form->field("rcsinfo"),
    +                    $session->param("name"), $ENV{REMOTE_ADDR});
    +           redirect($q, "$config{url}/".htmlpage($newpage));
    +   }
    +}
    +
     sub cgi_getsession ($) { #{{{
        my $q=shift;

    @@ -656,6 +756,9 @@
        elsif (defined $session->param("postsignin")) {
                cgi_postsignin($q, $session);
        }
    +   elsif ($do eq 'move') {
    +           cgi_movepage($q, $session);
    +   }
        elsif ($do eq 'prefs') {
                cgi_prefs($q, $session);
        }
    diff -urNX ignorepats ikiwiki/IkiWiki/Rcs/svn.pm ikidev/IkiWiki/Rcs/svn.pm
    --- ikiwiki/IkiWiki/Rcs/svn.pm      2007-01-27 16:04:48.000000000 -0800
    +++ ikidev/IkiWiki/Rcs/svn.pm       2007-02-22 01:51:29.923626000 -0800
    @@ -60,6 +60,34 @@
        }
     } #}}}

    +sub rcs_move ($$$$;$$) {
    +   my $file=shift;
    +   my $newname=shift;
    +   my $message=shift;
    +   my $rcstoken=shift;
    +   my $user=shift;
    +   my $ipaddr=shift;
    +   if (defined $user) {
    +           $message="web commit by $user".(length $message ? ": $message" : "");
    +   }
    +   elsif (defined $ipaddr) {
    +           $message="web commit from $ipaddr".(length $message ? ": $message" : "");
    +   }
    +
    +   chdir($config{srcdir}); # svn merge wants to be here
    +
    +   if (system("svn", "move", "--quiet",
    +              "$file", "$newname") != 0) {
    +           return 1;
    +   }
    +   if (system("svn", "commit", "--quiet",
    +              "--encoding", "UTF-8", "-m",
    +              possibly_foolish_untaint($message)) != 0) {
    +           return 1;
    +   }
    +   return undef # success
    +}
    +
     sub rcs_commit ($$$;$$) { #{{{
        # Tries to commit the page; returns undef on _success_ and
        # a version of the page with the rcs's conflict markers on failure.
    diff -urNX ignorepats ikiwiki/IkiWiki/Render.pm ikidev/IkiWiki/Render.pm
    --- ikiwiki/IkiWiki/Render.pm       2007-02-14 17:00:05.000000000 -0800
    +++ ikidev/IkiWiki/Render.pm        2007-02-22 18:30:00.451755000 -0800
    @@ -80,6 +80,7 @@

        if (length $config{cgiurl}) {
                $template->param(editurl => cgiurl(do => "edit", page => $page));
    +           $template->param(moveurl => cgiurl(do => "move", page => $page));
                $template->param(prefsurl => cgiurl(do => "prefs"));
                if ($config{rcs}) {
                        $template->param(recentchangesurl => cgiurl(do => "recentchanges"));
    diff -urNX ignorepats ikiwiki/templates/movepage.tmpl ikidev/templates/movepage.tmpl
    --- ikiwiki/templates/movepage.tmpl 1969-12-31 16:00:00.000000000 -0800
    +++ ikidev/templates/movepage.tmpl  2007-02-22 18:40:39.751763000 -0800
    @@ -0,0 +1,44 @@
    +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    +<html>
    +<head>
    +<base href="<TMPL_VAR BASEURL>" />
    +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    +<title><TMPL_VAR FORM-TITLE></title>
    +<link rel="stylesheet" href="<TMPL_VAR BASEURL>style.css" type="text/css" />
    +<link rel="stylesheet" href="<TMPL_VAR BASEURL>local.css" type="text/css" />
    +<TMPL_IF NAME="FAVICON">
    +<link rel="icon" href="<TMPL_VAR BASEURL><TMPL_VAR FAVICON>" type="image/x-icon" />
    +</TMPL_IF>
    +</head>
    +<body>
    +<TMPL_IF NAME="FAILED_SAVE">
    +<p>
    +<b>Failed to save your changes.</b>
    +</p>
    +<p>
    +Your changes were not able to be saved to disk. The system gave the error:
    +<blockquote>
    +<TMPL_VAR ERROR_MESSAGE>
    +</blockquote>
    +Your changes are preserved below, and you can try again to save them.
    +</p>
    +</TMPL_IF>
    +<TMPL_VAR FORM-START>
    +<div class="header">
    +<span><TMPL_VAR INDEXLINK>/ <TMPL_VAR FORM-TITLE></span>
    +</div>
    +<TMPL_VAR FIELD-DO>
    +<TMPL_VAR FIELD-FROM>
    +<TMPL_VAR FIELD-RCSINFO>
    +<TMPL_VAR FIELD-PAGE>
    +New location: <TMPL_VAR FIELD-NEWDIR>/ <TMPL_VAR FIELD-NEWNAME>
    +<br />
    +<TMPL_IF NAME="CAN_COMMIT">
    +Optional comment about this change:<br />
    +<TMPL_VAR FIELD-COMMENTS><br />
    +</TMPL_IF>
    +<input id="_submit" name="_submit" type="submit" value="Rename Page" /><input id="_submit_2" name="_submit" type="submit" value="Cancel" />
    +<TMPL_VAR FORM-END>
    +</body>
    +</html>
    diff -urNX ignorepats ikiwiki/templates/page.tmpl ikidev/templates/page.tmpl
    --- ikiwiki/templates/page.tmpl     2006-12-28 12:27:01.000000000 -0800
    +++ ikidev/templates/page.tmpl      2007-02-22 01:52:33.078464000 -0800
    @@ -32,6 +32,9 @@
     <TMPL_IF NAME="EDITURL">
     <li><a href="<TMPL_VAR EDITURL>">Edit</a></li>
     </TMPL_IF>
    +<TMPL_IF NAME="MOVEURL">
    +<li><a href="<TMPL_VAR MOVEURL>">Move</a></li>
    +</TMPL_IF>
     <TMPL_IF NAME="RECENTCHANGESURL">
     <li><a href="<TMPL_VAR RECENTCHANGESURL>">RecentChanges</a></li>
     </TMPL_IF>

----

I'm going to try to run through a full analysis and design for moving and
deleting pages here. I want to make sure all cases are covered. --[[Joey]]

## UI

The UI I envision is to add "Rename" and "Delete" buttons to the file edit
page. Both next to the Save button, and also at the bottom of the attachment
management interface.

The attachment(s) to rename or delete would be selected using the check boxes
and then the button applies to all of them. Deleting multiple attachments
in one go is fine; renaming multiple attachments in one go is ambiguous,
and it can just error out if more than one is selected for rename.
(Alternatively, it could allow moving them all to a different subdirectory.)

The Delete buttons lead to a page to confirm the deletion(s).

The Rename buttons lead to a page with a text edit box for editing the
page name. The title of the page is edited, not the actual filename.

There will also be a optional comment field, so a commit message can be
written for the rename/delete.

Note that there's an edge case concerning pages that have a "/" encoded
as part of their title. There's no way for a title edit box to
differentiate between that, and a "/" that is instended to refer to a
subdirectory to move the page to. Consequence is that "/" will always be
treated literally, as a subdir separator; it will not be possible to use
this interface to put an encoded "/" in a page's name.

Once a page is renamed, ikiwiki will return to the page edit interface,
now for the renamed page. Any modifications that the user had made to the
textarea will be preserved.

Similarly, when an attachment is renamed, or deleted, return to the page
edit interface (with the attachments displayed).

When a page is deleted, redirect the user to the toplevel index.

Note that this design, particularly the return to the edit interface after
rename, means that the rename button can *only* be put on the page edit ui.
It won't be possible to put it on the action bar or somewhere else. (It
would be possible to code up a different rename button that doesn't do
that, and use it elsewhere.)

Hmm, unless it saves the edit state and reloads it later, while using a separate
form. Which seems to solve other problems, so I think is the way to go.

## SubPages

When renaming `foo`, it probably makes sense to also rename
`foo/Discussion`. Should other SubPages in `foo/` also be renamed? I think
it's probably simplest to rename all of its SubPages too.

When deleting `foo`, I don't think SubPages should be deleted. The
potential for mistakes and abuse is too large.

## link fixups

When renaming a page, it's desirable to keep links that point to it
working. Rather than use redirection pages, I think that all pages that
link to it should be modified to fix their links.

In some cases, a redirection page will be wanted, to keep long-lived urls
working. Since the meta plugin supports creating such pages, and since they
won't always be needed, I think it will be simplest to just leave it up to
the user to create such a redirection page after renaming a page.

## who can delete/rename what?

The source page must be editable by the user to be deleted/renamed.
When renaming, the dest page must not already exist, and must be creatable
by the user, too.

When deleting/renaming attachments, the `allowed_attachments` PageSpec
is checked too.

## RCS

Two new optional functions are added to the RCS interface:

* `rcs_delete(file, message, rcstoken, user, ipaddr)`
* `rcs_rename(old, new, message, rcstoken, user, ipaddr)`

The page move/rename code will check if these are not available, and error
out.

Similar to `rcs_commit` both of these take a rcstoken, which is generated
by an earlier `rcs_prepedit`.

## conflicts

Cases that have to be dealt with:

* Alice clicks "delete" button for a page; Bob makes a modification;
  Alice confirms deletion. Ideally in this case, Alice should get an error
  message that there's a conflict.
* Alice opens edit UI for a page; Bob makes a modification; Alice
  clicks delete button and confirms deletion. Again here, Alice should get
  a conflict error. Note that this means that the rcstoken should be
  recorded when the edit UI is first opened, not when the delete button is
  hit.
* Alice and Bob both try to delete a page at the same time. It's fine for
  the second one to get a message that it no longer exists. Or just to
  silently fail to delete the deleted page..
* Alice deletes a page; Bob had edit window open for it, and saves
  it afterwards. I think that Bob should win in this case; Alice can always
  notice the page has been added back, and delete it again.
* Alice clicks "rename" button for a page; Bob makes a modification;
  Alice confirms rename. This case seems easy, it should just rename the
  modified page.
* Alice opens edit UI for a page; Bob makes a modification; Alice
  clicks rename button and confirms rename. Seems same as previous case.
* Alice and Bob both try to rename a page at the same time (to probably
  different names). Or one tries to delete, and the other to rename. 
  I think it's acceptible for the second one to get an error message that
  the page no longer exists.
* Alice renames a page; Bob had edit window open for it, and saves
  it afterwards, under old name. I think it's acceptible for Bob to succeed
  in saving it under the old name in this case, though not ideal.
* Alice renames (or deletes) a page. In the meantime, Bob is uploading an
  attachment to it, and finishes after the rename finishes. Is it
  acceptible for the attachment to be saved under the old name?
* Alice starts creating a new page. In the meantime, Bob renames a
  different page to that name. Alice should get an error message when
  committing; and it should have conflict markers. Ie, this should work the
  same as if Bob had edited the new page at the same time as Alice did.
* Bob starts renaming a page. In the meantime, Alice creates a new page
  with the name he's renaming it to. Here Bob should get a error message
  that he can't rename the page to an existing name. (A conflict resolution
  edit would also be ok.)