", "
")
+ html = html.replace('', otag) # With pygments
+ html = html.replace("\n
\n
", ctag)
+ cleaner = PwicCleanerHtml(
+ str(PwicLib.option(sql, row["project"], "skipped_tags", "")),
+ PwicLib.option(sql, row["project"], "link_nofollow") is not None,
+ )
+ cleaner.feed(html)
+ html = cleaner.get_html()
+ return PwicExtension.on_html(sql, row["project"], row["page"], row["revision"], html).replace("\r", "")
+
+ def _corehtml2html(self, sql: sqlite3.Cursor, row: Dict, html: str, base_url: str, legal_notice: str) -> str:
+ # Convert HTML without headers to full HTML
+ htmlStyles = PwicStylerHtml()
+ html = PwicLib.extended_syntax(
+ html,
+ PwicLib.option(sql, row["project"], "heading_mask"),
+ PwicLib.option(sql, row["project"], "no_heading") is None,
+ )[0]
+ html = htmlStyles.html % (
+ row["author"].replace('"', """),
+ row["date"],
+ row["time"],
+ PwicExtension.on_html_description(sql, row["project"], None, row["page"], row["revision"]).replace(
+ '"', """
+ ),
+ PwicExtension.on_html_keywords(sql, row["project"], None, row["page"], row["revision"]).replace(
+ '"', """
+ ),
+ row["page"].replace("<", "<").replace(">", ">"),
+ row["title"].replace("<", "<").replace(">", ">"),
+ htmlStyles.get_css(rel=self.options["relative_html"]).replace(
+ "src:url(/", "src:url(%s/" % escape(base_url)
+ ),
+ "" if legal_notice == "" else ("" % legal_notice),
+ html,
+ )
+ if not self.options["relative_html"]:
+ html = html.replace(' Dict[int, Dict[str, Union[str, int, bool]]]:
+ # Extract the meta-informations of the embedded pictures
+ MAX_H = max(
+ 0,
+ PwicLib.intval(
+ PwicLib.convert_length(PwicLib.option(sql, row["project"], "odt_image_height_max", "900px"), "", 0)
+ ),
+ )
+ MAX_W = max(
+ 0,
+ PwicLib.intval(
+ PwicLib.convert_length(PwicLib.option(sql, row["project"], "odt_image_width_max", "600px"), "", 0)
+ ),
+ )
+ docids = ["0"]
+ subdocs = PwicConst.REGEXES["document"].findall(row["markdown"])
+ if subdocs is not None:
+ for sd in subdocs:
+ sd = str(PwicLib.intval(sd[0]))
+ if sd not in docids:
+ docids.append(sd)
+ query = """ SELECT a.id, a.project, a.page, a.filename, a.mime, a.width, a.height, a.exturl
+ FROM documents AS a
+ INNER JOIN roles AS b
+ ON b.project = a.project
+ AND b.user = ?
+ AND b.disabled = ''
+ WHERE a.id IN (%s)
+ AND a.mime LIKE 'image/%%' """
+ sql.execute(query % ",".join(docids), (self.user,))
+ pict = {}
+ while True:
+ rowdoc = sql.fetchone()
+ if rowdoc is None:
+ break
+
+ # Optimize the size of the picture
+ try:
+ if rowdoc["width"] > MAX_W:
+ rowdoc["height"] *= MAX_W / rowdoc["width"]
+ rowdoc["width"] = MAX_W
+ if rowdoc["height"] > MAX_H:
+ rowdoc["width"] *= MAX_H / rowdoc["height"]
+ rowdoc["height"] = MAX_H
+ except ValueError:
+ pass
+
+ # Store the meta data
+ entry = {}
+ entry["filename"] = join(PwicConst.DOCUMENTS_PATH % rowdoc["project"], rowdoc["filename"])
+ entry["link"] = "special/document/%d" % (rowdoc["id"] if rowdoc["exturl"] == "" else rowdoc["exturl"])
+ entry["link_odt_img"] = "special/document_%d" % (
+ rowdoc["id"] if rowdoc["exturl"] == "" else rowdoc["exturl"]
+ ) # LibreOffice does not support the paths with multiple folders
+ entry["compressed"] = PwicLib.mime_compressed(PwicLib.file_ext(rowdoc["filename"]))
+ entry["manifest"] = (
+ (
+ '+ if (tag != "p") and (lastTag == "li"): + self.tag_path.append("p") + self.buffer.push(f'<{self.maps["p"]}>') + # ... subitems should close
+ elif (tag in ["ul", "ol"]) and (lastTag == "p"):
+ self.tag_path.pop()
+ self.buffer.push(f'{self.maps["p"]}>')
+ del lastTag
+
+ # Identify the new tag
+ if tag not in PwicConst.VOID_HTML:
+ self.tag_path.append(tag)
+ if tag == "blockquote":
+ self.blockquote_on = True
+ if tag == "blockcode":
+ self.blockcode_on = True
+ self.has_code = True
+
+ # Surrounding extra tags
+ if tag not in self.maps:
+ raise PwicError()
+ if self.maps[tag] is None:
+ if (tag == "input") and (PwicLib.read_attr(attrs, "type") == "checkbox"):
+ self.buffer.push("\u2611" if PwicLib.read_attr_key(attrs, "checked") else "\u2610")
+ return
+ if tag in self.extrasStart:
+ self.buffer.push(self.extrasStart[tag][0])
+
+ # Tag itself
+ tag_img = {}
+ self.buffer.push("<" + str(self.maps[tag]))
+ if tag in self.attributes:
+ for property_key in self.attributes[tag]:
+ property_value = self.attributes[tag][property_key]
+ if property_value[:1] != "#":
+ if property_key[:5] != "dummy":
+ self.buffer.push(' %s="%s"' % (property_key, escape(property_value)))
+ else:
+ property_value = property_value[1:]
+ if tag == "p":
+ if self.blockquote_on:
+ self.buffer.push(' text:style-name="Blockquote"')
+ break
+ else:
+ for key, value_ns in attrs:
+ value = PwicLib.nns(value_ns)
+ if key == property_value:
+ # Fix the base URL for the links
+ if (tag == "a") and (key == "href"):
+ if value[:3] == "../":
+ value = f"{self.base_url}/{self.project}/{self.page}/{value}"
+ if value[:2] == "./":
+ value = f"{self.base_url}/{self.project}/{self.page}/{value[2:]}"
+ elif value[:1] in ["?", "#"]:
+ value = f"{self.base_url}/{self.project}/{self.page}{value}"
+ elif value[:1] == "/":
+ value = self.base_url + str(value)
+ elif value == ".":
+ value = f"{self.base_url}/{self.project}/{self.page}"
+
+ # Fix the attributes for the pictures
+ if tag == "img":
+ if key == "alt":
+ tag_img["alt"] = value
+ elif key == "title":
+ tag_img["title"] = value
+ elif key == "src":
+ if value[:1] == "/":
+ value = value[1:]
+ if self.pict is not None:
+ docid_re = PwicConst.REGEXES["document_imgsrc"].match(value)
+ if docid_re is not None:
+ width = height = 0
+ docid = PwicLib.intval(docid_re.group(1))
+ if docid in self.pict:
+ if self.pict[docid]["remote"] or (
+ self.pict[docid]["link"] == value
+ ):
+ value = self.pict[docid]["link_odt_img"]
+ width = self.pict[docid]["width"]
+ height = self.pict[docid]["height"]
+ if 0 in [width, height]:
+ width = height = PwicLib.intval(
+ PwicConst.DEFAULTS["odt_img_defpix"]
+ )
+ self._replace_marker("{$w}", PwicLib.convert_length(width, "cm", 2))
+ self._replace_marker("{$h}", PwicLib.convert_length(height, "cm", 2))
+
+ # Fix the class name for the syntax highlight
+ if (tag == "span") and self.blockcode_on and (key == "class"):
+ value = "Code_" + value
+
+ if property_key[:5] != "dummy":
+ self.buffer.push(' %s="%s"' % (property_key, escape(value)))
+ break
+ if tag in PwicConst.VOID_HTML:
+ self.buffer.push("/")
+ self.buffer.push(">")
+
+ # Surrounding extra tags
+ if tag == "img": # Void tag
+ if "alt" in tag_img:
+ self.buffer.push("
+ if (tag == "li") and (lastTag == "p"):
+ self.tag_path.pop()
+ self.buffer.push(f'{self.maps["p"]}>')
+ del lastTag
+
+ # Identify the tag
+ if self.tag_path[-1] != tag:
+ raise PwicError()
+ self.tag_path.pop()
+
+ # Surrounding extra tags
+ if tag in self.extrasEnd:
+ self.buffer.push(self.extrasEnd[tag])
+
+ # Final mapping
+ if tag in self.maps:
+ if tag not in PwicConst.VOID_HTML:
+ if tag == "blockquote":
+ self.blockquote_on = False
+ if tag == "blockcode":
+ self.blockcode_on = False
+ if self.maps[tag] is not None:
+ self.buffer.push(f"{self.maps[tag]}>")
+
+ # Handle the descriptors of the tables
+ if tag == "tr":
+ self.table_descriptors[-1]["max"] = max(
+ self.table_descriptors[-1]["count"], self.table_descriptors[-1]["max"]
+ )
+ self.table_descriptors[-1]["count"] = 0
+ if tag == "table":
+ cursor = self.table_descriptors[-1]["cursor"]
+ odt = self.buffer.pop()
+ self.buffer.override(
+ odt[:cursor]
+ + "
+ if (self.tag_path[-1] if len(self.tag_path) > 0 else "") == "li":
+ self.tag_path.append("p")
+ self.buffer.push(f'<{self.maps["p"]}>')
+ # Text alignment for the code
+ if self.blockcode_on:
+ data = data.replace("\r", "")
+ data = data.replace("\n", " for block annotations
+ # The missing tag is then added dynamically here
+ missing = len(self.tag_path) == 0
+ if missing:
+ self.buffer.push(" Just type tags.
+ grafs = []
+ for i, graf in enumerate(re.split(r"\n{2,}", text)):
+ if graf in self.html_blocks:
+ # Unhashify HTML blocks
+ grafs.append(self.html_blocks[graf])
+ else:
+ cuddled_list = None
+ if "cuddled-lists" in self.extras:
+ # Need to put back trailing '\n' for `_list_item_re`
+ # match at the end of the paragraph.
+ li = self._list_item_re.search(graf + "\n")
+ # Two of the same list marker in this paragraph: a likely
+ # candidate for a list cuddled to preceding paragraph
+ # text (issue 33). Note the `[-1]` is a quick way to
+ # consider numeric bullets (e.g. "1." and "2.") to be
+ # equal.
+ if (
+ li
+ and len(li.group(2)) <= 3
+ and (
+ (li.group("next_marker") and li.group("marker")[-1] == li.group("next_marker")[-1])
+ or li.group("next_marker") is None
+ )
+ ):
+ start = li.start()
+ cuddled_list = self._do_lists(graf[start:]).rstrip("\n")
+ assert re.match(r"^<(?:ul|ol).*?>", cuddled_list)
+ graf = graf[:start]
+
+ # Wrap tags.
+ graf = self._run_span_gamut(graf)
+ grafs.append(" " % self._html_class_str_from_tag("p") + graf.lstrip(" \t") + " %s >>u&y;if(_!==f>>>u&y)break;_&&(l+=(1<o&&(c=c.removeBefore(r,u,i-l)),c&&f a&&(a=c.size),i(u)||(c=c.map((function(e){return he(e)}))),r.push(c)}return a>e.size&&(e=e.setSize(a)),vt(e,t,r)}function qt(e){return e The support team for your project may be different than the one mentioned below. So refer to the special page of your project in priority. Refer to the file By design, the most important configuration cannot be done online, so you will have to invoke the administration console. Execute the command To simplify this syntax, just type The initialization consists in creating the SQLite database and the folders that store the uploaded files. It is done once at the beginning with the command In rare cases if the Python code fails for example, the database can be locked without timeout. In this case, try first to run the command With the command The project-dependent variables are mostly changeable online in the special page, but not all for security reasons. The project-independent variables generally can be edited through the console only and it often requires the restart of the server. The users will then have to reconnect from another tab, unless you define the option To show one or all the parameters, use the command In case you need to clean the obsolete variables, run the command You can connect to Pwic.wiki by using a professional third-party identity provider. The mecanism OAuth relies on secret keys and, obviously, a lot of trust. The user connects from another website and Pwic.wiki pings back the server to fetch the main email address of the user. The account is then automatically configured by default. No other personal information are used. Several project-independent variables must be defined in the following order. Any change requires the restart of the server. This feature stands for 2-factor authentication (2FA) and Time-based One Time Password (TOTP). The concept is to share a secret key with the user once. When he connects to the website, a PIN code is requested: its value changes every 30 seconds automatically and should be accepted only once. Consequently, this random password managed on a separate device acts as a second source of authentication and strengthens the security of your account. The setup is very easy: This authentication technique has the benefit to be standard (RFC 6238), offline and easy to activate. It cannot act as a primary authentication method. It is also incompatible with the federated authentication because some providers include built-in 2FA techniques already (especially SMS). 2FA is optional, user-specific and can be assigned, removed and updated at any moment. The feature is supported but not enabled by default because the filter by IP address should not be sensible to some special HTTP headers. By using Nginx or Apache, you need to modify the code of the extension named Optionally, if you use mobile devices, it is possible to compress the big static files with the command The option must be configured as well in your reverse proxy server (if you use one). To facilitate the maintenance of the database remotely, the command First, it is recommended to warn your users in advance through the option At the time of the maintenance, shut down the server with To restore the backup, simply replace the current database with a previous backup. Any changes made meanwhile will be lost. You must also ensure that the backup file is compatible with your used version of Pwic.wiki and that the salt As an alternative, you can backup the entire folder As per the authorizations are designed, the creation of a new project is only possible in command line. Run the command By default, you cannot create new pages. You must extend your rights as a manager, or add new dedicated users having that profile. Refer to the matrix above for the feasible actions per profile. To show the existing projects and their ownership, run the command: A project can become orphaned if the unique administrator left or is disabled. In this case, it is not possible to assign a new role online. Run the command When you revoke a user, you are warned if a project will be orphaned. The operation consists in copying one or several projects into a separate database file. The copied projects are not removed from the original database. The audit log stored in its dedicated database and the documents stored on the drive are not modified. After you copied the databases and the documents to the target server, you can clean the audit data to remove some irrelevant traces. Run the command It is not possible to merge two databases into one, which would be the reverse operation. Deleting a project cannot be undone. The user accounts are not deleted but their rights are removed from the projects. The action is done in the console only through the following command: The users are created by default as readers. The default password is predetermined by the general configuration variable The users must change their password before you can change their authorizations. To ensure the consistency of the documentation and the audit tables, it is not possible to delete a user account. With the federated authentication, the user accounts are created automatically. You don't need to create them in advance else two password managements will exist simultaneously. To force the use of OAuth, you must reset the password in the console. The form is available in your profile page. It is recommended to setup Pwic.wiki with HTTPS support so that the passwords cannot be intercepted in clear format. The self-signed certicates cannot be used publicly because they generate bypassable errors in your browser. The recovery of the lost passwords is an administrative task since Pwic.wiki handles neither emails, nor personal information. This command creates the user account if it does not exist, but it grants no role on the projects. Run the command The operation revokes the roles of the user on every project. If this user is the unique administrator of some projects, there are displayed. The user account is not deleted physically, and replacing it with the default ghost account is not implemented but technically possible. Run the command The rights are project-dependent. The form is accessible from the special page. The administrators only can grant new rights to the users if they changed their password once. A user must have one profile at least (ex: reader) to be able to view the project. To switch from one profile to another for a user, you should first activate the new profile, then deactivate the old profile. The profile administrator cannot be removed or disabled oneself to ensure that there is always one administrator per project. The assignment of the rights is monitored. You should also consider the separation of duties: no one should be able to do everything. The anonymous access is given by the special user account The creation of new pages is restricted to the managers in order to monitor the documentation efficiently. Each page is codified with an external identifier that can be listed alphabetically in each project overview. If you tick the checkbox to generate a knowledge page, an incremental counter is appended to the name of the page that acts as a prefix. If a page is created in reference to another page, its content is copied by default. In that way, you can dedicate a project to store the default templates. It is not possible to refer to a page that doesn't exist or for which you have no access rights. A page can be renamed and moved to another project as long as you have access rights to the target project. Each project must contain the home page A page is a collection of revisions. A new revision is created each time a page is modified. A past revision cannot be updated, but deleted under some conditions. The tags are the base to classify the pages freely. The keywords are separated by a space. You can search for a tag in the search engine by usign a hash (ex: The content of the page follows the Markdown (MD) syntax and you can use HTML markups outside of a code block. The dangerous attributes are automatically removed, but beware to not break the export to OpenDocument. If enabled by the option Saving as draft means that you are working on a page. The revision needs no particular attention, except the one of the current editor. You can delete your own drafts. They are also automatically deleted once you publish a final version. Saving as final means that the document is ready to be reviewed by the validator. It also physically deletes all your own previous revisions marked as draft unless you define the option If you want to save a document which is neither a draft, nor a final version, just save it with none of these flags. Such a revision is a milestone and can be deleted by an administrator only. The attribute Reason of the page is a mandatory text that summarizes the purpose of the modification. It is used in the history to search for old revisions at glance. The attribute Milestone of the page is an optional free comment that can be used, for example, by the manager to request a delivery date, or by the editor to announce a publishing date. You can also indicate your difficulties, the current progress, etc... The attribute Header line means that the page is displayed in the header line. It helps to structure your documentation by themes. There is no limitation in the number of header items and only a manager can set the attribute. The attribute Protected page means that the page can be modified by a manager only. A manager can change all these attributes without recreating a revision if the option You can upload several files for a page. In case it is a picture, you can also include it in the page as a figure, else as a link. All the files are listed on a gallery below the page. In any case, many rules apply in the uploading process: For any document stored in the supported formats, you can import its content as a Markdown text by clicking on the button {{pwic.emojis.hammer}}, unless you defined the option This feature has two usages: Because the manager is responsible for the organization of the pages, you must have this role for the source and destination projects. The internal links won't be updated automatically, but the list of the affected pages is displayed. The broken links can be found also in the special pages You delete a revision one by one, not the entire page at once. Moreover some restrictions apply to ensure the consistency of the documentation. A manager and an editor can delete their own drafts. An administrator can delete a revision that is neither final, nor validated. Because of the incremental numbering, the highest identifier of a deleted page can be reallocated. And the gaps in the numbering of the revisions exist by design. You must have an account to be able to read the documentation, unless the option The pages are saved into a cache to improve the overall performances of the server at the cost of the disk space. After certain technical operations or over the time, it might be needed to clear the cache with the command A page is usually read online but you can export it on your desktop in several file formats. The export to HTML is a single file that can be opened in your web browser. However, it does not embed the images, the attachments and your custom styles. By using the feature "Save as" of your web browser, you can save the page and the loaded pictures, but without the attachments. This format is the basic content used by Pwic.wiki. The file can be edited further with a classical raw text editor, but it needs a dedicated application to be rendered nicely. This file format is public but it is not a DOCX document. The file can be edited in MS Word, LibreOffice and more. The export is designed to be very similar to the HTML rendering online, like as if you copied-pasted the text in your editor. However, slight differences of layout are expectable. The success of the conversion can be altered if you use HTML tags in your pages. This format is not supported by Pwic.wiki, so it cannot be disabled in the options. By using the feature "Print" of your web browser, select a virtual printer to produce the final PDF file. When doing so, some useless elements of the page are hidden. On Windows, you can enable the PDF printer by executing the command Several conditions are required to validate a page: The administrators are able to download a project as a ZIP file containing all the pages and attached documents. The purposes are the creation of a backup or the preparation of a migration for example. The export is audited and can be disabled by setting the administrative variable Pwic.wiki can verify the internal links of the pages to ensure that: The checks apply within a given project. The links to other projects are neither reported as orphan, nor as broken. The feature serves as a general monitoring tool for the documentation. The directed graph is a visual representation of the links between the pages. It is possible to detect the pages of high importance and the ones that are insufficiently put into the light. In case you link to another project, only the targeted pages are displayed. The title of the page is hidden if you don't have access to the project. The pages in red correspond to broken links. You can download the graph as a file. After you installed GraphViz, you can regenerate the picture with the command: The search works with exact literals. If you are looking for the word The search engine doesn't build an index of words. It means that you are able to search within a word. For example, You can search for individual words, but also for sentences if you use double quotes. For exemple, You can exclude a term by adding a leading minus sign ( The terms that are not negated act as an AND condition. It means that all the terms must appear so that the page can be selected. That's why you should not use the keyword Some special search terms exist: At the end, the pages are sorted by the number of occurrences of the search terms. Each term that is not negated contribute to the score as a sum. Because of that, your search terms should be at least 3- or 4-character long. The search is case-insensitive. The search applies on the name of the page, the file name and the mime of the file. The syntax of the terms is similar to the one used for the pages. You can use inclusive and exclusive keywords, but not the special ones that contain It is not possible to search within the content of the files. Pwic.wiki logs many activities to enforce the traceability of the documentation. In case of troubles, it is possible to unwind the events and have a better understanding of the situation. Online, the administrators of a project can see the events related to the project only, like the creation of a page, a deletion, a validation... At system level, it is possible to display all the events, including the ones not related to a given project. The listing can be filtered out by date and content with the following syntax: Run the command If the users don't delete their cookies and if you use the option Run the command Some projects being completed, it is possible that they have not received any recent modifications by their owners. General figures about the server and the projects can be summarized by the command Because the audit data can grow fast, you can archive the data on a regular basis with the command In fact, the entries from the table By design, you cannot remove the entries in the short horizon of 90 days. Also note that archiving the audit data impacts the reliability of the reports above. The user page contains a public overview online of the recently modified pages (over the last 30 days) and the uploaded documents. Your own user page is mentioned in the footer. The web interface communicates with Pwic.wiki through an application programming interface (API) whose specifications are available online. You can develop external robots in the limit of your permitted authorizations. Some examples are available in the folder You can add your own paths by extending the source code. The Application Programming Interface (API) is a set of custom HTTP end-points used by the web user interface to perform all the updates in Pwic.wiki via POST requests only. All the users have access to a partial set of safe features depending on their online authentication. The API is usable for the mass automation of the public data in read and write mode. It does not integrate well with BI tools because the data model is not reflected transparently and consistently. The API cannot be turned off but you can restrict the users by their authorizations for each project. If you have a direct and local access to the server, you can use an SQLite ODBC driver locally to integrate Pwic.wiki with your favorite BI tool. However, all the data will be exposed without any filter or check. It is a critical point of failure if you leak the values of the private keys stored in the table env. There is no possible option to prevent the local use of an ODBC driver. The access to the database is restricted by the file system only. OData is a protocol to share data with external systems. Pwic.wiki implements minimalistic features (read-only, no filter, no stream, basic authentication), so that you can query the data remotely for your reporting. The tested tool is PowerBI that is often used in the business environments. First, you need to activate the OData interface through the global option Second, the OData feed is available at the following addresses: The data are filtered from source and the private ones remain hidden. The OData cannot be filtered out by a dollar-argument. An authentication through a challenge Basic applies at the lowest level only. You cannot use the federated authentication (OAuth). For your security within an untrusted network, you should enable HTTPS. The exposed data may differ depending on your authorizations. A basic use of OData is possible with an account having a role of reader on the projects that you wants to report on. The mass import of the tables in PowerBI via the shortest path is not supported for an unknown technical reason. Instead, you should add each table by its full path, which will require an authentication each time. Pwic.wiki is released under the terms of the GNU Affero General Public License v3+. For more details, refer to the file Pwic.wiki also relies on several external resources whose licenses are summarized in the following table: {{pwic.emojis.pin}} {% trans %}This instance is accessible anonymously.{% endtrans %} {{pwic.emojis.red_check}} {% trans %}Your browser is incompatible or enable JavaScript.{% endtrans %} {{pwic.emojis.globe}} {% trans %}You successfully logged out.{% endtrans %} {% trans %}You can go back to:{% endtrans %} {{pwic.emojis.padlock}} {% trans %}The page does not exist.{% endtrans %} {% trans %}Please verify your authorizations, the spelling of the link and the revision level of the page.{% endtrans %} {% trans %}You can go back to:{% endtrans %} {{ gettext('The server has started on %(date)s at %(time)s.')|format(date=pwic.up.date|escape, time=pwic.up.time|escape) }} {{ gettext('The current time on the server is %(date)s at %(time)s.')|format(date=pwic.systime.date|escape, time=pwic.systime.time|escape) }} {{ gettext('You are connected in %(protocol)s.')|format(protocol=pwic.protocol|escape) }} {{pwic.emojis.padlock}} {% trans %}You have no authorization to create a new page. Please contact your manager.{% endtrans %} {% trans %}Project:{% endtrans %} {% trans %}Page identifier:{% endtrans %}
+ {% trans %}Tags:{% endtrans %} {% trans %}Milestone:{% endtrans %} {% trans %}Project:{% endtrans %} {% trans %}Page identifier:{% endtrans %} {{pwic.emojis.padlock}} {% trans %}You are not authorized to edit the page.{% endtrans %} {% trans %}The following settings are changeable online and they are applicable to the current project only.{% endtrans %} {{ gettext('Refer to your administrator to configure the other environment variables (whose details are available in the help page), or if you want a setting to apply once for all the projects hosted on the server.')|format(project=pwic.project|urlencode) }} {% trans %}You have no orphaned page.{% endtrans %} {% trans %}These pages cannot be reached by simple clicks but are still referenced by the search engine.{% endtrans %} {% trans %}You have no broken link to a page.{% endtrans %} {% trans %}A broken link displays the error 404 because the targeted page does not exist.{% endtrans %} {% trans %}Note: the table does not check the links to the other projects.{% endtrans %} {% trans %}You have no broken link to a document.{% endtrans %} {% trans %}Managing the files without updating the pages may result in invalid links.{% endtrans %} {% trans %}Target project:{% endtrans %} {% trans %}Target page identifier:{% endtrans %}
+ {% trans %}This form can be used to rename an existing page.{% endtrans %}
+
+ {% if pwic.relations|count > 0 %}
+ {% trans %}No link will be updated within the following pages:{% endtrans %}
+ {% endif %}
+ {% trans %}Disk space usage:{% endtrans %}
+ {% if pwic.disk_space.project_max > 0 %}
+ {{pwic.disk_space.used|size2str|escape}} / {{pwic.disk_space.project_max|size2str|escape}} ({{pwic.disk_space.percentage|escape}} %)
+ {% else %}
+ {{pwic.disk_space.used|size2str|escape}}
+ {% endif %}
+ {{pwic.emojis.eye}}
+ {% if pwic.env.validated_only %}
+ {{ gettext('You are viewing the revision %(revision)d and other revisions are pending.')|format(revision=pwic.revision, project=pwic.project|urlencode, page=pwic.page|urlencode) }}
+ {% else %}
+ {{ gettext('You are viewing the revision %(revision)d, but the latest version is available.')|format(revision=pwic.revision, project=pwic.project|urlencode, page=pwic.page|urlencode) }}
+ {% endif %}
+ {{pwic.emojis.flag}} {{ gettext('This page has been validated by %(user2)s on %(sdate)s at %(stime)s.')|format(user1=pwic.valuser|urlencode, user2=pwic.valuser|escape, sdate=pwic.valdate|escape, stime=pwic.valtime|escape) }}
+ {% trans %}Related pages:{% endtrans %}
+ {% for p in pwic.relations %}
+ {% if loop.index > 1 %} – {% endif %}
+ {{p[1]|escape}}
+ {% endfor %}
+
+ {{ gettext('Revision #%(revision)d was last modified by %(author2)s {{pwic.emojis.red_check}} {% trans %}You don't have the authorizations to access this project.{% endtrans %} {% trans %}To request your access, get in touch with the support team:{% endtrans %} {% trans %}Unfortunately, there is no defined support information to request your access.{% endtrans %} {% trans %}Unfortunately, you have access to no project at all.{% endtrans %} {% trans %}Search terms:{% endtrans %}
+
+ {% if not pwic.pure_reader or not pwic.env.no_history %}
+
+
+ ,
+ {{pwic.emojis.padlock}} {% trans %}You have no authorization to create a new user. Please contact your administrator.{% endtrans %} {% trans %}Reminder: your password must be changed before you can add a new user to the selected project.{% endtrans %} {% trans %}Project:{% endtrans %} {% trans %}User name:{% endtrans %} {% trans %}Reminder: the password of a user must be changed before his roles can be changed.{% endtrans %} {% trans %}The user has no recent contribution.{% endtrans %}", 2)
+ for cell in rows[0]:
+ add_hline(" ", 2)
+ add_hline("", 1)
+ # Only one header row allowed.
+ rows = rows[1:]
+ # If no more rows, don't create a tbody.
+ if rows:
+ add_hline("", 1)
+ for row in rows:
+ add_hline("{} ".format(format_cell(cell)), 3)
+ add_hline("", 2)
+ for cell in row:
+ add_hline(" ", 2)
+ add_hline("", 1)
+ add_hline("")
+ return "\n".join(hlines) + "\n"
+
+ def _do_wiki_tables(self, text):
+ # Optimization.
+ if "||" not in text:
+ return text
+
+ less_than_tab = self.tab_width - 1
+ wiki_table_re = re.compile(
+ r"""
+ (?:(?<=\n\n)|\A\n?) # leading blank line
+ ^([ ]{0,%d})\|\|.+?\|\|[ ]*\n # first line
+ (^\1\|\|.+?\|\|\n)* # any number of subsequent lines
+ """
+ % less_than_tab,
+ re.M | re.X,
+ )
+ return wiki_table_re.sub(self._wiki_table_sub, text)
+
+ def _run_span_gamut(self, text):
+ # These are all the transformations that occur *within* block-level
+ # tags like paragraphs, headers, and list items.
+
+ text = self._do_code_spans(text)
+
+ text = self._escape_special_chars(text)
+
+ # Process anchor and image tags.
+ if "link-patterns" in self.extras:
+ text = self._do_link_patterns(text)
+
+ text = self._do_links(text)
+
+ # Make links out of things like `{} ".format(format_cell(cell)), 3)
+ add_hline("
)", break_tag, text)
+
+ return text
+
+ # "Sorta" because auto-links are identified as "tag" tokens.
+ _sorta_html_tokenize_re = re.compile(
+ r"""
+ (
+ \\* # escapes
+ (?:
+ # tag
+ ?
+ (?:\w+) # tag name
+ (?:\s+(?:[\w-]+:)?[\w-]+=(?:".*?"|'.*?'))* # attributes
+ \s*/?>
+ |
+ # auto-link (e.g., ":
+ peek_tokens = split_tokens[index : index + 3]
+ elif token == "
":
+ peek_tokens = split_tokens[index - 2 : index + 1]
+ else:
+ return False
+ except IndexError:
+ return False
+
+ return re.match(r"md5-[A-Fa-f0-9]{32}
", "".join(peek_tokens))
+
+ tokens = []
+ split_tokens = self._sorta_html_tokenize_re.split(text)
+ is_html_markup = False
+ for index, token in enumerate(split_tokens):
+ if is_html_markup and not _is_auto_link(token) and not _is_code_span(index, token):
+ sanitized = self._sanitize_html(token)
+ key = _hash_text(sanitized)
+ self.html_spans[key] = sanitized
+ tokens.append(key)
+ else:
+ tokens.append(self._encode_incomplete_tags(token))
+ is_html_markup = not is_html_markup
+ return "".join(tokens)
+
+ def _unhash_html_spans(self, text):
+ for key, sanitized in list(self.html_spans.items()):
+ text = text.replace(key, sanitized)
+ return text
+
+ def _sanitize_html(self, s):
+ if self.safe_mode == "replace":
+ return self.html_removed_text
+ elif self.safe_mode == "escape":
+ replacements = [
+ ("&", "&"),
+ ("<", "<"),
+ (">", ">"),
+ ]
+ for before, after in replacements:
+ s = s.replace(before, after)
+ return s
+ else:
+ raise MarkdownError("invalid value for 'safe_mode': %r (must be " "'escape' or 'replace')" % self.safe_mode)
+
+ _inline_link_title = re.compile(
+ r"""
+ ( # \1
+ [ \t]+
+ (['"]) # quote char = \2
+ (?P tags.
+
+ This is a combination of Markdown.pl's _DoAnchors() and
+ _DoImages(). They are done together because that simplified the
+ approach. It was necessary to use a different approach than
+ Markdown.pl because of the lack of atomic matching support in
+ Python's regex engine used in $g_nested_brackets.
+ """
+ MAX_LINK_TEXT_SENTINEL = 3000 # markdown2 issue 24
+
+ # `anchor_allowed_pos` is used to support img links inside
+ # anchors, but not anchors inside anchors. An anchor's start
+ # pos must be `>= anchor_allowed_pos`.
+ anchor_allowed_pos = 0
+
+ curr_pos = 0
+ while True: # Handle the next link.
+ # The next '[' is the start of:
+ # - an inline anchor: [text](url "title")
+ # - a reference anchor: [text][id]
+ # - an inline img: 
+ # - a reference img: ![text][id]
+ # - a footnote ref: [^id]
+ # (Only if 'footnotes' extra enabled)
+ # - a footnote defn: [^id]: ...
+ # (Only if 'footnotes' extra enabled) These have already
+ # been stripped in _strip_footnote_definitions() so no
+ # need to watch for them.
+ # - a link definition: [id]: url "title"
+ # These have already been stripped in
+ # _strip_link_definitions() so no need to watch for them.
+ # - not markup: [...anything else...
+ try:
+ start_idx = text.index("[", curr_pos)
+ except ValueError:
+ break
+ text_length = len(text)
+
+ # Find the matching closing ']'.
+ # Markdown.pl allows *matching* brackets in link text so we
+ # will here too. Markdown.pl *doesn't* currently allow
+ # matching brackets in img alt text -- we'll differ in that
+ # regard.
+ bracket_depth = 0
+ for p in range(start_idx + 1, min(start_idx + MAX_LINK_TEXT_SENTINEL, text_length)):
+ ch = text[p]
+ if ch == "]":
+ bracket_depth -= 1
+ if bracket_depth < 0:
+ break
+ elif ch == "[":
+ bracket_depth += 1
+ else:
+ # Closing bracket not found within sentinel length.
+ # This isn't markup.
+ curr_pos = start_idx + 1
+ continue
+ link_text = text[start_idx + 1 : p]
+
+ # Fix for issue 341 - Injecting XSS into link text
+ if self.safe_mode:
+ link_text = self._hash_html_spans(link_text)
+ link_text = self._unhash_html_spans(link_text)
+
+ # Possibly a footnote ref?
+ if "footnotes" in self.extras and link_text.startswith("^"):
+ normed_id = re.sub(r"\W", "-", link_text[1:])
+ if normed_id in self.footnotes:
+ self.footnote_ids.append(normed_id)
+ result = '' '%s' % (
+ normed_id,
+ normed_id,
+ len(self.footnote_ids),
+ )
+ text = text[:start_idx] + result + text[p + 1 :]
+ else:
+ # This id isn't defined, leave the markup alone.
+ curr_pos = p + 1
+ continue
+
+ # Now determine what this is by the remainder.
+ p += 1
+
+ # Inline anchor or img?
+ if text[p : p + 1] == "(": # attempt at perf improvement
+ url, title, url_end_idx = self._extract_url_and_title(text, p)
+ if url is not None:
+ # Handle an inline anchor or img.
+ is_img = start_idx > 0 and text[start_idx - 1] == "!"
+ if is_img:
+ start_idx -= 1
+
+ # We've got to encode these to avoid conflicting
+ # with italics/bold.
+ url = url.replace("*", self._escape_table["*"]).replace("_", self._escape_table["_"])
+ if title:
+ title_str = ' title="%s"' % (
+ _xml_escape_attr(title)
+ .replace("*", self._escape_table["*"])
+ .replace("_", self._escape_table["_"])
+ )
+ else:
+ title_str = ""
+ if is_img:
+ img_class_str = self._html_class_str_from_tag("img")
+ result = '
= anchor_allowed_pos:
+ safe_link = self._safe_href.match(url)
+ if self.safe_mode and not safe_link:
+ result_head = '' % (title_str)
+ else:
+ result_head = '' % (self._protect_url(url), title_str)
+ result = "%s%s" % (result_head, link_text)
+ if "smarty-pants" in self.extras:
+ result = result.replace('"', self._escape_table['"'])
+ #
allowed from curr_pos on, from
+ # anchor_allowed_pos on.
+ curr_pos = start_idx + len(result_head)
+ anchor_allowed_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[url_end_idx:]
+ else:
+ # Anchor not allowed here.
+ curr_pos = start_idx + 1
+ continue
+
+ # Reference anchor or img?
+ else:
+ match = self._tail_of_reference_link_re.match(text, p)
+ if match:
+ # Handle a reference-style anchor or img.
+ is_img = start_idx > 0 and text[start_idx - 1] == "!"
+ if is_img:
+ start_idx -= 1
+ link_id = match.group("id").lower()
+ if not link_id:
+ link_id = link_text.lower() # for links like [this][]
+ if link_id in self.urls:
+ url = self.urls[link_id]
+ # We've got to encode these to avoid conflicting
+ # with italics/bold.
+ url = url.replace("*", self._escape_table["*"]).replace("_", self._escape_table["_"])
+ title = self.titles.get(link_id)
+ if title:
+ title = (
+ _xml_escape_attr(title)
+ .replace("*", self._escape_table["*"])
+ .replace("_", self._escape_table["_"])
+ )
+ title_str = ' title="%s"' % title
+ else:
+ title_str = ""
+ if is_img:
+ img_class_str = self._html_class_str_from_tag("img")
+ result = '
= anchor_allowed_pos:
+ if self.safe_mode and not self._safe_href.match(url):
+ result_head = '' % (title_str)
+ else:
+ result_head = '' % (self._protect_url(url), title_str)
+ result = "%s%s" % (result_head, link_text)
+ if "smarty-pants" in self.extras:
+ result = result.replace('"', self._escape_table['"'])
+ #
allowed from curr_pos on, from
+ # anchor_allowed_pos on.
+ curr_pos = start_idx + len(result_head)
+ anchor_allowed_pos = start_idx + len(result)
+ text = text[:start_idx] + result + text[match.end() :]
+ else:
+ # Anchor not allowed here.
+ curr_pos = start_idx + 1
+ else:
+ # This id isn't defined, leave the markup alone.
+ # set current pos to end of link title and continue from there
+ curr_pos = p
+ continue
+
+ # Otherwise, it isn't markup.
+ curr_pos = start_idx + 1
+
+ return text
+
+ def header_id_from_text(self, text, prefix, n):
+ """Generate a header id attribute value from the given header
+ HTML content.
+
+ This is only called if the "header-ids" extra is enabled.
+ Subclasses may override this for different header ids.
+
+ @param text {str} The text of the header tag
+ @param prefix {str} The requested prefix for header ids. This is the
+ value of the "header-ids" extra key, if any. Otherwise, None.
+ @param n {int} The
tag.
+ @returns {str} The value for the header tag's "id" attribute. Return
+ None to not have an id attribute and to exclude this header from
+ the TOC (if the "toc" extra is specified).
+ """
+ header_id = _slugify(text)
+ if prefix and isinstance(prefix, str):
+ header_id = prefix + "-" + header_id
+
+ self._count_from_header_id[header_id] += 1
+ if 0 == len(header_id) or self._count_from_header_id[header_id] > 1:
+ header_id += "-%s" % self._count_from_header_id[header_id]
+
+ return header_id
+
+ def _header_id_exists(self, text):
+ header_id = _slugify(text)
+ prefix = self.extras["header-ids"].get("prefix")
+ if prefix and isinstance(prefix, str):
+ header_id = prefix + "-" + header_id
+ return header_id in self._count_from_header_id or header_id in map(lambda x: x[1], self._toc)
+
+ def _toc_add_entry(self, level, id, name):
+ if level > self._toc_depth:
+ return
+ if self._toc is None:
+ self._toc = []
+ self._toc.append((level, id, self._unescape_special_chars(name)))
+
+ _h_re_base = r"""
+ (^(.+)[ \t]{0,99}\n(=+|-+)[ \t]*\n+)
+ |
+ (^(\#{1,6}) # \1 = string of #'s
+ [ \t]%s
+ (.+?) # \2 = Header text
+ [ \t]{0,99}
+ (?%s\n\n" % (n, header_id_attr, html, n)
+
+ _h_tag_re = re.compile(
+ r"""
+ ^
tags.
+ """
+ yield 0, "
" % (self._html_class_str_from_tag("code"), c)
+
+ def _do_code_spans(self, text):
+ # * Backtick quotes are used for "
+ for tup in inner:
+ yield tup
+ yield 0, "
"
+
+ def _add_newline(self, inner):
+ # Add newlines around the inner contents so that _strict_tag_block_re matches the outer div.
+ yield 0, "\n"
+ yield from inner
+ yield 0, "\n"
+
+ def wrap(self, source, outfile=None):
+ """Return the source with a code, pre, and div."""
+ if outfile is None:
+ # pygments >= 2.12
+ return self._add_newline(self._wrap_pre(self._wrap_code(source)))
+ else:
+ # pygments < 2.12
+ return self._wrap_div(self._add_newline(self._wrap_pre(self._wrap_code(source))))
+
+ formatter_opts.setdefault("cssclass", "codehilite")
+ formatter = HtmlCodeFormatter(**formatter_opts)
+ return pygments.highlight(codeblock, lexer, formatter)
+
+ def _code_block_sub(self, match, is_fenced_code_block=False):
+ lexer_name = None
+ if is_fenced_code_block:
+ lexer_name = match.group(2)
+ codeblock = match.group(3)
+ codeblock = codeblock[:-1] # drop one trailing newline
+ else:
+ codeblock = match.group(1)
+ codeblock = self._outdent(codeblock)
+ codeblock = self._detab(codeblock)
+ codeblock = codeblock.lstrip("\n") # trim leading newlines
+ codeblock = codeblock.rstrip() # trim trailing whitespace
+
+ # Use pygments only if not using the highlightjs-lang extra
+ if lexer_name and "highlightjs-lang" not in self.extras:
+ lexer = self._get_pygments_lexer(lexer_name)
+ if lexer:
+ leading_indent = " " * (len(match.group(1)) - len(match.group(1).lstrip()))
+ return self._code_block_with_lexer_sub(codeblock, leading_indent, lexer, is_fenced_code_block)
+
+ pre_class_str = self._html_class_str_from_tag("pre")
+
+ if "highlightjs-lang" in self.extras and lexer_name:
+ code_class_str = ' class="%s language-%s"' % (lexer_name, lexer_name)
+ else:
+ code_class_str = self._html_class_str_from_tag("code")
+
+ if is_fenced_code_block:
+ # Fenced code blocks need to be outdented before encoding, and then reapplied
+ leading_indent = " " * (len(match.group(1)) - len(match.group(1).lstrip()))
+ if codeblock:
+ # only run the codeblock through the outdenter if not empty
+ leading_indent, codeblock = self._uniform_outdent(codeblock, max_outdent=leading_indent)
+
+ codeblock = self._encode_code(codeblock)
+
+ if lexer_name == "mermaid" and "mermaid" in self.extras:
+ return '\n%s
\n' % (
+ leading_indent,
+ codeblock,
+ )
+
+ return "\n%s
\n" % (leading_indent, pre_class_str, code_class_str, codeblock)
+ else:
+ codeblock = self._encode_code(codeblock)
+
+ return "\n%s\n
\n" % (pre_class_str, code_class_str, codeblock)
+
+ def _code_block_with_lexer_sub(self, codeblock, leading_indent, lexer, is_fenced_code_block):
+ if is_fenced_code_block:
+ formatter_opts = self.extras["fenced-code-blocks"] or {}
+ else:
+ formatter_opts = {}
+
+ def unhash_code(codeblock):
+ for key, sanitized in list(self.html_spans.items()):
+ codeblock = codeblock.replace(key, sanitized)
+ replacements = [("&", "&"), ("<", "<"), (">", ">")]
+ for old, new in replacements:
+ codeblock = codeblock.replace(old, new)
+ return codeblock
+
+ # remove leading indent from code block
+ _, codeblock = self._uniform_outdent(codeblock, max_outdent=leading_indent)
+
+ codeblock = unhash_code(codeblock)
+ colored = self._color_with_pygments(codeblock, lexer, **formatter_opts)
+
+ # add back the indent to all lines
+ return "\n%s\n" % self._uniform_indent(colored, leading_indent, True)
+
+ def _html_class_str_from_tag(self, tag):
+ """Get the appropriate ' class="..."' string (note the leading
+ space), if any, for the given tag.
+ """
+ if "html-classes" not in self.extras:
+ return ""
+ try:
+ html_classes_from_tag = self.extras["html-classes"]
+ except TypeError:
+ return ""
+ else:
+ if isinstance(html_classes_from_tag, dict):
+ if tag in html_classes_from_tag:
+ return ' class="%s"' % html_classes_from_tag[tag]
+ return ""
+
+ def _do_code_blocks(self, text):
+ """Process Markdown `%s\n
` blocks."""
+ code_block_re = re.compile(
+ r"""
+ (?:\n\n|\A\n?)
+ ( # $1 = the code block -- one or more lines, starting with a space/tab
+ (?:
+ (?:[ ]{%d} | \t) # Lines must start with a tab or a tab-width of spaces
+ .*\n+
+ )+
+ )
+ ((?=^[ ]{0,%d}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
+ # Lookahead to make sure this block isn't already in a code block.
+ # Needed when syntax highlighting is being used.
+ (?!([^<]|<(/?)span)*\
)
+ """
+ % (self.tab_width, self.tab_width),
+ re.M | re.X,
+ )
+ return code_block_re.sub(self._code_block_sub, text)
+
+ _fenced_code_block_re = re.compile(
+ r"""
+ (?:\n+|\A\n?|(?<=\n))
+ (^[ \t]*`{3,})\s{0,99}?([\w+-]+)?\s{0,99}?\n # $1 = opening fence (captured for back-referencing), $2 = optional lang
+ (.*?) # $3 = code block content
+ \1[ \t]*\n # closing fence
+ """,
+ re.M | re.X | re.S,
+ )
+
+ def _fenced_code_block_sub(self, match):
+ return self._code_block_sub(match, is_fenced_code_block=True)
+
+ def _do_fenced_code_blocks(self, text):
+ """Process ```-fenced unindented code blocks ('fenced-code-blocks' extra)."""
+ return self._fenced_code_block_re.sub(self._fenced_code_block_sub, text)
+
+ # Rules for a code span:
+ # - backslash escapes are not interpreted in a code span
+ # - to include one or or a run of more backticks the delimiters must
+ # be a longer run of backticks
+ # - cannot start or end a code span with a backtick; pad with a
+ # space and that space will be removed in the emitted HTML
+ # See `test/tm-cases/escapes.text` for a number of edge-case
+ # examples.
+ _code_span_re = re.compile(
+ r"""
+ (?%s spans.
+ #
+ # * You can use multiple backticks as the delimiters if you want to
+ # include literal backticks in the code span. So, this input:
+ #
+ # Just type ``foo `bar` baz`` at the prompt.
+ #
+ # Will translate to:
+ #
+ #
foo `bar` baz
at the prompt.`bar`
...
+ return self._code_span_re.sub(self._code_span_sub, text)
+
+ def _encode_code(self, text):
+ """Encode/escape certain characters inside Markdown code runs.
+ The point is that in code, these characters are literals,
+ and lose their special Markdown meanings.
+ """
+ replacements = [
+ # Encode all ampersands; HTML entities are not
+ # entities within a Markdown code span.
+ ("&", "&"),
+ # Do the angle bracket song and dance:
+ ("<", "<"),
+ (">", ">"),
+ ]
+ for before, after in replacements:
+ text = text.replace(before, after)
+ hashed = _hash_text(text)
+ self._code_table[text] = hashed
+ return hashed
+
+ def _wavedrom_block_sub(self, match):
+ # if this isn't a wavedrom diagram block, exit now
+ if match.group(2) != "wavedrom":
+ return match.string[match.start() : match.end()]
+
+ # dedent the block for processing
+ lead_indent, waves = self._uniform_outdent(match.group(3))
+ # default tags to wrap the wavedrom block in
+ open_tag, close_tag = '"
+
+ # check if the user would prefer to have the SVG embedded directly
+ if not isinstance(self.extras["wavedrom"], dict):
+ embed_svg = True
+ else:
+ # default behaviour is to embed SVGs
+ embed_svg = self.extras["wavedrom"].get("prefer_embed_svg", True)
+
+ if embed_svg:
+ try:
+ import wavedrom
+
+ waves = wavedrom.render(waves).tostring()
+ open_tag, close_tag = "\1", text)
+ return text
+
+ _underline_re = re.compile(r"(?)(?=\S)(.+?)(?<=\S)(?)", re.S)
+
+ def _do_underline(self, text):
+ text = self._underline_re.sub(r"\1", text)
+ return text
+
+ _tg_spoiler_re = re.compile(r"\|\|\s?(.+?)\s?\|\|", re.S)
+
+ def _do_tg_spoiler(self, text):
+ text = self._tg_spoiler_re.sub(r".+?
)", re.S)
+
+ def _dedent_two_spaces_sub(self, match):
+ return re.sub(r"(?m)^ ", "", match.group(1))
+
+ def _block_quote_sub(self, match):
+ bq = match.group(1)
+ is_spoiler = "spoiler" in self.extras and self._bq_all_lines_spoilers.match(bq)
+ # trim one level of quoting
+ if is_spoiler:
+ bq = self._bq_one_level_re_spoiler.sub("", bq)
+ else:
+ bq = self._bq_one_level_re.sub("", bq)
+ # trim whitespace-only lines
+ bq = self._ws_only_line_re.sub("", bq)
+ bq = self._run_block_gamut(bq) # recurse
+
+ bq = re.sub("(?m)^", " ", bq)
+ # These leading spaces screw with content, so we need to fix that:
+ bq = self._html_pre_block_re.sub(self._dedent_two_spaces_sub, bq)
+
+ if is_spoiler:
+ return '
\n%s\n
\n\n' % bq
+ else:
+ return "\n%s\n
\n\n" % bq
+
+ def _do_block_quotes(self, text):
+ if ">" not in text:
+ return text
+ if "spoiler" in self.extras:
+ return self._block_quote_re_spoiler.sub(self._block_quote_sub, text)
+ else:
+ return self._block_quote_re.sub(self._block_quote_sub, text)
+
+ def _form_paragraphs(self, text):
+ # Strip leading and trailing lines:
+ text = text.strip("\n")
+
+ # Wrap
",
+ ]
+
+ if not self.footnote_title:
+ self.footnote_title = "Jump back to footnote %d in the text."
+ if not self.footnote_return_symbol:
+ self.footnote_return_symbol = "↩"
+
+ # self.footnotes is generated in _strip_footnote_definitions, which runs re.sub on the whole
+ # text. This means that the dict keys are inserted in order of appearance. Use the dict to
+ # sort footnote ids by that same order
+ self.footnote_ids.sort(key=lambda a: list(self.footnotes.keys()).index(a))
+ for i, id in enumerate(self.footnote_ids):
+ if i != 0:
+ footer.append("")
+ footer.append('" % indent())
+ h_stack.append(level)
+ elif level == h_stack[-1]:
+ lines[-1] += ""
+ else:
+ while level < h_stack[-1]:
+ h_stack.pop()
+ if not lines[-1].endswith(""):
+ lines[-1] += ""
+ lines.append("%s
" % indent())
+ lines.append('%s=Et)return pt(e,f,c,s,d);if(l&&!d&&2===f.length&&st(f[1^p]))return f[1^p];if(l&&d&&1===f.length&&st(d))return d;var v=e&&e===this.ownerID,g=l?d?c:c^u:c|u,_=l?d?yt(f,p,d,v):_t(f,p,v):bt(f,p,d,v);return v?(this.bitmap=g,this.nodes=_,this):new Ge(e,g,_)},Ze.prototype.get=function(e,t,n,r){void 0===t&&(t=Oe(n));var o=(0===e?t:t>>>e)&y,a=this.nodes[o];return a?a.get(e+m,t,n,r):r},Ze.prototype.update=function(e,t,n,r,o,a,i){void 0===n&&(n=Oe(r));var s=(0===t?n:n>>>t)&y,u=o===b,c=this.nodes,l=c[s];if(u&&!l)return this;var p=it(l,e,t+m,n,r,o,a,i);if(p===l)return this;var f=this.count;if(l){if(!p&&--fs)return q();var e=o.next();return r||t===M?e:U(t,u-1,t===N?void 0:e.value[1],e)}))},c}function on(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var a=this;if(o)return this.cacheResult().__iterate(r,o);var i=0;return e.__iterate((function(e,o,s){return t.call(n,e,o,s)&&++i&&r(e,o,a)})),i},r.__iteratorUncached=function(r,o){var a=this;if(o)return this.cacheResult().__iterator(r,o);var i=e.__iterator(R,o),s=!0;return new F((function(){if(!s)return q();var e=i.next();if(e.done)return e;var o=e.value,u=o[0],c=o[1];return t.call(n,c,u,a)?r===R?e:U(r,u,c,e):(s=!1,q())}))},r}function an(e,t,n,r){var o=bn(e);return o.__iterateUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterate(o,a);var s=!0,u=0;return e.__iterate((function(e,a,c){if(!s||!(s=t.call(n,e,a,c)))return u++,o(e,r?a:u-1,i)})),u},o.__iteratorUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterator(o,a);var s=e.__iterator(R,a),u=!0,c=0;return new F((function(){var e,a,l;do{if((e=s.next()).done)return r||o===M?e:U(o,c++,o===N?void 0:e.value[1],e);var p=e.value;a=p[0],l=p[1],u&&(u=t.call(n,l,a,i))}while(u);return o===R?e:U(o,a,l,e)}))},o}function sn(e,t){var n=s(e),o=[e].concat(t).map((function(e){return i(e)?n&&(e=r(e)):e=n?se(e):ue(Array.isArray(e)?e:[e]),e})).filter((function(e){return 0!==e.size}));if(0===o.length)return e;if(1===o.length){var a=o[0];if(a===e||n&&s(a)||u(e)&&u(a))return a}var c=new te(o);return n?c=c.toKeyedSeq():u(e)||(c=c.toSetSeq()),(c=c.flatten(!0)).size=o.reduce((function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}}),0),c}function un(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var a=0,s=!1;function u(e,c){var l=this;e.__iterate((function(e,o){return(!t||cs&&(n=s-u),a=n;a>=0;a--){for(var p=!0,f=0;fo&&(r=o):r=o;var a=t.length;if(a%2!=0)throw new TypeError("Invalid hex string");r>a/2&&(r=a/2);for(var i=0;io)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var a=!1;;)switch(r){case"hex":return b(this,e,t,n);case"utf8":case"utf-8":return _(this,e,t,n);case"ascii":return w(this,e,t,n);case"latin1":case"binary":return E(this,e,t,n);case"base64":return x(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return C(this,e,t,n);default:if(a)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),a=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var k=4096;function O(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;oSystem support
+ Matrix of the authorizations
+
+
+
+
+
+ Action Admin Manager Editor Validator Reader
+ Setup the server {{pwic.emojis.check}}
+ Create a backup {{pwic.emojis.check}} (console)
+ Create a project {{pwic.emojis.check}} (console)
+ Take over a project {{pwic.emojis.check}} (console)
+ Split a project {{pwic.emojis.check}} (console)
+ Delete a project {{pwic.emojis.check}} (console)
+ Create a user account {{pwic.emojis.check}}
+ Change the own password {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}}
+ Change the password of another user {{pwic.emojis.check}} (console)
+ Grant the authorizations {{pwic.emojis.check}}
+ Create a page {{pwic.emojis.check}}
+ Modify a page {{pwic.emojis.check}} {{pwic.emojis.check}}
+ Move a page {{pwic.emojis.check}}
+ Delete a page {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial)
+ Read a page {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}}
+ View the history of a page {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}}
+ Export a page {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial)
+ Validate a revision {{pwic.emojis.check}}
+ Export a project {{pwic.emojis.check}}
+ Verify the links {{pwic.emojis.check}}
+ Graph of the links {{pwic.emojis.check}}
+ Search across the pages {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}}
+ Search across the documents {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}}
+ View the activity log {{pwic.emojis.check}}
+ View the activity of a user {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}} {{pwic.emojis.check}}
+ Automate Pwic.wiki (API) {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial)
+ Report into BI (OData) {{pwic.emojis.check}} {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial) {{pwic.emojis.check}} (partial)
+ Read the licences {{pwic.emojis.check}} {{pwic.emojis.finger_up}} Setup the server
+ README.md
available at the root directory of the application to know more about the default setup.Running commands
+ python3 pwic_admin.py [arguments]
under Linux or python pwic_admin.py [arguments]
under Windows../pa [arguments]
under Linux or pa.bat [arguments]
under Windows. By convenience, the long syntax for Linux is used below.Initialize the database
+ python3 pwic_admin.py init-db
.python3 pwic_admin.py unlock-db
else restart the server.Set the parameters
+ python3 pwic_admin.py set-env --help
, you can define the following parameters that will change Pwic.wiki's behavior.keep_sessions
.
+
+
+
+ Name
+ Project-dependent
+ Changeable online
+ Description
+
+
+
+ api_cors
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Enable CORS for the API from the origins separated by a space listed in this option (example:
+ http://site1.tld https://site2.tld
). The value *
allows all the origins.
+
+
+ api_expose_markdown
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Expose the content of the pages in the API
+ /api/project/get
to allow the full copy of the documentation.
+
+
+ api_restrict
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Partially restrict the availability of the public API to the readers. It is recommended if you enable the option
+ no_login
.
+
+
+ audit_range
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Number of days in the past for the online visibility of the audit data that are restricted to the administrators. Define the value
+ -1
to switch off the listing. The archived audit data are not visible online.
+
+
+ auto_join
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Any user is granted as a reader depending on the value of the option:
+
+
- active
: automatically when the user connects to Pwic.wiki,
+
- sso
: same as active
for the federated authentication only,
+
- passive
: when the user opens the project on purpose.
+
You can add any user manually at any time. To exclude a user alongside with the automatic join, simply disable (not remove) the user from the management screen of the users. The option is useful when you need a public workspace for your team members but you don't want to manage the initial access manually. The option must be defined by project, not globally.
+
+
+ base_url
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Define the base HTTP(S) address of your website so that the links can be rebuilt correctly when some data are exported. Don't write the trailing character
+ /
. Restart the server after the option is set. Verify the option https
.
+
+
+ client_size_max
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ If you handle pages larger than 1 MB, you should enlarge the quantity of data that you can submit by HTTP request else you receive the error 413 Request entity too large. This setting must be configured as well in your reverse proxy server (if you have one).
+
+
+
+ compressed_cache
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ If the option
+ no_cache
is disabled, you have the choice to compress the HTML cache of the pages to save some disk space in the database, but a little CPU effort is required to decompress all the output pages. Restart the server after the option is modified and clear the cache the use the new option.
+
+
+ copyright_years
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Override the copyright years in the footer so that it corresponds to your own website.
+
+
+
+ css
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Define absolute or relative HTTP locations for additional CSS files separated by
+ ;
. This applies as a default theme.
+
+
+ css_dark
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Define absolute or relative HTTP locations for additional CSS files separated by
+ ;
. This applies as an overlay of the default theme and if dark_theme
is activated.
+
+
+ css_printing
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Define absolute or relative HTTP locations for additional CSS files separated by
+ ;
. This applies as an overlay of the default theme (the dark theme doesn't apply here) when the user prints the page. This option has no effect on no_printing
.
+
+
+ dark_theme
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Enable the dark mode that is less aggressive in terms of brightness.
+
+
+
+ db_async
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Disable the synchronous commit logic of the database to increase significantly its speed. The increased risk to lose data is the compensation. Restart the server after the option is modified.
+
+
+
+ document_name_regex
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Set a regular expression to force any uploaded files to respect a strict naming convention. For example, you can use the pattern
+ \.(7z|gz|zip)$
to allow some file extensions only. The file names are always checked in lower case.
+
+
+ document_pixels_max
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Define the maximal number of pixels (simple multiplication of the width by the height) for an uploaded picture. For information, a standard screenshot is around 2 millions pixels.
+
+
+
+ document_size_max
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ All the uploaded files must have a size in bytes lower or equal than this limit. Set the value
+ 0
to disable the upload of the files.
+
+
+ edit_time_min
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Apply a minimum number of seconds between two edits. This limit does not apply to the managers.
+
+
+
+ emojis
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ List of emojis that can be suggested when you edit a page. They are written in hexadecimal notation and separated by a space. For example,
+ 2705 274C 2754
will propose ✅ ❌ ❔
. Many websites can help you to identify the Unicode symbols. If the option has the value *
, all the default emojis are loaded.
+
+
+ export_project_revisions
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Include all the revisions of the pages when you export a project as a ZIP file. It increases the memory footprint.
+
+
+
+ feed_size
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Maximal number of entries in the ATOM and RSS feeds.
+
+
+
+ file_formats_disabled
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ List of the file extensions separated with a space that cannot be used to export a page. The value
+ *
disables all the formats.
+
+
+ fixed_templates
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ The layout of the website is loaded once. This option is recommended in your productive environment to increase the performances. You must restart Pwic.wiki after an update is applied to the source code.
+
+
+
+ heading_mask
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Codify the headers according to the given format. The default one is
+ 1.1.1.1.1.1.
. There are 6 levels maximum. You can use the Arabic numerals (1
), the Roman notation up to 4999 in uppercase (I
) or in lowercase (i
), a letter in uppercase (A
) or in lowercase (a
). The separator is a free character. For example, you can define A)1)a-a.1.a.
. The implicit tag name for the header is named with #p
followed by the shortest codification, in lowercase and with an underscore (ex: #pi_1_a_a
).
+
+
+ http_404
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Enforce the usage of strict HTTP errors
+ 404 Not found
instead of soft redirections. The option interacts with your SEO.
+
+
+ http_log_file
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ - Define the absolute or relative file name to log passively all the HTTP requests. There is no log file when the option is empty.
+
+
- The option is effective upon the restart of the server.
+
- Because the log file is exclusively locked, you must terminate Pwic.wiki before you can rotate the logs with the command python3 pwic_admin.py rotate-logs
.
+
- The log file includes neither user names, nor processed data. It should not be mistaken with the internal audit tables that are updated by name when a relevant write operation occurs in the database.
+
+
+ http_log_format
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ The format of the HTTP log file can be modified according to this specification.
+
+
+
+ http_referer
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Enable the check on the HTTP referer for the POST requests (that is for the API) in regards of the option
+ base_url
that must be defined. This security feature is not reliable and old-fashioned. The option does not apply on the static files. Restart the server after the option is set.
+
+
+ https
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Enable HTTPS provided that you have installed or generated the appropriate certificates whose paths are defined by the internal constants
+ PRIVATE_KEY
and PUBLIC_KEY
. The new value is effective after the restart of the server.
+
+
+ ip_filter
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ List of IP addresses or IP networks separated by a space. The unauthorized addresses are prefixed by the tilde character
+ ~
and used as AND conditions. The authorized addresses are OR conditions and must be true at least once. Use the prefix #
to ignore a value. The IP address used for the check is deducted from the HTTP headers through a custom code. The option does not affect the static files. You need to restart the server to consider the made changes.
+
+
+ keep_drafts
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ By default, the drafts are considered as a work in progress, so they are not important milestones for the traceability of the documentation. This option prevents the automatic deletion of the own drafts when a final page is saved. The drafts remain deletable manually.
+
+
+
+ keep_sessions
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ The session of a user is saved in a client-side encrypted cookie, so nobody can change its values. This option prevents the internal cryptographic key from being regenerated when the server starts. The effects are:
+
+
- The sessions of the users are never terminated, unless they expire after some time due to the option session_expiry
or you restart Pwic.wiki with the command line option --new-session
.
+
- While they are writing, the users don't have to reconnect in another tab after the server is restarted, but the session expiry is still applicable.
+
- The following message disappear from the console: "Cannot decrypt cookie value, create a new fresh session".
+
Warning: the cryptographic key is stored in the database when the option is enabled. Leaking its value may compromise all the data stored in the database.
+
+
+ language
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Language code used on the project on 2 characters that follows the codification ISO 639-1. If the value is not specified, the language of the connection is used.
+
+
+
+ legal_notice
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ A legal text can be displayed at the bottom of the pages.
+
+
+
+ link_new_tab
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ The external links will open in a new tab. You don't need to regenerate the cache after the value is changed because the option is dynamic client-side.
+
+
+
+ link_nofollow
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ The external links are marked as
+ rel="nofollow"
and don't contribute to the search engine optimization (SEO). The option applies server-side on the dynamic content only, so the cache must be regenerated if the option is changed. The option is not recommended by default.
+
+
+ magic_bytes
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Pwic.wiki has a predefined list of usual file types and their signature. This security feature ensures that a file has the expected extension of its first bytes. Your local mimes can be listed by running the command
+ python pwic_admin.py show-mime
.
+
+
+ maintenance
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ The value is displayed on the pages to inform the users of a planned maintenance. The upload and the deletion of the documents are immediately disabled.
+
+
+
+ manifest
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Enable the progressive web application (PWA) for the project. The web browsers expect your website be setup with HTTPS, either with the option `https`, or behind a reverse proxy server.
+
+
+
+ mathjax
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Enable the renderer of mathematical expressions. As a special constraints, the sign
+ \
must be doubled. For example, the usual form \(\sqrt{x^2}\)
should be written as \\(\\sqrt{x^2}\\)
in the online editor.
+
+
+ message
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Show an online message for informational purposes only. No HTML is allowed.
+
+
+
+ no_cache
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Disable the caching of the pages for debugging purposes. When the option is enabled, the existing cache can be cleared from the administration console only.
+
+
+
+ no_copy_code
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Disable the one-click copy of the source code rendered in the pages. The option does not react to the option
+ no_text_selection
.
+
+
+ no_dictation
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Disable the voice recognition when you edit a page. It contributes to a higher level of confidentiality.
+
+
+
+ no_document_conversion
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Disable the possibility to convert an attached document to Markdown when you edit a page.
+
+
+
+ no_document_list
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Hide the attachments at the end of the pages. You should then include direct links in your pages.
+
+
+
+ no_export_project
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Disable the export of the project as an archive for the online administrators.
+
+
+
+ no_feed
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Disable the ATOM and RSS feeds.
+
+
+
+ no_graph
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Disable the ability to render the pages of the project in a graph.
+
+
+
+ no_heading
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Hide the automatic codification of the headers. The option
+ heading_mask
is still applicable for the internal naming of the paragraphs, so that you can link directly to them. The option has no effect on the export to OpenDocument.
+
+
+ no_help
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Hide the current help from the footer but the page remains accessible by direct access.
+
+
+
+ no_highlight
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Disable the highlight of the source codes. Restart the server after the option is modified. The dependent Python package
+ pygments
is optional during the setup.
+
+
+ no_history
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Disable the ability to see and search the revisions of a page (a fortiori to access one of its revisions explicitly) when the user is a pure reader. In other words, the pure readers only see the latest revision, or the latest validated revision if the option
+ validated_only
is used too.
+
+
+ no_link_review
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Disable the ability to check the broken and orphaned links of a project.
+
+
+
+ no_login
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ - Skip the login screen and assign the anonymous role to your visitors.
+
+
- On contrary to the option auto_join
, they don't need a user account to read the pages.
+
- You can hide a project by disabling the user account {{pwic.constants.anonymous_user}}
.
+
- The option robots
is recommended when the option is turned on.
+
+
+
+ no_new_user
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ When the option is enabled, you can assign only existing user accounts to your project. To create a new user account, use the administration console of Pwic.wiki. The option has no effect on the federated authentication.
+
+
+
+ no_printing
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ To save some trees, enable a basic protection to prevent the printing of a page. It also disables the export to PDF through a virtual printer.
+
+
+
+ no_search
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Disable the search engine and the search link that you can add to your browser.
+
+
+
+ no_sitemap
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Disable the sitemap for your project. This publicly generated file helps the crawlers to index your website especially when it is accessible anonymously (refer to the option
+ no_login
).
+
+
+ no_sort_table
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Disable the ability to sort the tables by clicking on their header line.
+
+
+
+ no_space_page
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ When a page is created or moved, the option replaces the spaces in its name by an underscore (
+ _
).
+
+
+ no_table_csv
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Hide the caption at the end on the long tables that allow their download as a CSV file.
+
+
+
+ no_text_selection
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Enable a basic protection to prevent the user from playing around with his web browser to copy the text of the main page. It also forces the readers to focus on reading the page.
+
+
+
+ odata
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Enable the OData service to report Pwic.wiki into your favorite BI system.
+
+
+
+ odt_image_height_max
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Maximal height of the pictures exported to OpenDocument expressed in unit
+ cm
, in
, mm
, pc
, pt
or px
. The value 600px
is used by default.
+
+
+ odt_image_width_max
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Maximal width of the pictures exported to OpenDocument expressed in unit
+ cm
, in
, mm
, pc
, pt
or px
. The value 900px
is used by default.
+
+
+ odt_page_height
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Height of the paper when you export a page to OpenDocument and expressed in unit
+ cm
, in
, mm
, pc
, pt
or px
. The value 29.7cm
is used by default (format A4).
+
+
+ odt_page_landscape
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ With this option, the page layout is landscape when you export a page to OpenDocument. By default, the portrait layout is used.
+
+
+
+ odt_page_margin
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Size of the borders of the paper exported to OpenDocument. The value should be expressed in unit
+ cm
, in
, mm
, pc
, pt
or px
. The default value 2.5cm
(~1 in) is used by convenience.
+
+
+ odt_page_width
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Width of the paper when you export a page to OpenDocument and expressed in unit
+ cm
, in
, mm
, pc
, pt
or px
. The value 21cm
is used by default (format A4).
+
+
+ page_count_max
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Maximal number of pages that can be defined per project.
+
+
+
+ password_regex
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Set a regular expression to force the passwords to respect a predefined format. Few examples can be found here. The option does not apply when you reset the password from the console.
+
+
+
+ project_size_max
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Define the maximal disk space in bytes allowed for all the attached documents of a project.
+
+
+
+ pwic_audit_id
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ This option is automatically set in the backup databases only to identify the last known event of the audit log. In case you need to restore the database, you can clean and reuse the existing audit table. The option is used for internal purposes and it cannot be managed manually.
+
+
+
+ pwic_session
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Non-changeable and private key for the handling of the sessions. Refer to
+ keep_sessions
for more details. The value must never be disclosed.
+
+
+ quick_fix
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Under some conditions, a new revision is not created if the content of the page is not changed. A manager can then fix the attributes of a page quickly on behalf of the original author of the latest revision.
+
+
+
+ registration_link
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ HTTP link to request your access to the website. You cannot register yourself.
+
+
+
+ remote_url
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ This option allows the conversion of a remote web page into Markdown when you edit a page. For technical reasons related to CORS, the download is made by the server. The fetched URLs are added to the audit log.
+
+
+
+ revision_count_max
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Maximal number of revisions that can be defined per page. The deleted revisions don't matter for the total.
+
+
+
+ revision_size_max
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Maximal number of characters per revision. If needed, adjust the option
+ client_size_max
accordingly.
+
+
+ robots
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+
+ - The meta information
+ robots
impacts the search engine optimization (SEO) of your projects in case it is made public through the option no_login
.
+
- The parameter is optionally considered by the crawler. Several values separated with a space are available: archive
, noarchive
, follow
, nofollow
, imageindex
, noimageindex
, index
, noindex
, snippet
, nosnippet
, translate
and notranslate
.
+
- Refer also to the related option seo_hide_revs
.
+
- The values are overridden for some specific pages.
+
+
+ rstrip
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Remove the trailing space characters when a page is saved.
+
+
+
+ seo_hide_revs
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ The old revisions of a page are automatically marked as non-indexable for the public search engines. This dynamic rule overrides some values defined in the option
+ robots
. The option is incompatible with validated_only
.
+
+
+ session_expiry
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Number of seconds after which a user is automatically disconnected. Don't use a too small value. When a user edits a page, Pwic.wiki warns if the session is lost to not waste the work in progress. The option is effective upon the restart of the server.
+
+
+
+ show_members_max
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Reduce the display of the members in the special page if their number is greater than the value of this option. Indeed, the list can be big in large organizations. Only the lists of readers and editors are reduced, so that you can always contact the deciders.
+
+
+
+ skipped_tags
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Specify the list of additional HTML elements separated by a space that must be removed during the rendering of the pages. This filter applies to the export to OpenDocument too. Some tags are mandatorily removed for security reasons. You should regenerate the cache if you make the option more permissive.
+
+
+
+ strict_cookies
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Enable the strict SameSite for the cookies, but the federated authentication cannot be used anymore. Restart the server after the option is set.
+
+
+
+ support_email
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ A user can write to this email address to get support.
+
+
+
+ support_phone
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ A user can call this phone number to get support.
+
+
+
+ support_text
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Free text to describe how to get in touch with the support team. You cannot use HTML.
+
+
+
+ support_url
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Portal address that a user can use to find an extended help.
+
+
+
+ title
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Suffix appended to the title of the pages that can be used to name the website.
+
+
+
+ totp
{{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Enable the two-factor authentication (2FA TOTP). The existing secret keys are not removed when the option is disabled. The user accounts can be managed at any moment.
+
+
+
+ validated_only
{{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ Force the default redirection of the pure readers to the last validated revision of a page if it exists. This feature allows the editors to prepare the upcoming revisions of a page without showing them by default, and to publish only validated pages to the end-users. As a complement, use the option
+ no_history
to hide the history of the page.
+
+
+ zip_no_exec
{{pwic.emojis.green_check}}
+ {{pwic.emojis.red_check}}
+ Forbid the upload of ZIP files that contain an executable content.
+ python3 pwic_admin.py show-env
.python3 pwic_admin.py repair-env
. This could happen after Pwic.wiki is upgraded.Federated authentication
+
+
+
+
+
+ Name
+ Description
+
+ github
+ google
+ microsoft
+
+
+ base_url
Refer above.
+ {{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+
+
+
+ oauth_provider
Name of the third-party identity provider:
+
+
- github
(documentation)
+
- google
(documentation)
+
- microsoft
(documentation)
+
- No value means that the feature is disabled
+ {{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+
+
+
+ oauth_tenant
The tenant identifies your organization.
+ {{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ {{pwic.emojis.green_check}}
+
+
+
+ oauth_identifier
Unique identifier of the application as declared in the third-party identity provider.
+ {{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+
+
+
+ oauth_secret
Secret password to connect to the third-party identity provider. The saved value is protected in Pwic.wiki against its public exposure.
+ {{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+ {{pwic.emojis.green_check}}
+
+
+
+ oauth_domains
List of the authorized domains for the email of the user, separated by a space and ordered by descending priority.
+ Optional
+ Optional
+ Optional
+
+
+
+ strict_cookies
Remove this option.
+ {{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ {{pwic.emojis.red_check}}
+ Two-factor authentication (2FA TOTP)
+
+
+ base_url
and totp
,./pa reset-totp [username]
(Linux) or pa.bat reset-totp [username]
(Windows) to generate a secret key for the given user account,Reverse proxy
+ on_ip_header
and to configure the format of the HTTP log.Compress the static files
+ python3 pwic_admin.py compress-static
. This will reduce a bit the bandwidth and the loading time.SQL queries
+ python3 pwic_admin.py execute-sql
allows you to execute online SQL statements in the console. It is very useful to upgrade Pwic.wiki and perform special operations.{{pwic.emojis.finger_up}} Create a backup
+ maintenance
. Note that it disables the capability to attach or update new documents to the pages.python3 pwic_admin.py shutdown-server
, and run the command python3 pwic_admin.py create-backup
. A new file is created with the current time stamp. The name of the backup file can be extracted with sed
if you have further scripting needs. Basically, it is a copy of the database without the documents that remain in the dedicated folder of the projects.PWIC_SALT
is the same. To resynchronize the documents with the database and vice versa, run the command python3 pwic_admin.py repair-documents --test
from the console.db/
with your own administration tools.{{pwic.emojis.finger_up}} Create a project
+ python3 pwic_admin.py create-project --help
for further details.python3 pwic_admin.py show-projects
.{{pwic.emojis.finger_up}} Take over a project
+ python3 pwic_admin.py takeover-project --help
for further details.{{pwic.emojis.finger_up}} Split a project
+ python3 pwic_admin.py split-project --help
for further details.{{pwic.emojis.finger_up}} Delete a project
+ python3 pwic_admin.py delete-project --help
.{{pwic.emojis.finger_up}} Create a user account
+ PWIC_DEFAULT_PASSWORD
.{{pwic.emojis.finger_up}} Change the own password
+ {% if pwic.user != '' %}
+ {{pwic.emojis.finger_up}} Change the password of another user
+ python3 pwic_admin.py reset-password --help
for further details.{{pwic.emojis.finger_up}} Revoke a user
+ python3 pwic_admin.py revoke-user --help
for further details.{{pwic.emojis.finger_up}} Grant the authorizations
+ {{pwic.constants.anonymous_user|escape}}
. The account cannot be removed. You can disable it or enhance its rights (excluding administrator).{{pwic.emojis.finger_up}} Create a page
+ {{pwic.constants.default_home|escape}}
. It is accessed by default, it is sorted in first place in the header bar, and it holds the link to the special section of the site. Thus it is not recommended to delete this particular page.{{pwic.emojis.finger_up}} Modify a page
+
+ Text content
+ #tag
). A tag counts a lot in the evaluation of the results.mathjax
, you can embed mathematical expressions supported by MathJax. As a special constraints, the sign \
must be doubled. For example, the usual form \(\sqrt{x^2}\)
should be written as \\(\\sqrt{x^2}\\)
. Because it is not possible to preview the formulas before saving, you should save your page as draft.keep_drafts
.quick_fix
is enabled.Attachments
+
+
+
+
+ document_name_regex
.document_size_max
or project_size_max
.magic_bytes
is enabled.PwicExtension.on_api_document_*
, populate the field documents.exturl
and don't keep any local copy.no_document_conversion
. The images are excluded from the processing. The embedded convertor produces a more suitable content in regards of what the renown tool Pandoc can achieve.{{pwic.emojis.finger_up}} Move a page
+
+
+ {{pwic.emojis.finger_up}} Delete a page
+ {{pwic.emojis.finger_up}} Read a page
+ no_login
and the anonymous user are enabled for the concerned projects.python3 pwic_admin.py clear-cache
. To not wait for the on-the-fly caching of the pages, you can regenerate in mass the cache with the command python3 pwic_admin.py regenerate-cache
. The cache is not used to export a page or a project.{{pwic.emojis.finger_up}} Export a page
+ html - Web page
+ md - Markdown
+ odt - OpenDocument
+ pdf - Desktop PDF
+ %windir%\System32\OptionalFeatures.exe
.{{pwic.emojis.finger_up}} Validate a revision
+
+
+
+
+ {{pwic.emojis.finger_up}} Export a project
+ no_export_project
.{{pwic.emojis.finger_up}} Verify the links
+
+
+ {{pwic.emojis.finger_up}} Graph of the links
+ dot -Tsvg input.gv -o output.svg
.{{pwic.emojis.finger_up}} Search across the pages
+ excellent
but the page contains the word exellent
(without the c
), you won't find any result. Consequently, writing without mistake is a main objective not only for search purposes, but also for the general quality of the documentation.document
will match with both document
and documents
, but documents
will match with itself only. Consequently, you should avoid the plural forms to maximize the number of results. This feature is also very important to be able to search for partial technical keywords if the documentation contains lines of code."my documentation"
will not match with my last documentation
. However, my documentation
would match with it because there is no quote (my
and documentation
are processed separately).-
). For example, -interface
will exclude all the pages that contain the word interface
. You can combine negated terms and they will act as an OR condition. It means that if one of the negated terms is part of the page, then the page is fully excluded. In case you want to search the minus sign explicitly, consider using surrounding quotes ("-interface"
).and
explicitly. It is not possible to make OR conditions with these terms, so do separate searches instead.
+
+ term
searches in the content of the page, or for the exact page identifier. This is the default behavior seen above.author:term
checks the term against the author of the latest revision.title:term
checks the term in the title of the page.:latest
searches for the most recent page.:draft
searches for drafts.:final
searches for final pages.:validated
checks that the page is validated, whoever is the validator.validator:term
checks the term against the validator of the latest revision. If the term is empty, the check returns a match, so do prefer the keyword :validated
in that case.:document
checks that the page has at least one document attached.#tag
checks that the tag is assigned to the page according to your own classification logic.{{pwic.emojis.finger_up}} Search across the documents
+ :
.{{pwic.emojis.finger_up}} View the activity log
+
+ All the activities
+ python3 pwic_admin.py show-audit --max 365 | grep grant
would return all the modifications of the users' rights over the last year.All the logins
+ python3 pwic_admin.py show-login --days 30
to show the last logins of the users over the last given days (30 by default).keep_sessions
, the users won't need to reconnect very often. Therefore an old login date is not necessarily a sign of inactivity.All the inactive writers
+ python3 pwic_admin.py show-inactivity --days 30
to highlight the inactive users who can modify the documentation.Statistics
+ python3 pwic_admin.py show-stats
.Archive the log
+ python3 pwic_admin.py archive-audit --selective 30 --complete 365
. In the selective horizon (ie. older than the given number of days), some generic entries are removed but all the core traces of the projects are kept. In the full horizon, all the entries are removed.audit
are just moved to the table audit_arch
and you can dispose of the data at your convenience through SQL commands. To remove the old entries physically, you would execute: DELETE FROM audit.audit_arch
.{{pwic.emojis.finger_up}} View the activity of a user
+ {{pwic.emojis.finger_up}} Automate Pwic.wiki (API)
+ static/api/
.{{pwic.emojis.finger_up}} Report into BI
+
+ Through API remotely
+ Through ODBC locally
+ Through OData remotely
+ odata
. Its effect is immediate.
+
+ /api/odata
: the description of the service,/api/odata/$metadata
: the schema of the data,/api/odata/table
: the data with the name table to be replaced by the real name that you want to retrieve. The paths are provided in the root description of the service.{{pwic.emojis.finger_up}} Read the licenses
+ LICENSE
.
+
+
+
+ Name
+ Tested version
+ Type
+ License
+
+
+ aiohttp
+ 3.11.11 (>=3.8)
+ Library for Python
+ Apache 2.0 License
+
+
+ aiohttp-cors
+ 0.7.0
+ Library for Python
+ Apache 2.0 License
+
+
+ aiohttp-session
+ 2.12.1 (>=2.10)
+ Library for Python
+ Apache 2.0 License
+
+
+ cash.js
+ 8.1.0
+ Library for JavaScript
+ MIT License
+
+
+ CodeMirror
+ 5.65.16
+ Library for JavaScript
+ MIT License
+
+
+ ImageSize
+ 1.4.1
+ Library for Python
+ MIT License
+
+
+ Jinja2
+ 3.1.5
+ Library for Python
+ BSD-3-Clause License
+
+
+ Markdown2
+ 2.4.13 (modified, >=2.3.10, <2.5)
+ Library for Python
+ MIT License
+
+
+ MathJax
+ 3.1.2
+ Library for JavaScript
+ Apache 2.0 License
+
+
+ Noto Sans
+ 14
+ Font
+ Open Font License
+
+
+ PrettyTable
+ 3.13.0
+ Library for Python
+ BSD License
+
+
+ pygments
+ 2.19.1
+ Library for Python
+ BSD-2-Clause License
+
+
+ pyotp
+ 2.9.0
+ Library for Python
+ MIT License
+
+
+ Python
+ 3.12.2
+ Programming language
+ Python Software Foundation License
+
+
+ Robots.txt
+ -
+ Data source
+ Freemium
+
+
+ SQLite
+ 3
+ Database engine
+ Public domain
+
+
+ SVG pan & zoom
+ 3.5.1
+ Library for JavaScript
+ BSD-2-Clause License
+
+
+ Swagger UI
+ 3.14.1
+ Library for JavaScript
+ Apache 2.0 License
+
+
+ Viz.js
+ 1.7.1
+ Library for JavaScript
+ MIT License
+
+
+ {% trans %}Server information{% endtrans %}
+ {% trans %}Environment variables{% endtrans %}
+
+
+
+
+ {% if pwic.audits|count > 0 %}
+
+
+ {% trans %}Key{% endtrans %}
+ {% trans %}Scope{% endtrans %}
+ {% trans %}Value{% endtrans %}
+ {{ gettext('Audit of the last %(n)d days')|format(n=pwic.range) }}
+
+
+ {% endif %}
+
+ {% include 'js/page-audit.js' %}
+
+
+
+ {% for audit in pwic.audits %}
+ {% trans %}Date{% endtrans %}
+ {% trans %}Time{% endtrans %}
+ {% trans %}Author{% endtrans %}
+ {% trans %}Event{% endtrans %}
+ {% trans %}Project{% endtrans %}
+ {% trans %}Page{% endtrans %}
+ {% trans %}Reference{% endtrans %}
+ {% trans %}String{% endtrans %}
+
+
+ {% endfor %}
+ {{audit.date|escape}}
+ {{audit.time|escape}}
+ {{audit.author|escape}}
+ {{audit.event|escape}}
+
+ {% if audit.project != '' %}
+ {{audit.project|escape}}
+ {% endif %}
+
+
+ {% if audit.page != '' %}
+ {{audit.page|escape}}
+ {% endif %}
+
+
+ {% if audit.reference > 0 %}
+ {% if audit.event[-9:] == '-revision' %}
+ {{audit.reference|escape}}
+ {% elif audit.event[-9:] == '-document' %}
+ {{audit.reference|escape}}
+ {% else %}
+ ?
+ {% endif %}
+ {% endif %}
+
+
+ {% if audit.string != '' %}
+ {{audit.string|escape}}
+ {% elif audit.user != '' %}
+ {{audit.user|escape}}
+ {% endif %}
+
+ Comparison of revision {{pwic.old_revision|escape}} and revision {{pwic.new_revision|escape}}
+
+
+
+ {{pwic.diff}}
+ {% trans %}New page{% endtrans %}
+
+
+
+ {% trans %}Copy in reference to another page{% endtrans %}
+
+
+ [{{pwic.page|escape}}] {{pwic.title|escape}}
+
+ {% if not pwic.manager and (pwic.protection or not pwic.editor) %}
+
+
+
+
+
+
+ {% for key in pwic.changeable_vars %}
+ {% trans %}Variable{% endtrans %}
+ {% trans %}Value{% endtrans %}
+
+
+ {% endfor %}
+
+ {{key|escape}}
+
+
+
+ [{{pwic.page|escape}}] {{pwic.title|escape}}
+
+
+
+
+
+ {% for rev in pwic.revisions %}
+ {% trans %}Compare{% endtrans %}
+ {% trans %}Revision{% endtrans %}
+ {% trans %}Flags{% endtrans %}
+ {% trans %}Author{% endtrans %}
+ {% trans %}Date{% endtrans %}
+ {% trans %}Time{% endtrans %}
+ {% trans %}Comment{% endtrans %}
+ {% trans %}Milestone{% endtrans %}
+
+
+ {% endfor %}
+
+ {{rev.revision|escape}}
+
+ {% if rev.draft %}
+ {{pwic.emojis.hourglass}}
+ {% endif %}
+ {% if rev.final %}
+ {{pwic.emojis.notes}}
+ {% endif %}
+ {% if rev.valuser != '' %}
+ {{pwic.emojis.flag}}
+ {% endif %}
+
+ {{rev.author|escape}}
+ {{rev.date|escape}}
+ {{rev.time|escape}}
+ {{rev.comment|escape}}
+ {{rev.milestone|escape}}
+ {% trans %}Orphaned pages{% endtrans %}
+ {% if pwic.orphans|count == 0 %}
+
+ {% for page in pwic.orphans %}
+
+ {% endif %}
+
+
+ {% trans %}Broken links to a page{% endtrans %}
+ {% if pwic.broken|count == 0 %}
+
+
+
+
+ {% for page in pwic.broken %}
+ {% trans %}Page{% endtrans %}
+ {% trans %}Non-existent page{% endtrans %}
+
+
+ {% endfor %}
+ {{page[0]|escape}}
+ {{pwic.emojis.sheet}} {{page[1]|escape}}
+ {% trans %}Broken links to a document{% endtrans %}
+ {% if pwic.broken_docs|count == 0 %}
+
+
+ {% endif %}
+
+
+
+ {% for page in pwic.broken_docs %}
+ {% trans %}Page{% endtrans %}
+ {% trans %}Non-existent document{% endtrans %}
+
+
+ {% endfor %}
+ {{page|escape}}
+ {{pwic.broken_docs[page]|sort|join(', ')|escape}}
+ [{{pwic.project|escape}}/{{pwic.page|escape}}] {{pwic.title|escape}}
+
+
+
+
+
+
+
+
+ {% endif %}
+
+ {% include 'js/page-move.js' %}
+
+
+ {% for row in pwic.relations %}
+ {% trans %}Link{% endtrans %}
+ {% trans %}Title{% endtrans %}
+ {% trans %}Date{% endtrans %}
+
+
+ {% endfor %}
+ /{{row.project|escape}}/{{row.page|escape}}
+ {{row.title|escape}}
+ {{row.date|escape}}
+ {% trans %}Internal support{% endtrans %}
+ {% include 'html/block-support.html' %}
+ {% endif %}
+
+
+ {% if pwic.recents|count > 0 %}
+ {% trans %}Recent updates{% endtrans %}
+
+
+ {% endif %}
+
+
+ {% if pwic.admins|count + pwic.managers|count + pwic.editors|count + pwic.validators|count + pwic.readers|count > 0 %}
+
+
+
+ {% for recent in pwic.recents %}
+ {% trans %}Date{% endtrans %}
+ {% trans %}Time{% endtrans %}
+ {% trans %}Page{% endtrans %}
+ {% trans %}Author{% endtrans %}
+ {% trans %}Comment{% endtrans %}
+ {% trans %}Milestone{% endtrans %}
+
+
+ {% endfor %}
+ {{recent.date|escape}}
+ {{recent.time|escape}}
+ {{recent.title|escape}}
+ {{recent.author|escape}}
+ {{recent.comment|escape}}
+ {{recent.milestone|escape}}
+ {% trans %}Team members{% endtrans %}
+
+ {% if pwic.admins|count > 0 %}
+
+ {% endif %}
+
+
+ {% if (pwic.pages|count > 0) and (not pwic.user|reserved_user_name) %}
+ {% trans %}All the pages of the project{% endtrans %}
+
+
+ {% endif %}
+
+
+ {% if (pwic.tags|count > 0) and (not pwic.user|reserved_user_name) %}
+
+
+ {% for page in pwic.pages %}
+ {% trans %}Page{% endtrans %}
+ {% trans %}Title{% endtrans %}
+ {% trans %}Revision{% endtrans %}
+ {% trans %}Flags{% endtrans %}
+ {% trans %}Author{% endtrans %}
+ {% trans %}Date{% endtrans %}
+ {% trans %}Time{% endtrans %}
+ {% trans %}Milestone{% endtrans %}
+
+
+ {% endfor %}
+ {{page.page|escape}}
+ {{page.title|escape}}
+ {{page.revision|escape}}
+
+ {% if page.draft %}
+ {{pwic.emojis.hourglass}}
+ {% endif %}
+ {% if page.final %}
+ {{pwic.emojis.notes}}
+ {% endif %}
+ {% if page.valuser != '' %}
+ {{pwic.emojis.flag}}
+ {% endif %}
+
+ {{page.author|escape}}
+ {{page.date|escape}}
+ {{page.time|escape}}
+ {{page.milestone|escape}}
+ {% trans %}Progress by tag{% endtrans %}
+
+
+
+
+
+ {% include 'js/page-special-progress.js' %}
+ {% endif %}
+
+
+ {% if (pwic.documents|count > 0) and (not pwic.user|reserved_user_name) %}
+
+
+ {% trans %}Tag{% endtrans %}
+ {% trans %}Draft{% endtrans %}
+ {{pwic.emojis.updown}}
+ {% trans %}Step{% endtrans %}
+ {{pwic.emojis.updown}}
+ {% trans %}Final{% endtrans %}
+ {{pwic.emojis.updown}}
+ {% trans %}Validated{% endtrans %}
+ {{pwic.emojis.updown}}
+ {% trans %}All the documents of the project{% endtrans %}
+
+
+ {% endif %}
+
+
+ {% for doc in pwic.documents %}
+ {% trans %}Page{% endtrans %}
+ {% trans %}File name{% endtrans %}
+ {% trans %}Format{% endtrans %}
+ {% trans %}Size{% endtrans %}
+ {% trans %}Hash{% endtrans %}
+ {% trans %}Author{% endtrans %}
+ {% trans %}Date{% endtrans %}
+ {% trans %}Time{% endtrans %}
+ {% trans %}Download{% endtrans %}
+
+
+ {% endfor %}
+ {{doc.page|escape}}
+
+ {{doc.filename|escape}}
+ {% if doc.exturl != ''%}
+ {{pwic.emojis.cloud}}
+ {% endif%}
+
+ {{doc.mime_icon}} {{doc.extension|upper}}
+ {{doc.size|size2str|escape}}
+
+ {{doc.hash[:8]|escape}}
+ {% if doc.occurrence > 1 %}
+ {{pwic.emojis.gemini}}
+ {% endif %}
+
+ {{doc.author|escape}}
+ {{doc.date|escape}}
+ {{doc.time|escape}}
+ {{pwic.emojis.inbox}}
+
')}}
')}}{% trans %}Attached documents{% endtrans %}
+
+
{{pwic.emojis.inbox}}
+
+ {% endif %}
+ {% for doc in pwic.documents %}
+
+ {{doc.filename|escape}}
+
{{doc.mime|escape}} ({{doc.size|size2str|escape}})
+
+ {% endfor %}
+
{{pwic.emojis.inbox}}
+
+ {% endif %}
+ {% for doc in pwic.images %}
+
+ {{doc.filename|escape}}
+
{{doc.mime|escape}} ({{doc.size|size2str|escape}})
+
+ {% endfor %}
+
on %(date)s at %(time)s — %(shash)s')|format(revision=pwic.revision, author1=pwic.author|urlencode, author2=pwic.author|escape, date=pwic.date|escape, time=pwic.time|escape, hash=pwic.hash|escape, shash=pwic.hash[:16]|escape) }}
+
')}}{% trans %}Joined projects{% endtrans %}
+
+
+ {% endif %}
+
+ {% if pwic.joinable_projects|count > 0 %}
+
+
+ {% for p in pwic.projects %}
+ {% trans %}Project{% endtrans %}
+ {% trans %}Description{% endtrans %}
+ {% trans %}Last activity{% endtrans %}
+ {% trans %}Creation date{% endtrans %}
+
+
+ {% endfor %}
+ {{p.project|escape}}
+ {{p.description|escape}}
+ {{(p.last_activity or '-')|escape}}
+ {{p.date|escape}}
+ {% trans %}Available public projects{% endtrans %}
+
+
+ {% endif %}
+
+
+ {% for p in pwic.joinable_projects %}
+ {% trans %}Project{% endtrans %}
+ {% trans %}Description{% endtrans %}
+ {% trans %}Date{% endtrans %}
+
+
+ {% endfor %}
+ {{p.project|escape}}
+ {{p.description|escape}}
+ {{p.date|escape}}
+ {{pwic.pages|count + pwic.documents|count}} {% trans %}results{% endtrans %}
+
+ {% trans %}Pages{% endtrans %}
+
+
+ {% endif %}
+
+ {% if pwic.documents|count > 0 %}
+
+
+
+ {% for page in pwic.pages %}
+ {% trans %}Page{% endtrans %}
+ {% trans %}Revision{% endtrans %}
+ {% trans %}Title{% endtrans %}
+ {% trans %}Flags{% endtrans %}
+ {% trans %}Author{% endtrans %}
+ {% trans %}Date{% endtrans %}
+ {% trans %}Time{% endtrans %}
+ {% trans %}Score{% endtrans %}
+
+
+ {% endfor %}
+ {{page.page|escape}}
+ {{page.revision|escape}}
+ {{page.title|escape}}
+
+ {% if pwic.with_rev and page.latest %}
+ {{pwic.emojis.watch}}
+ {% endif %}
+ {% if page.draft %}
+ {{pwic.emojis.hourglass}}
+ {% endif %}
+ {% if page.final %}
+ {{pwic.emojis.notes}}
+ {% endif %}
+ {% if page.valuser != '' %}
+ {{pwic.emojis.flag}}
+ {% endif %}
+
+ {{page.author|escape}}
+ {{page.date|escape}}
+ {{page.time|escape}}
+ {{page.score|escape}}
+ {% trans %}Documents{% endtrans %}
+
+
+ {% endif %}
+
+{% endblock %}
diff --git a/src/ttfrog/pwic/templates/html/user-create.html b/src/ttfrog/pwic/templates/html/user-create.html
new file mode 100644
index 0000000..613071d
--- /dev/null
+++ b/src/ttfrog/pwic/templates/html/user-create.html
@@ -0,0 +1,36 @@
+{% extends 'html/main.html' %}
+
+
+{% block title %}{% trans %}Create a new user{% endtrans %}{% endblock %}
+
+
+{% block content %}
+
+
+
+ {% for doc in pwic.documents %}
+ {% trans %}Page{% endtrans %}
+ {% trans %}File name{% endtrans %}
+ {% trans %}Format{% endtrans %}
+ {% trans %}Size{% endtrans %}
+ {% trans %}Author{% endtrans %}
+ {% trans %}Date{% endtrans %}
+
+
+
+ {% endfor %}
+ {{doc.page|escape}}
+ {{doc.filename|escape}}
+ {{doc.mime_icon}} {{doc.extension|upper}}
+ {{doc.size|size2str|escape}}
+ {{doc.author|escape}}
+ {{doc.date|escape}}
+
+
+
+
+
+
+
+
+ {% set u = namespace(value=0) %}
+ {% for role in pwic.roles %}
+ {% trans %}User{% endtrans %}
+ {% trans %}Pwd{% endtrans %}
+ {% trans %}Activity{% endtrans %}
+ {% trans %}Admin{% endtrans %}
+ {% trans %}Manager{% endtrans %}
+ {% trans %}Editor{% endtrans %}
+ {% trans %}Validator{% endtrans %}
+ {% trans %}Reader{% endtrans %}
+ {% trans %}Disabled{% endtrans %}
+ {% trans %}Delete{% endtrans %}
+
+
+ {% set u.value = u.value + 7 %}
+ {% endfor %}
+ {{role.user|escape}}
+
+ {% if role.user|reserved_user_name %}
+ {{pwic.emojis.star}}
+ {% elif role.oauth %}
+ {{pwic.emojis.id}}
+ {% elif role.initial %}
+ {{pwic.emojis.unlocked}}
+ {% else %}
+ {{pwic.emojis.green_check}}
+ {% endif %}
+
+ {{role.activity|escape}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% if (role.user != pwic.user) and (not role.user|reserved_user_name) %}
+
+ {% endif %}
+
+ {{pwic.userpage|escape}}
+
+ {% trans %}Profile{% endtrans %}
+
+
+
+ {% if (not pwic.password_oauth) and (pwic.user == pwic.userpage) and (not pwic.userpage|reserved_user_name) %}
+
+ {% for project in pwic.projects %}
+
+ {% trans %}Contributions in the last 90 days{% endtrans %}
+ {% if pwic.pages|count == 0 %}
+
+
+ {% endif %}
+
+
+ {% if (pwic.documents|count > 0) and (not pwic.user|reserved_user_name) %}
+
+
+
+ {% for page in pwic.pages %}
+ {% trans %}Project{% endtrans %}
+ {% trans %}Page{% endtrans %}
+ {% trans %}Title{% endtrans %}
+ {% trans %}Flags{% endtrans %}
+ {% trans %}Date{% endtrans %}
+ {% trans %}Time{% endtrans %}
+ {% trans %}Milestone{% endtrans %}
+
+
+ {% endfor %}
+ {{page.project|escape}}
+ {{page.page|escape}}
+ {{page.title|escape}}
+
+ {% if page.draft %}
+ {{pwic.emojis.hourglass}}
+ {% endif %}
+ {% if page.final %}
+ {{pwic.emojis.notes}}
+ {% endif %}
+ {% if page.valuser != '' %}
+ {{pwic.emojis.flag}}
+ {% endif %}
+
+ {{page.date|escape}}
+ {{page.time|escape}}
+ {{page.milestone|escape}}
+ {% trans %}Uploaded documents{% endtrans %}
+
+
+ {% endif %}
+
+
+
+ {% for doc in pwic.documents %}
+ {% trans %}Project{% endtrans %}
+ {% trans %}Page{% endtrans %}
+ {% trans %}File name{% endtrans %}
+ {% trans %}Format{% endtrans %}
+ {% trans %}Size{% endtrans %}
+ {% trans %}Hash{% endtrans %}
+ {% trans %}Date{% endtrans %}
+ {% trans %}Time{% endtrans %}
+ {% trans %}Download{% endtrans %}
+
+
+ {% endfor %}
+ {{doc.project|escape}}
+ {{doc.page|escape}}
+
+ {{doc.filename|escape}}
+ {% if doc.exturl != ''%}
+ {{pwic.emojis.cloud}}
+ {% endif%}
+
+ {{doc.mime_icon}} {{doc.extension|upper}}
+ {{doc.size|size2str|escape}}
+
+ {{doc.hash[:8]|escape}}
+ {% if doc.occurrence > 1 %} {{pwic.emojis.gemini}} {% endif %}
+
+ {{doc.date|escape}}
+ {{doc.time|escape}}
+ {{pwic.emojis.inbox}}
+