Gitea: TOC for Wiki Pages

Created on 2 Feb 2017  路  16Comments  路  Source: go-gitea/gitea

  • Gitea version (or commit ref): 1.0.1
  • Git version: 2.11.0
  • Operating system: Arch Linux
  • Database (use [x]):

    • [ ] PostgreSQL

    • [ ] MySQL

    • [x] SQLite

  • Can you reproduce the bug at https://try.gitea.io:

    • [ ] Yes (provide example URL)

    • [ ] No

    • [x] Not relevant

  • Log gist:

Description

This is an "clone" of gogs issue 3931
Having an TOC in Wiki Pages could be helpfull.


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

kinfeature revieweconfirmed

Most helpful comment

Hello everybody,

i've modified some JS i've written some time ago to create the toc - if there is a rendered md file. Feel free to use this code.

If there should be a option inside repository settings to enable/disable the script, I couln't create the pull request. (My golang is too bad.)
Otherwise I could create the js file and add a line in html template. Leave a comment if I should open such a merge/pull request. (If this solution is ok.)

Requirements:

Benefints:

  • JS don't alter files inside git repository.
  • fast and small: calculation done on client side, using pure js

Disadvantage:

  • Does not work with disabled JS.

Here is the Code:

(function() {
    // html listings ----------------------------------------------------
    let openedLists, listEopen;
    // close list
    const _closeList = function (count) {
        let out = '';
        if(count == false || count == 0 || count == 'undefined' || typeof(count) == 'undefined' ) {
            count = openedLists.length;
        } else {
            count = Math.min(count, openedLists.length);
        }
        while (count > 0){
            out += '</li>' + openedLists.pop();
            listEopen = true;
            count--;
        }
        return out;
    };
    // open list
    const _openList = function (level) {
        let out = '<ul>';
        openedLists.push('</ul>');
        listEopen = false;
        return out;
    };
    // handle list element
    // create valid html list
    const __list = function (line, level, id) {
        let out = '';
        let diff = level - openedLists.length;
        if(diff > 0) { //open new level
            out += _openList(level);
            out += __list(line,level, id);
        }  else if(diff < 0 ) {
            out += _closeList(-diff);
            out += __list(line, level, id);
        } else { // only add list element
            out += ((listEopen)?'</li>':'') + '<li><a href="#' + id + '" rel="nofollow">' + line + '</a>';
            listEopen = true;
        }
        return out;
    };
    /** 
     * find headlines and create list ----------------------------------
     * @param target    Element  target container where toc should be created
     */
    const create_toc_inside = function(target) {
        let rm;
        if(target != null) {
            if( (rm = target.querySelector('.auto-toc-wrapper')) != null ) {
                rm.parentNode.removeChild(rm);
            }
            openedLists = []; listEopen = false;
            // get content and create html
            const elms = target.querySelectorAll('h1,h2,h3,h4,h5');
            let html = '';
            for(let i = 0; i < elms.length; i++){
                let l = elms[i].tagName.substr(1); //level
                let t = elms[i].innerText.trim().trim(''); //text
                let id = elms[i].id;
                // create html 
                if(t.length > 0 && l >= 1) {
                    html += __list( t, l, id);
                } else {
                    html += _closeList(0) + l;
                }
            }
            html += _closeList(0);
            //create elements
            let d = document.createElement('div');
            d.id = 'auto-toc';
            d.className = 'anchor-wrap';
            d.innerHTML = '<h2>Table of Contents</h2>';
            let d2 = document.createElement('div');
            d2.className = 'auto-toc-container';
            d2.innerHTML = html;
            d2.insertBefore(d, d2.firstChild);
            let c = document.createElement('div');
            c.className = 'auto-toc-wrapper';
            c.appendChild(d2);
            //inject toc
            target.insertBefore(c, target.firstChild);
            //set style
            c.style.cssText = "float:right;background:#fff;padding:0 0 7px 20px;position:relative;z-index:1";
            d2.style.cssText = "padding:7px;border:1px solid #333;border-radius:5px";
        }
    };
    // create toc ----------------------------------
    create_toc_inside(document.querySelector('.file-view.markdown')); // md
    create_toc_inside(document.querySelector('.segment.markdown')); // wiki pages
})();

Result - MD files:
image

Result - build in wiki pages (md):
(image is from random gitea wiki page i found with google)
image

Edit: I noticed this issue is for wiki pages, i've only tested on md files (this was what i was looking for in google search).
Edit2: Added line for wiki pages

All 16 comments

It seems it's already supported?

A table of contents is supported in Gittea? I mean an TOC based on headings in the Wiki Pages.
Then i do not know how to use it (could not find anything).
The original GOGS Issue is still open and so i think if this works, it was implemented in this project only.

could you see https://try.gitea.io/lunny/vscode/wiki, is the right bar what you want?

@lunny although this is an cool feature (is this already implemented?) i mean such thing like this: https://i.stack.imgur.com/IgJJd.png

@MorphBonehunter yes, https://try.gitea.io/lunny/vscode/wiki is the demo of the master. I see what's you want. The content is generated automatically by a special syntax?

@lunny yes that's what i thought about. Maybe this isn't possible with an markup renderer atm.
I thought something like this in an README.md:

%%TOC%%

# heading one
# heading two
## subheading

and the %%TOC%% is replaced with:

  1. heading one
  2. heading two
    2.1 subheading

Maybe this could not realized and we are bound to such things like https://github.com/thlorenz/doctoc and have to generate the TOC befor commit.

TOC is really important. Documentation (written in English) is IMHO just as important as the code, which is "a kind of" of English too. With code, we have all sorts of neat code browsers. With documentation, a TOC is a navigation aid, which helps review the content. The search feature is also critical but that's covered by another issue #3760

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs during the next 2 weeks. Thank you for your contributions.

It's a pity that so little attention is paid to one of the most critical factors of a successful software project: the documentation. When multiple pages are authored, TOCs are a critical part of the overall structure. IMHO this issue deserves far more attention than what it has got so far.

@fangchin feel free to submit PR for this

@lafriks I will discuss with my team and get a better idea about the scope. If we cannot work on a PR ourselves, I will see if we see other options. The overall progress of this project has been delightful, but IMHO the documentation handling side (wiki, doc search) etc has not been on par with the code handling side.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs during the next 2 weeks. Thank you for your contributions.

is there any work on it? Should we keep this item as reviewed ?

FWIW, it is possible to script a similar feature if interested:

When editing the wiki on the Web interface, gitea clones a temporary copy of it on disk. If you create a custom pre-commit hook and make sure your gitea user has something like this in their ~/.gitconfig:

[init]
    templatedir = /home/git/git-templates

And stick the script in hooks/pre-commit. Then it will be installed when gitea creates a temporary clone of the wiki to edit. Each page save is a commit to the temporary repo, so that script would be run on each save. You can then manipulate any data from the wiki since it is just a bunch of text files which provides almost infinite flexibility for this case.

Heres a small example of one that would update the sidebar with the last 3 changed pages on each page save (added some comments):

#!/usr/bin/perl

use strict;
use warnings;

my $git_url = qx (git remote get-url origin);
chomp $git_url;
if ( index( $git_url, ".wiki" ) == -1 ) {
    # we aren't in a wiki, lets bail
    exit;
}

# wiki is currently structured as a git repo with one level of markdown files
# for each page. _Sidebar.md and _Footer.md are special and should be ignored
my @md_files = glob '"[!_]*.md"';

# When cloned, all files have the same mtime so we need git to show us the order
# in which the have been changed
my @md_files_recent
    = qx (git --no-pager log --oneline --name-only --pretty=format: | grep -iv _Sidebar.md | sed -r '/^\\s*\$/d\');
chomp @md_files_recent;

# The file that triggered this script won't be listed in the log until after this commit is complete
# so grab the name here and stick it at the top of the recently changed list

# FIX ME check if a file was deleted, since that isn't a useful recently edied link
my @currently_changing_files = qx (git diff --cached --name-only | grep -iv _Sidebar.md | sed -r '/^\\s*\$/d\');
chomp @currently_changing_files;

if ( $currently_changing_files[0] ) {
    unshift @md_files_recent, $currently_changing_files[0];
}

# strip .md
for (@md_files_recent) {s{\.[^.]+$}{}g}

# loop through array in order and remove files we've already seen so we can get
# a uniq list of recently changed files while preserving the expected order
@md_files_recent = do {
    my %seen;
    grep { !$seen{$_}++ } @md_files_recent;
};

@md_files = sort { lc($a) cmp lc($b) } @md_files;

my $final_sidebar_content;

$final_sidebar_content .= "### Recently Changed files\n";

if ( $#md_files_recent >= 2 ) {

    foreach my $i ( 0 .. 2 ) {
        my $link_title = $md_files_recent[$i];
        my $link       = $link_title;
        $link_title =~ s/-/ /gis;
        $final_sidebar_content .= "* [$link_title]($link)\n";
    }
}
else {
    foreach my $i ( 0 .. $#md_files_recent ) {
        my $link_title = $md_files_recent[$i];
        my $link       = $link_title;
        $link_title =~ s/-/ /gis;
        $final_sidebar_content .= "* [$link_title]($link)\n";
    }
}

my $filename = '_Sidebar.md';
open( my $fh, '>', $filename ) or die "Could not open file '$filename' $!";
print $fh $final_sidebar_content;
close $fh;
system( "git", "add", "$filename" );

You can easily have it go through all of the files and extract headers as well to create a TOC that you can then inject into any file.

Hello everybody,

i've modified some JS i've written some time ago to create the toc - if there is a rendered md file. Feel free to use this code.

If there should be a option inside repository settings to enable/disable the script, I couln't create the pull request. (My golang is too bad.)
Otherwise I could create the js file and add a line in html template. Leave a comment if I should open such a merge/pull request. (If this solution is ok.)

Requirements:

Benefints:

  • JS don't alter files inside git repository.
  • fast and small: calculation done on client side, using pure js

Disadvantage:

  • Does not work with disabled JS.

Here is the Code:

(function() {
    // html listings ----------------------------------------------------
    let openedLists, listEopen;
    // close list
    const _closeList = function (count) {
        let out = '';
        if(count == false || count == 0 || count == 'undefined' || typeof(count) == 'undefined' ) {
            count = openedLists.length;
        } else {
            count = Math.min(count, openedLists.length);
        }
        while (count > 0){
            out += '</li>' + openedLists.pop();
            listEopen = true;
            count--;
        }
        return out;
    };
    // open list
    const _openList = function (level) {
        let out = '<ul>';
        openedLists.push('</ul>');
        listEopen = false;
        return out;
    };
    // handle list element
    // create valid html list
    const __list = function (line, level, id) {
        let out = '';
        let diff = level - openedLists.length;
        if(diff > 0) { //open new level
            out += _openList(level);
            out += __list(line,level, id);
        }  else if(diff < 0 ) {
            out += _closeList(-diff);
            out += __list(line, level, id);
        } else { // only add list element
            out += ((listEopen)?'</li>':'') + '<li><a href="#' + id + '" rel="nofollow">' + line + '</a>';
            listEopen = true;
        }
        return out;
    };
    /** 
     * find headlines and create list ----------------------------------
     * @param target    Element  target container where toc should be created
     */
    const create_toc_inside = function(target) {
        let rm;
        if(target != null) {
            if( (rm = target.querySelector('.auto-toc-wrapper')) != null ) {
                rm.parentNode.removeChild(rm);
            }
            openedLists = []; listEopen = false;
            // get content and create html
            const elms = target.querySelectorAll('h1,h2,h3,h4,h5');
            let html = '';
            for(let i = 0; i < elms.length; i++){
                let l = elms[i].tagName.substr(1); //level
                let t = elms[i].innerText.trim().trim(''); //text
                let id = elms[i].id;
                // create html 
                if(t.length > 0 && l >= 1) {
                    html += __list( t, l, id);
                } else {
                    html += _closeList(0) + l;
                }
            }
            html += _closeList(0);
            //create elements
            let d = document.createElement('div');
            d.id = 'auto-toc';
            d.className = 'anchor-wrap';
            d.innerHTML = '<h2>Table of Contents</h2>';
            let d2 = document.createElement('div');
            d2.className = 'auto-toc-container';
            d2.innerHTML = html;
            d2.insertBefore(d, d2.firstChild);
            let c = document.createElement('div');
            c.className = 'auto-toc-wrapper';
            c.appendChild(d2);
            //inject toc
            target.insertBefore(c, target.firstChild);
            //set style
            c.style.cssText = "float:right;background:#fff;padding:0 0 7px 20px;position:relative;z-index:1";
            d2.style.cssText = "padding:7px;border:1px solid #333;border-radius:5px";
        }
    };
    // create toc ----------------------------------
    create_toc_inside(document.querySelector('.file-view.markdown')); // md
    create_toc_inside(document.querySelector('.segment.markdown')); // wiki pages
})();

Result - MD files:
image

Result - build in wiki pages (md):
(image is from random gitea wiki page i found with google)
image

Edit: I noticed this issue is for wiki pages, i've only tested on md files (this was what i was looking for in google search).
Edit2: Added line for wiki pages

any news? or plan to merge this feature ? anything ?

Was this page helpful?
0 / 5 - 0 ratings