mirror of
https://github.com/YunoHost-Apps/etherpad_ynh.git
synced 2024-09-03 18:36:10 +02:00
Repackage
This commit is contained in:
parent
971f1391c1
commit
598d6e71d0
389 changed files with 2248 additions and 74034 deletions
674
LICENSE
Normal file
674
LICENSE
Normal file
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
72
README.md
72
README.md
|
@ -1,4 +1,70 @@
|
||||||
# Etherpad-lite for YunoHost
|
# Etherpad for YunoHost
|
||||||
|
|
||||||
[](https://dash.yunohost.org/appci/app/etherpadlite)  
|
[](https://dash.yunohost.org/appci/app/etherpad)  
|
||||||
[](https://install-app.yunohost.org/?app=etherpadlite)
|
[](https://install-app.yunohost.org/?app=etherpad)
|
||||||
|
|
||||||
|
*[Lire ce readme en français.](./README_fr.md)*
|
||||||
|
|
||||||
|
> *This package allow you to install Etherpad quickly and simply on a YunoHost server.
|
||||||
|
If you don't have YunoHost, please see [here](https://yunohost.org/#/install) to know how to install and enjoy it.*
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Etherpad is a highly customizable Open Source online editor providing collaborative editing in really real-time.
|
||||||
|
|
||||||
|
**Shipped version:** 1.8.6
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
* [Official demo](https://video.etherpad.com/)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
You can access two different admin panels, for Etherpad by accessing `domain.tld/admin`.
|
||||||
|
You can also find a configuration file for Etherpad at this path `/var/www/etherpad/settings.json`.
|
||||||
|
|
||||||
|
*Skin Builder* (accessible at this address `domain.tld/pad/p/test#skinvariantsbuilder`) allows you to customize the skin of your pad. It will give you a parameter to copy into your configuration file `/var/www/etherpad/settings.json`.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
* Official documentation: http://etherpad.org/doc/v1.8.6
|
||||||
|
* YunoHost documentation: https://yunohost.org/#/app_etherpad
|
||||||
|
|
||||||
|
## YunoHost specific features
|
||||||
|
|
||||||
|
#### Multi-users support
|
||||||
|
|
||||||
|
* Is LDAP auth supported? **No**
|
||||||
|
* Can the app be used by multiple users? **Yes**
|
||||||
|
|
||||||
|
#### Supported architectures
|
||||||
|
|
||||||
|
* x86-64 - [](https://ci-apps.yunohost.org/ci/apps/etherpad/)
|
||||||
|
* ARMv8-A - [](https://ci-apps-arm.yunohost.org/ci/apps/etherpad/)
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
## Additionnal informations
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* Report a bug: https://github.com/YunoHost-Apps/etherpad_ynh/issues
|
||||||
|
* Etherpad website: http://etherpad.org/
|
||||||
|
* Upstream app repository: https://github.com/ether/etherpad-lite
|
||||||
|
* YunoHost website: https://yunohost.org/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Developers infos
|
||||||
|
|
||||||
|
Please do your pull request to the [testing branch](https://github.com/YunoHost-Apps/etherpad_ynh/tree/testing).
|
||||||
|
|
||||||
|
To try the testing branch, please proceed like that.
|
||||||
|
```
|
||||||
|
sudo yunohost app install https://github.com/YunoHost-Apps/etherpad_ynh/tree/testing --debug
|
||||||
|
or
|
||||||
|
sudo yunohost app upgrade etherpad -u https://github.com/YunoHost-Apps/etherpad_ynh/tree/testing --debug
|
||||||
|
```
|
||||||
|
|
70
README_fr.md
Normal file
70
README_fr.md
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
# Etherpad pour YunoHost
|
||||||
|
|
||||||
|
[](https://dash.yunohost.org/appci/app/etherpad)  [](https://github.com/YunoHost/Apps/#what-to-do-if-i-cant-maintain-my-app-anymore-)
|
||||||
|
[](https://install-app.yunohost.org/?app=etherpad)
|
||||||
|
|
||||||
|
*[Read this readme in english.](./README.md)*
|
||||||
|
|
||||||
|
> *Ce package vous permet d'installer Etherpad rapidement et simplement sur un serveur YunoHost.
|
||||||
|
Si vous n'avez pas YunoHost, merci de regarder [ici](https://yunohost.org/#/install_fr) pour savoir comment l'installer et en profiter.*
|
||||||
|
|
||||||
|
## Résumé
|
||||||
|
Etherpad est un éditeur en ligne Open Source hautement personnalisable qui permet l'édition collaborative en temps réel.
|
||||||
|
Ce paquet installera les mêmes plugins que [Framapad](https://framapad.org/).
|
||||||
|
|
||||||
|
**Version embarquée :** 1.8.6
|
||||||
|
|
||||||
|
## Captures d'écran
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Démo
|
||||||
|
|
||||||
|
* [Démo officielle](https://video.etherpad.com/)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Vous pouvez accéder à deux panneaux d'administration différents, pour Etherpad en accédant à `domain.tld/admin` et pour MyPads par `domain.tld/mypads/?/admin`. Vous pouvez également trouver le fichier de configuration pour Etherpad à `/var/www/etherpad/settings.json`.
|
||||||
|
|
||||||
|
*Skin Builder* (accessible à cette adresse `domain.tld/pad/p/test#skinvariantsbuilder`) vous permet de personnaliser l'apparence de votre pad. Il vous donnera un paramètre à copier dans votre fichier de configuration `/var/www/etherpad/settings.json`.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
* Documentation officielle : http://etherpad.org/doc/v1.8.6
|
||||||
|
* Documentation YunoHost : https://yunohost.org/#/app_etherpad
|
||||||
|
|
||||||
|
## Fonctionnalités spécifiques à YunoHost
|
||||||
|
|
||||||
|
#### Support multi-utilisateurs
|
||||||
|
|
||||||
|
* L'authentification LDAP est-elle prise en charge ? **Non**
|
||||||
|
* L'application peut-elle être utilisée par plusieurs utilisateurs ? **Oui**
|
||||||
|
|
||||||
|
#### Architectures supportées
|
||||||
|
|
||||||
|
* x86-64 - [](https://ci-apps.yunohost.org/ci/apps/etherpad/)
|
||||||
|
* ARMv8-A - [](https://ci-apps-arm.yunohost.org/ci/apps/etherpad/)
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
## Informations additionnelles
|
||||||
|
|
||||||
|
## Liens
|
||||||
|
|
||||||
|
* Reporter un bug : https://github.com/YunoHost-Apps/etherpad_ynh/issues
|
||||||
|
* Site d'Etherpad : http://etherpad.org/
|
||||||
|
* Dépôt GitHub de l'application : https://github.com/ether/etherpad-lite
|
||||||
|
* Site de YunoHost : https://yunohost.org/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Informations à l'intention des développeurs
|
||||||
|
|
||||||
|
Merci de faire vos pull request sur la [branche testing](https://github.com/YunoHost-Apps/etherpad_ynh/tree/testing).
|
||||||
|
|
||||||
|
Pour tester la branche testing, merci de procéder ainsi.
|
||||||
|
```
|
||||||
|
sudo yunohost app install https://github.com/YunoHost-Apps/etherpad_ynh/tree/testing --debug
|
||||||
|
ou
|
||||||
|
sudo yunohost app upgrade etherpad -u https://github.com/YunoHost-Apps/etherpad_ynh/tree/testing --debug
|
||||||
|
```
|
35
check_process
Normal file
35
check_process
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# See here for more information
|
||||||
|
# https://github.com/YunoHost/package_check#syntax-check_process-file
|
||||||
|
|
||||||
|
# Move this file from check_process.default to check_process when you have filled it.
|
||||||
|
|
||||||
|
;; Test complet
|
||||||
|
; Manifest
|
||||||
|
domain="domain.tld" (DOMAIN)
|
||||||
|
path="/path" (PATH)
|
||||||
|
admin="john" (USER)
|
||||||
|
language="fr"
|
||||||
|
is_public=1 (PUBLIC|public=1|private=0)
|
||||||
|
password="pass"
|
||||||
|
port="9001" (PORT)
|
||||||
|
; Checks
|
||||||
|
pkg_linter=1
|
||||||
|
setup_sub_dir=1
|
||||||
|
setup_root=1
|
||||||
|
setup_nourl=0
|
||||||
|
setup_private=1
|
||||||
|
setup_public=1
|
||||||
|
upgrade=1
|
||||||
|
backup_restore=1
|
||||||
|
multi_instance=1
|
||||||
|
port_already_use=0
|
||||||
|
change_url=1
|
||||||
|
;;; Levels
|
||||||
|
Level 5=auto
|
||||||
|
;;; Options
|
||||||
|
Email=
|
||||||
|
Notification=none
|
||||||
|
;;; Upgrade options
|
||||||
|
; commit=CommitHash
|
||||||
|
name=Name and date of the commit.
|
||||||
|
manifest_arg=domain=DOMAIN&path=PATH&admin=USER&language=fr&is_public=1&password=pass&port=9001&
|
6
conf/app.src
Normal file
6
conf/app.src
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
SOURCE_URL=https://github.com/ether/etherpad-lite/archive/1.8.6.tar.gz
|
||||||
|
SOURCE_SUM=89f9c8e8b2aff4335e02b5ec00eec67410eb3ab08f0d55d5dd0670a5c558bf09
|
||||||
|
SOURCE_SUM_PRG=sha256sum
|
||||||
|
SOURCE_FORMAT=tar.gz
|
||||||
|
SOURCE_IN_SUBDIR=true
|
||||||
|
SOURCE_FILENAME=
|
49
conf/credentials.json
Normal file
49
conf/credentials.json
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
This file must be valid JSON. But comments are allowed
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The type of the database.
|
||||||
|
*
|
||||||
|
* You can choose between many DB drivers, for example: dirty, postgres,
|
||||||
|
* sqlite, mysql.
|
||||||
|
*
|
||||||
|
* You shouldn't use "dirty" for for anything else than testing or
|
||||||
|
* development.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Database specific settings are dependent on dbType, and go in dbSettings.
|
||||||
|
* Remember that since Etherpad 1.6.0 you can also store these informations in
|
||||||
|
* credentials.json.
|
||||||
|
*
|
||||||
|
* For a complete list of the supported drivers, please refer to:
|
||||||
|
* https://www.npmjs.com/package/ueberdb2
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* An Example of MySQL Configuration */
|
||||||
|
"dbType" : "postgres",
|
||||||
|
"dbSettings" : {
|
||||||
|
"user" : "__DB_NAME__",
|
||||||
|
"host" : "localhost",
|
||||||
|
"password": "__DB_PWD__",
|
||||||
|
"database": "__DB_NAME__"
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Users for basic authentication.
|
||||||
|
*
|
||||||
|
* is_admin = true gives access to /admin.
|
||||||
|
* If you do not uncomment this, /admin will not be available!
|
||||||
|
*
|
||||||
|
* WARNING: passwords should not be stored in plaintext in this file.
|
||||||
|
* If you want to mitigate this, please install ep_hash_auth and
|
||||||
|
* follow the section "secure your installation" in README.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
"users": {
|
||||||
|
"__ADMIN__": {
|
||||||
|
"password": "__PASSWORD__",
|
||||||
|
"is_admin": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,79 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
### BEGIN INIT INFO
|
|
||||||
# Provides: etherpad-lite
|
|
||||||
# Required-Start: $local_fs $remote_fs $network $syslog
|
|
||||||
# Required-Stop: $local_fs $remote_fs $network $syslog
|
|
||||||
# Default-Start: 2 3 4 5
|
|
||||||
# Default-Stop: 0 1 6
|
|
||||||
# Short-Description: starts etherpad lite
|
|
||||||
# Description: starts etherpad lite using start-stop-daemon
|
|
||||||
### END INIT INFO
|
|
||||||
|
|
||||||
PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/opt/node/bin"
|
|
||||||
LOGFILE="/var/log/APPTOCHANGE/APPTOCHANGE.log"
|
|
||||||
EPLITE_DIR="/var/www/APPTOCHANGE"
|
|
||||||
EPLITE_BIN="bin/safeRun.sh"
|
|
||||||
USER="www-data"
|
|
||||||
GROUP="www-data"
|
|
||||||
DESC="Etherpad Lite"
|
|
||||||
NAME="APPTOCHANGE"
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
. /lib/lsb/init-functions
|
|
||||||
|
|
||||||
start() {
|
|
||||||
echo "Starting $DESC... "
|
|
||||||
|
|
||||||
start-stop-daemon --start --chuid "$USER:$GROUP" --background --make-pidfile --pidfile /var/run/$NAME.pid --exec $EPLITE_DIR/$EPLITE_BIN -- $LOGFILE || true
|
|
||||||
echo "done"
|
|
||||||
}
|
|
||||||
|
|
||||||
#We need this function to ensure the whole process tree will be killed
|
|
||||||
killtree() {
|
|
||||||
local _pid=$1
|
|
||||||
local _sig=${2-TERM}
|
|
||||||
for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
|
|
||||||
killtree ${_child} ${_sig}
|
|
||||||
done
|
|
||||||
kill -${_sig} ${_pid}
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
echo "Stopping $DESC... "
|
|
||||||
if test -f /var/run/$NAME.pid; then
|
|
||||||
while test -d /proc/$(cat /var/run/$NAME.pid); do
|
|
||||||
killtree $(cat /var/run/$NAME.pid) 15
|
|
||||||
sleep 0.5
|
|
||||||
done
|
|
||||||
rm /var/run/$NAME.pid
|
|
||||||
fi
|
|
||||||
echo "done"
|
|
||||||
}
|
|
||||||
|
|
||||||
status() {
|
|
||||||
status_of_proc -p /var/run/$NAME.pid "" "APPTOCHANGE" && exit 0 || exit $?
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
start)
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
stop
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
stop
|
|
||||||
start
|
|
||||||
;;
|
|
||||||
status)
|
|
||||||
status
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $NAME {start|stop|restart|status}" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
exit 0
|
|
|
@ -1,6 +1,22 @@
|
||||||
location YNH_PATH/ {
|
location __PATH__/ {
|
||||||
rewrite ^YNH_PATH$ YNH_PATH/ permanent;
|
|
||||||
proxy_pass http://YNH_DOMAIN:YNH_PORT/;
|
# Force usage of https
|
||||||
proxy_set_header Host $host;
|
if ($scheme = http) {
|
||||||
proxy_buffering off;
|
rewrite ^ https://$server_name$request_uri? permanent;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:__PORT__/;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Host $server_name;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
# Include SSOWAT user panel.
|
||||||
|
include conf.d/yunohost_panel.conf.inc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
location / {
|
|
||||||
proxy_pass http://localhost:9001/;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
# be careful, this line doesn't override any proxy_buffering on set in a conf.d/file.conf
|
|
||||||
proxy_buffering off;
|
|
||||||
}
|
|
|
@ -1,150 +1,534 @@
|
||||||
/*
|
/*
|
||||||
This file must be valid JSON. But comments are allowed
|
* This file must be valid JSON. But comments are allowed
|
||||||
|
*
|
||||||
Please edit settings.json, not settings.json.template
|
* Please edit settings.json, not settings.json.template
|
||||||
*/
|
*
|
||||||
|
* Please note that starting from Etherpad 1.6.0 you can store DB credentials in
|
||||||
|
* a separate file (credentials.json).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* ENVIRONMENT VARIABLE SUBSTITUTION
|
||||||
|
* =================================
|
||||||
|
*
|
||||||
|
* All the configuration values can be read from environment variables using the
|
||||||
|
* syntax "${ENV_VAR}" or "${ENV_VAR:default_value}".
|
||||||
|
*
|
||||||
|
* This is useful, for example, when running in a Docker container.
|
||||||
|
*
|
||||||
|
* EXAMPLE:
|
||||||
|
* "port": "${PORT:9001}"
|
||||||
|
* "minify": "${MINIFY}"
|
||||||
|
* "skinName": "${SKIN_NAME:colibris}"
|
||||||
|
*
|
||||||
|
* Would read the configuration values for those items from the environment
|
||||||
|
* variables PORT, MINIFY and SKIN_NAME.
|
||||||
|
*
|
||||||
|
* If PORT and SKIN_NAME variables were not defined, the default values 9001 and
|
||||||
|
* "colibris" would be used.
|
||||||
|
* The configuration value "minify", on the other hand, does not have a
|
||||||
|
* designated default value. Thus, if the environment variable MINIFY were
|
||||||
|
* undefined, "minify" would be null.
|
||||||
|
*
|
||||||
|
* REMARKS:
|
||||||
|
* 1) please note that variable substitution always needs to be quoted.
|
||||||
|
*
|
||||||
|
* "port": 9001, <-- Literal values. When not using
|
||||||
|
* "minify": false substitution, only strings must be
|
||||||
|
* "skinName": "colibris" quoted. Booleans and numbers must not.
|
||||||
|
*
|
||||||
|
* "port": "${PORT:9001}" <-- CORRECT: if you want to use a variable
|
||||||
|
* "minify": "${MINIFY:true}" substitution, put quotes around its name,
|
||||||
|
* "skinName": "${SKIN_NAME}" even if the required value is a number or
|
||||||
|
* a boolean.
|
||||||
|
* Etherpad will take care of rewriting it
|
||||||
|
* to the proper type if necessary.
|
||||||
|
*
|
||||||
|
* "port": ${PORT:9001} <-- ERROR: this is not valid json. Quotes
|
||||||
|
* "minify": ${MINIFY} around variable names are missing.
|
||||||
|
* "skinName": ${SKIN_NAME}
|
||||||
|
*
|
||||||
|
* 2) Beware of undefined variables and default values: nulls and empty strings
|
||||||
|
* are different!
|
||||||
|
*
|
||||||
|
* This is particularly important for user's passwords (see the relevant
|
||||||
|
* section):
|
||||||
|
*
|
||||||
|
* "password": "${PASSW}" // if PASSW is not defined would result in password === null
|
||||||
|
* "password": "${PASSW:}" // if PASSW is not defined would result in password === ''
|
||||||
|
*
|
||||||
|
* If you want to use an empty value (null) as default value for a variable,
|
||||||
|
* simply do not set it, without putting any colons: "${ABIWORD}".
|
||||||
|
*
|
||||||
|
* 3) if you want to use newlines in the default value of a string parameter,
|
||||||
|
* use "\n" as usual.
|
||||||
|
*
|
||||||
|
* "defaultPadText" : "${DEFAULT_PAD_TEXT}Line 1\nLine 2"
|
||||||
|
*/
|
||||||
{
|
{
|
||||||
// Name your instance!
|
/*
|
||||||
|
* Name your instance!
|
||||||
|
*/
|
||||||
"title": "Etherpad",
|
"title": "Etherpad",
|
||||||
|
|
||||||
// favicon default name
|
/*
|
||||||
// alternatively, set up a fully specified Url to your own favicon
|
* favicon default name
|
||||||
|
* alternatively, set up a fully specified Url to your own favicon
|
||||||
|
*/
|
||||||
"favicon": "favicon.ico",
|
"favicon": "favicon.ico",
|
||||||
|
|
||||||
//IP and port which etherpad should bind at
|
/*
|
||||||
"ip": "0.0.0.0",
|
* Skin name.
|
||||||
"port" : 9001,
|
*
|
||||||
|
* Its value has to be an existing directory under src/static/skins.
|
||||||
// Session Key, used for reconnecting user sessions
|
* You can write your own, or use one of the included ones:
|
||||||
// Set this to a secure string at least 10 characters long. Do not share this value.
|
*
|
||||||
"sessionKey" : "KEY",
|
* - "no-skin": an empty skin (default). This yields the unmodified,
|
||||||
|
* traditional Etherpad theme.
|
||||||
|
* - "colibris": the new experimental skin (since Etherpad 1.8), candidate to
|
||||||
|
* become the default in Etherpad 2.0
|
||||||
|
*/
|
||||||
|
"skinName": "colibris",
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Node native SSL support
|
* Skin Variants
|
||||||
// this is disabled by default
|
*
|
||||||
//
|
* Use the UI skin variants builder at /p/test#skinvariantsbuilder
|
||||||
// make sure to have the minimum and correct file access permissions set
|
*
|
||||||
// so that the Etherpad server can access them
|
* For the colibris skin only, you can choose how to render the three main
|
||||||
|
* containers:
|
||||||
|
* - toolbar (top menu with icons)
|
||||||
|
* - editor (containing the text of the pad)
|
||||||
|
* - background (area outside of editor, mostly visible when using page style)
|
||||||
|
*
|
||||||
|
* For each of the 3 containers you can choose 4 color combinations:
|
||||||
|
* super-light, light, dark, super-dark.
|
||||||
|
*
|
||||||
|
* For example, to make the toolbar dark, you will include "dark-toolbar" into
|
||||||
|
* skinVariants.
|
||||||
|
*
|
||||||
|
* You can provide multiple skin variants separated by spaces. Default
|
||||||
|
* skinVariant is "super-light-toolbar super-light-editor light-background".
|
||||||
|
*
|
||||||
|
* For the editor container, you can also make it full width by adding
|
||||||
|
* "full-width-editor" variant (by default editor is rendered as a page, with
|
||||||
|
* a max-width of 900px).
|
||||||
|
*/
|
||||||
|
"skinVariants": "dark-toolbar super-light-editor light-background",
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IP and port which Etherpad should bind at.
|
||||||
|
*
|
||||||
|
* Binding to a Unix socket is also supported: just use an empty string for
|
||||||
|
* the ip, and put the full path to the socket in the port parameter.
|
||||||
|
*
|
||||||
|
* EXAMPLE USING UNIX SOCKET:
|
||||||
|
* "ip": "", // <-- has to be an empty string
|
||||||
|
* "port" : "/somepath/etherpad.socket", // <-- path to a Unix socket
|
||||||
|
*/
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"port": __PORT__,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Option to hide/show the settings.json in admin page.
|
||||||
|
*
|
||||||
|
* Default option is set to true
|
||||||
|
*/
|
||||||
|
"showSettingsInAdminPage": true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Node native SSL support
|
||||||
|
*
|
||||||
|
* This is disabled by default.
|
||||||
|
* Make sure to have the minimum and correct file access permissions set so
|
||||||
|
* that the Etherpad server can access them
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
"ssl" : {
|
"ssl" : {
|
||||||
"key" : "/path-to-your/epl-server.key",
|
"key" : "/path-to-your/epl-server.key",
|
||||||
"cert" : "/path-to-your/epl-server.crt"
|
"cert" : "/path-to-your/epl-server.crt",
|
||||||
|
"ca": ["/path-to-your/epl-intermediate-cert1.crt", "/path-to-your/epl-intermediate-cert2.crt"]
|
||||||
},
|
},
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*The Type of the database. You can choose between dirty, postgres, sqlite and mysql
|
/*
|
||||||
//You shouldn't use "dirty" for for anything else than testing or development
|
* The type of the database.
|
||||||
"dbType" : "dirty",
|
*
|
||||||
//the database specific settings
|
* You can choose between many DB drivers, for example: dirty, postgres,
|
||||||
"dbSettings" : {
|
* sqlite, mysql.
|
||||||
"filename" : "var/dirty.db"
|
*
|
||||||
},
|
* You shouldn't use "dirty" for for anything else than testing or
|
||||||
*/
|
* development.
|
||||||
|
*/
|
||||||
|
|
||||||
// An Example of MySQL Configuration
|
/*
|
||||||
"dbType" : "mysql",
|
* The default text of a pad
|
||||||
"dbSettings" : {
|
*/
|
||||||
"user" : "yunouser",
|
"defaultPadText" : "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https:\/\/etherpad.org\n",
|
||||||
"host" : "localhost",
|
|
||||||
"password": "yunopass",
|
|
||||||
"database": "yunobase"
|
|
||||||
},
|
|
||||||
|
|
||||||
//the default text of a pad
|
/*
|
||||||
"defaultPadText" : "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at http:\/\/etherpad.org\n",
|
* Default Pad behavior.
|
||||||
|
*
|
||||||
|
* Change them if you want to override.
|
||||||
|
*/
|
||||||
|
"padOptions": {
|
||||||
|
"noColors": false,
|
||||||
|
"showControls": true,
|
||||||
|
"showChat": true,
|
||||||
|
"showLineNumbers": true,
|
||||||
|
"useMonospaceFont": false,
|
||||||
|
"userName": false,
|
||||||
|
"userColor": false,
|
||||||
|
"rtl": false,
|
||||||
|
"alwaysShowChat": false,
|
||||||
|
"chatAndUsers": false,
|
||||||
|
"lang": "__LANGUAGE__"
|
||||||
|
},
|
||||||
|
|
||||||
/* Users must have a session to access pads. This effectively allows only group pads to be accessed. */
|
/*
|
||||||
"requireSession" : false,
|
* Pad Shortcut Keys
|
||||||
|
*/
|
||||||
|
"padShortcutEnabled" : {
|
||||||
|
"altF9": true, /* focus on the File Menu and/or editbar */
|
||||||
|
"altC": true, /* focus on the Chat window */
|
||||||
|
"cmdShift2": true, /* shows a gritter popup showing a line author */
|
||||||
|
"delete": true,
|
||||||
|
"return": true,
|
||||||
|
"esc": true, /* in mozilla versions 14-19 avoid reconnecting pad */
|
||||||
|
"cmdS": true, /* save a revision */
|
||||||
|
"tab": true, /* indent */
|
||||||
|
"cmdZ": true, /* undo/redo */
|
||||||
|
"cmdY": true, /* redo */
|
||||||
|
"cmdI": true, /* italic */
|
||||||
|
"cmdB": true, /* bold */
|
||||||
|
"cmdU": true, /* underline */
|
||||||
|
"cmd5": true, /* strike through */
|
||||||
|
"cmdShiftL": true, /* unordered list */
|
||||||
|
"cmdShiftN": true, /* ordered list */
|
||||||
|
"cmdShift1": true, /* ordered list */
|
||||||
|
"cmdShiftC": true, /* clear authorship */
|
||||||
|
"cmdH": true, /* backspace */
|
||||||
|
"ctrlHome": true, /* scroll to top of pad */
|
||||||
|
"pageUp": true,
|
||||||
|
"pageDown": true
|
||||||
|
},
|
||||||
|
|
||||||
/* Users may edit pads but not create new ones. Pad creation is only via the API. This applies both to group pads and regular pads. */
|
/*
|
||||||
"editOnly" : false,
|
* Should we suppress errors from being visible in the default Pad Text?
|
||||||
|
*/
|
||||||
|
"suppressErrorsInPadText": false,
|
||||||
|
|
||||||
/* if true, all css & js will be minified before sending to the client. This will improve the loading performance massivly,
|
/*
|
||||||
but makes it impossible to debug the javascript/css */
|
* If this option is enabled, a user must have a session to access pads.
|
||||||
"minify" : true,
|
* This effectively allows only group pads to be accessed.
|
||||||
|
*/
|
||||||
|
"requireSession": false,
|
||||||
|
|
||||||
/* How long may clients use served javascript code (in seconds)? Without versioning this
|
/*
|
||||||
may cause problems during deployment. Set to 0 to disable caching */
|
* Users may edit pads but not create new ones.
|
||||||
"maxAge" : 21600, // 60 * 60 * 6 = 6 hours
|
*
|
||||||
|
* Pad creation is only via the API.
|
||||||
|
* This applies both to group pads and regular pads.
|
||||||
|
*/
|
||||||
|
"editOnly": false,
|
||||||
|
|
||||||
/* This is the path to the Abiword executable. Setting it to null, disables abiword.
|
/*
|
||||||
Abiword is needed to advanced import/export features of pads*/
|
* If set to true, those users who have a valid session will automatically be
|
||||||
"abiword" : null,
|
* granted access to password protected pads.
|
||||||
|
*/
|
||||||
|
"sessionNoPassword": false,
|
||||||
|
|
||||||
/* This setting is used if you require authentication of all users.
|
/*
|
||||||
Note: /admin always requires authentication. */
|
* If true, all css & js will be minified before sending to the client.
|
||||||
|
*
|
||||||
|
* This will improve the loading performance massively, but makes it difficult
|
||||||
|
* to debug the javascript/css
|
||||||
|
*/
|
||||||
|
"minify": true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* How long may clients use served javascript code (in seconds)?
|
||||||
|
*
|
||||||
|
* Not setting this may cause problems during deployment.
|
||||||
|
* Set to 0 to disable caching.
|
||||||
|
*/
|
||||||
|
"maxAge": 21600, // 60 * 60 * 6 = 6 hours
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Absolute path to the Abiword executable.
|
||||||
|
*
|
||||||
|
* Abiword is needed to get advanced import/export features of pads. Setting
|
||||||
|
* it to null disables Abiword and will only allow plain text and HTML
|
||||||
|
* import/exports.
|
||||||
|
*/
|
||||||
|
"abiword": null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the absolute path to the soffice executable.
|
||||||
|
*
|
||||||
|
* LibreOffice can be used in lieu of Abiword to export pads.
|
||||||
|
* Setting it to null disables LibreOffice exporting.
|
||||||
|
*/
|
||||||
|
"soffice": null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Path to the Tidy executable.
|
||||||
|
*
|
||||||
|
* Tidy is used to improve the quality of exported pads.
|
||||||
|
* Setting it to null disables Tidy.
|
||||||
|
*/
|
||||||
|
"tidyHtml": null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allow import of file types other than the supported ones:
|
||||||
|
* txt, doc, docx, rtf, odt, html & htm
|
||||||
|
*/
|
||||||
|
"allowUnknownFileEnds": true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This setting is used if you require authentication of all users.
|
||||||
|
*
|
||||||
|
* Note: "/admin" always requires authentication.
|
||||||
|
*/
|
||||||
"requireAuthentication": false,
|
"requireAuthentication": false,
|
||||||
|
|
||||||
/* Require authorization by a module, or a user with is_admin set, see below. */
|
/*
|
||||||
|
* Require authorization by a module, or a user with is_admin set, see below.
|
||||||
|
*/
|
||||||
"requireAuthorization": false,
|
"requireAuthorization": false,
|
||||||
|
|
||||||
/*when you use NginX or another proxy/ load-balancer set this to true*/
|
/*
|
||||||
"trustProxy": true,
|
* When you use NGINX or another proxy/load-balancer set this to true.
|
||||||
|
*
|
||||||
|
* This is especially necessary when the reverse proxy performs SSL
|
||||||
|
* termination, otherwise the cookies will not have the "secure" flag.
|
||||||
|
*
|
||||||
|
* The other effect will be that the logs will contain the real client's IP,
|
||||||
|
* instead of the reverse proxy's IP.
|
||||||
|
*/
|
||||||
|
"trustProxy": false,
|
||||||
|
|
||||||
/* Privacy: disable IP logging */
|
/*
|
||||||
|
* Privacy: disable IP logging
|
||||||
|
*/
|
||||||
"disableIPlogging": false,
|
"disableIPlogging": false,
|
||||||
|
|
||||||
/* Users for basic authentication. is_admin = true gives access to /admin.
|
|
||||||
If you do not uncomment this, /admin will not be available! */
|
|
||||||
/*
|
/*
|
||||||
TODO add argument to ask ynh user admin, and change that here
|
* Time (in seconds) to automatically reconnect pad when a "Force reconnect"
|
||||||
"users": {
|
* message is shown to user.
|
||||||
"admin": {
|
*
|
||||||
"password": "changeme1",
|
* Set to 0 to disable automatic reconnection.
|
||||||
"is_admin": true
|
*/
|
||||||
|
"automaticReconnectionTimeout": 0,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default, when caret is moved out of viewport, it scrolls the minimum
|
||||||
|
* height needed to make this line visible.
|
||||||
|
*/
|
||||||
|
"scrollWhenFocusLineIsOutOfViewport": {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Percentage of viewport height to be additionally scrolled.
|
||||||
|
*
|
||||||
|
* E.g.: use "percentage.editionAboveViewport": 0.5, to place caret line in
|
||||||
|
* the middle of viewport, when user edits a line above of the
|
||||||
|
* viewport
|
||||||
|
*
|
||||||
|
* Set to 0 to disable extra scrolling
|
||||||
|
*/
|
||||||
|
"percentage": {
|
||||||
|
"editionAboveViewport": 0,
|
||||||
|
"editionBelowViewport": 0
|
||||||
},
|
},
|
||||||
"user": {
|
|
||||||
"password": "changeme1",
|
/*
|
||||||
"is_admin": false
|
* Time (in milliseconds) used to animate the scroll transition.
|
||||||
}
|
* Set to 0 to disable animation
|
||||||
|
*/
|
||||||
|
"duration": 0,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flag to control if it should scroll when user places the caret in the
|
||||||
|
* last line of the viewport
|
||||||
|
*/
|
||||||
|
"scrollWhenCaretIsInTheLastLineOfViewport": false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Percentage of viewport height to be additionally scrolled when user
|
||||||
|
* presses arrow up in the line of the top of the viewport.
|
||||||
|
*
|
||||||
|
* Set to 0 to let the scroll to be handled as default by Etherpad
|
||||||
|
*/
|
||||||
|
"percentageToScrollWhenUserPressesArrowUp": 0
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Restrict socket.io transport methods
|
||||||
|
*/
|
||||||
|
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allow Load Testing tools to hit the Etherpad Instance.
|
||||||
|
*
|
||||||
|
* WARNING: this will disable security on the instance.
|
||||||
|
*/
|
||||||
|
"loadTest": false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Disable indentation on new line when previous line ends with some special
|
||||||
|
* chars (':', '[', '(', '{')
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
"indentationOnNewLine": false,
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From Etherpad 1.8.3 onwards, import and export of pads is always rate
|
||||||
|
* limited.
|
||||||
|
*
|
||||||
|
* The default is to allow at most 10 requests per IP in a 90 seconds window.
|
||||||
|
* After that the import/export request is rejected.
|
||||||
|
*
|
||||||
|
* See https://github.com/nfriedly/express-rate-limit for more options
|
||||||
|
*/
|
||||||
|
"importExportRateLimiting": {
|
||||||
|
// duration of the rate limit window (milliseconds)
|
||||||
|
"windowMs": 90000,
|
||||||
|
|
||||||
|
// maximum number of requests per IP to allow during the rate limit window
|
||||||
|
"max": 10
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From Etherpad 1.8.3 onwards, the maximum allowed size for a single imported
|
||||||
|
* file is always bounded.
|
||||||
|
*
|
||||||
|
* File size is specified in bytes. Default is 50 MB.
|
||||||
|
*/
|
||||||
|
"importMaxFileSize": 52428800, // 50 * 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From Etherpad 1.8.3 onwards import was restricted to authors who had
|
||||||
|
* content within the pad.
|
||||||
|
*
|
||||||
|
* This setting will override that restriction and allow any user to import
|
||||||
|
* without the requirement to add content to a pad.
|
||||||
|
*
|
||||||
|
* This setting is useful for when you use a plugin for authentication so you
|
||||||
|
* can already trust each user.
|
||||||
|
*/
|
||||||
|
"allowAnyoneToImport": false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From Etherpad 1.9.0 onwards, when Etherpad is in production mode commits from individual users are rate limited
|
||||||
|
*
|
||||||
|
* The default is to allow at most 10 changes per IP in a 1 second window.
|
||||||
|
* After that the change is rejected.
|
||||||
|
*
|
||||||
|
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
|
||||||
|
*/
|
||||||
|
"commitRateLimiting": {
|
||||||
|
// duration of the rate limit window (seconds)
|
||||||
|
"duration": 1,
|
||||||
|
|
||||||
|
// maximum number of chanes per IP to allow during the rate limit window
|
||||||
|
"points": 10
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Toolbar buttons configuration.
|
||||||
|
*
|
||||||
|
* Uncomment to customize.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
"toolbar": {
|
||||||
|
"left": [
|
||||||
|
["bold", "italic", "underline", "strikethrough"],
|
||||||
|
["orderedlist", "unorderedlist", "indent", "outdent"],
|
||||||
|
["undo", "redo"],
|
||||||
|
["clearauthorship"]
|
||||||
|
],
|
||||||
|
"right": [
|
||||||
|
["importexport", "timeslider", "savedrevision"],
|
||||||
|
["settings", "embed"],
|
||||||
|
["showusers"]
|
||||||
|
],
|
||||||
|
"timeslider": [
|
||||||
|
["timeslider_export", "timeslider_returnToPad"]
|
||||||
|
]
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// restrict socket.io transport methods
|
/*
|
||||||
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
|
* Expose Etherpad version in the web interface and in the Server http header.
|
||||||
|
*
|
||||||
|
* Do not enable on production machines.
|
||||||
|
*/
|
||||||
|
"exposeVersion": false,
|
||||||
|
|
||||||
/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
|
/*
|
||||||
|
* The log level we are using.
|
||||||
|
*
|
||||||
|
* Valid values: DEBUG, INFO, WARN, ERROR
|
||||||
|
*/
|
||||||
"loglevel": "INFO",
|
"loglevel": "INFO",
|
||||||
|
|
||||||
//Logging configuration. See log4js documentation for further information
|
/*
|
||||||
// https://github.com/nomiddlename/log4js-node
|
* Logging configuration. See log4js documentation for further information:
|
||||||
// You can add as many appenders as you want here:
|
* https://github.com/nomiddlename/log4js-node
|
||||||
|
*
|
||||||
|
* You can add as many appenders as you want here.
|
||||||
|
*/
|
||||||
"logconfig" :
|
"logconfig" :
|
||||||
{ "appenders": [
|
{ "appenders": [
|
||||||
{ "type": "console"
|
{ "type": "console"
|
||||||
//, "category": "access"// only logs pad access
|
//, "category": "access"// only logs pad access
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
, { "type": "file"
|
/*
|
||||||
|
, { "type": "file"
|
||||||
, "filename": "your-log-file-here.log"
|
, "filename": "your-log-file-here.log"
|
||||||
, "maxLogSize": 1024
|
, "maxLogSize": 1024
|
||||||
, "backups": 3 // how many log files there're gonna be at max
|
, "backups": 3 // how many log files there're gonna be at max
|
||||||
//, "category": "test" // only log a specific category
|
//, "category": "test" // only log a specific category
|
||||||
}*/
|
}
|
||||||
/*
|
*/
|
||||||
, { "type": "logLevelFilter"
|
|
||||||
, "level": "warn" // filters out all log messages that have a lower level than "error"
|
/*
|
||||||
, "appender":
|
, { "type": "logLevelFilter"
|
||||||
{ Use whatever appender you want here }
|
, "level": "warn" // filters out all log messages that have a lower level than "error"
|
||||||
}*/
|
, "appender":
|
||||||
/*
|
{ Use whatever appender you want here }
|
||||||
, { "type": "logLevelFilter"
|
}
|
||||||
, "level": "error" // filters out all log messages that have a lower level than "error"
|
*/
|
||||||
, "appender":
|
|
||||||
{ "type": "smtp"
|
/*
|
||||||
, "subject": "An error occured in your EPL instance!"
|
, { "type": "logLevelFilter"
|
||||||
, "recipients": "bar@blurdybloop.com, baz@blurdybloop.com"
|
, "level": "error" // filters out all log messages that have a lower level than "error"
|
||||||
, "sendInterval": 60*5 // in secs -- will buffer log messages; set to 0 to send a mail for every message
|
, "appender":
|
||||||
, "transport": "SMTP", "SMTP": { // see https://github.com/andris9/Nodemailer#possible-transport-methods
|
{ "type": "smtp"
|
||||||
"host": "smtp.example.com", "port": 465,
|
, "subject": "An error occurred in your EPL instance!"
|
||||||
"secureConnection": true,
|
, "recipients": "bar@blurdybloop.com, baz@blurdybloop.com"
|
||||||
"auth": {
|
, "sendInterval": 300 // 60 * 5 = 5 minutes -- will buffer log messages; set to 0 to send a mail for every message
|
||||||
"user": "foo@example.com",
|
, "transport": "SMTP", "SMTP": { // see https://github.com/andris9/Nodemailer#possible-transport-methods
|
||||||
"pass": "bar_foo"
|
"host": "smtp.example.com", "port": 465,
|
||||||
|
"secureConnection": true,
|
||||||
|
"auth": {
|
||||||
|
"user": "foo@example.com",
|
||||||
|
"pass": "bar_foo"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
*/
|
||||||
}*/
|
|
||||||
] }
|
]
|
||||||
|
}, // logconfig
|
||||||
|
|
||||||
|
/* Override any strings found in locale directories */
|
||||||
|
"customLocaleStrings": {}
|
||||||
}
|
}
|
||||||
|
|
16
conf/systemd.service
Normal file
16
conf/systemd.service
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Etherpad-lite, the collaborative editor.
|
||||||
|
After=syslog.target network.target postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=__APP__
|
||||||
|
Group=__APP__
|
||||||
|
WorkingDirectory=__FINALPATH__
|
||||||
|
Environment="NODE_ENV=production"
|
||||||
|
Environment="__YNH_NODE_LOAD_PATH__"
|
||||||
|
ExecStart=__FINALPATH__/node_modules/ep_etherpad-lite/node/server.js
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -1,45 +1,45 @@
|
||||||
{
|
{
|
||||||
"name": "Etherpad Lite",
|
"name": "Etherpad",
|
||||||
"id": "etherpadlite",
|
"id": "etherpad",
|
||||||
"packaging_format": 1,
|
"packaging_format": 1,
|
||||||
"description": {
|
"description": {
|
||||||
"en": "online editor providing collaborative editing in really real-time",
|
"en": "Online editor providing collaborative editing in real-time.",
|
||||||
"fr": "editeur multiutilisiteur en temps réel en ligne"
|
"fr": "Éditeur en ligne fournissant l'édition collaborative en temps réel."
|
||||||
},
|
},
|
||||||
"url":"http://etherpad.org/",
|
"version": "1.8.6~ynh1",
|
||||||
"license":"free",
|
"url": "https://etherpad.org/",
|
||||||
|
"license": "Apache-2.0",
|
||||||
"maintainer": {
|
"maintainer": {
|
||||||
"name": "beudbeud",
|
"name": ""
|
||||||
"email": "beudbeud@beudibox.fr"
|
|
||||||
},
|
},
|
||||||
"requirements": {
|
"requirements": {
|
||||||
"yunohost": ">= 2.4.0"
|
"yunohost": ">= 4.0.7"
|
||||||
},
|
},
|
||||||
"multi_instance": "true",
|
"multi_instance": true,
|
||||||
"services":[
|
"services": [
|
||||||
"nginx",
|
"nginx",
|
||||||
"mysql"
|
"mysql"
|
||||||
],
|
],
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"install" : [
|
"install" : [
|
||||||
{
|
{
|
||||||
"name": "domain",
|
"name": "domain",
|
||||||
"type":"domain",
|
"type": "domain",
|
||||||
"ask": {
|
"ask": {
|
||||||
"en": "Choose a domain for Etherpad",
|
"en": "Choose a domain name for Etherpad",
|
||||||
"fr": "Choisissez un domaine pour Etherpad"
|
"fr": "Choisissez un nom de domaine pour Etherpad"
|
||||||
},
|
},
|
||||||
"example": "domain.org"
|
"example": "example.com"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "path",
|
"name": "path",
|
||||||
"type":"path",
|
"type": "path",
|
||||||
"ask": {
|
"ask": {
|
||||||
"en": "Choose a path for Etherpad",
|
"en": "Choose a path for Etherpad",
|
||||||
"fr": "Choisissez un chemin pour Etherpad"
|
"fr": "Choisissez un chemin pour Etherpad"
|
||||||
},
|
},
|
||||||
"example": "/pad",
|
"example": "/etherpad",
|
||||||
"default": "/pad"
|
"default": "/etherpad"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "is_public",
|
"name": "is_public",
|
||||||
|
@ -48,7 +48,39 @@
|
||||||
"en": "Is it a public application?",
|
"en": "Is it a public application?",
|
||||||
"fr": "Est-ce une application publique ?"
|
"fr": "Est-ce une application publique ?"
|
||||||
},
|
},
|
||||||
|
"help": {
|
||||||
|
"en": "If enabled, Etherpad will be accessible by people who do not have an account. This can be changed later via the webadmin.",
|
||||||
|
"fr": "Si cette case est cochée, Etherpad sera accessible aux personnes n’ayant pas de compte. Vous pourrez changer ceci plus tard via la webadmin."
|
||||||
|
},
|
||||||
"default": true
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "language",
|
||||||
|
"type": "string",
|
||||||
|
"ask": {
|
||||||
|
"en": "Choose the application language",
|
||||||
|
"fr": "Choisissez la langue de l'application"
|
||||||
|
},
|
||||||
|
"choices": ["fr", "en"],
|
||||||
|
"default": "fr"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "admin",
|
||||||
|
"type": "user",
|
||||||
|
"ask": {
|
||||||
|
"en": "Choose an admin user",
|
||||||
|
"fr": "Choisissez l'administrateur"
|
||||||
|
},
|
||||||
|
"example": "johndoe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "password",
|
||||||
|
"type": "password",
|
||||||
|
"ask": {
|
||||||
|
"en": "Set the administrator password",
|
||||||
|
"fr": "Définissez le mot de passe administrateur"
|
||||||
|
},
|
||||||
|
"example": "Choose a password"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
35
scripts/_common.sh
Normal file
35
scripts/_common.sh
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# COMMON VARIABLES
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
# dependencies used by the app
|
||||||
|
pkg_dependencies="postgresql apt-transport-https"
|
||||||
|
|
||||||
|
nodejs_version=12
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# PERSONAL HELPERS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# EXPERIMENTAL HELPERS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# FUTURE OFFICIAL HELPERS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
# Execute a command as another user
|
||||||
|
# usage: ynh_exec_as USER COMMAND [ARG ...]
|
||||||
|
ynh_exec_as() {
|
||||||
|
local USER=$1
|
||||||
|
shift 1
|
||||||
|
|
||||||
|
if [[ $USER = $(whoami) ]]; then
|
||||||
|
eval "$@"
|
||||||
|
else
|
||||||
|
sudo -u "$USER" "$@"
|
||||||
|
fi
|
||||||
|
}
|
|
@ -1,24 +1,67 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Exit on command errors and treat unset variables as an error
|
#=================================================
|
||||||
set -eu
|
# GENERIC START
|
||||||
|
#=================================================
|
||||||
|
# IMPORT GENERIC HELPERS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
# See comments in install script
|
source ../settings/scripts/_common.sh
|
||||||
app=$YNH_APP_INSTANCE_NAME
|
|
||||||
|
|
||||||
# Source YunoHost helpers
|
|
||||||
source /usr/share/yunohost/helpers
|
source /usr/share/yunohost/helpers
|
||||||
|
|
||||||
# Backup sources & data
|
#=================================================
|
||||||
# Note: the last argument is where to save this path, see the restore script.
|
# MANAGE SCRIPT FAILURE
|
||||||
ynh_backup "/var/www/${app}" "sources"
|
#=================================================
|
||||||
|
|
||||||
# Dump the database
|
ynh_clean_setup () {
|
||||||
dbname=$app
|
ynh_clean_check_starting
|
||||||
dbuser=$app
|
}
|
||||||
dbpass=$(ynh_app_setting_get "$app" dbpass)
|
# Exit if an error occurs during the execution of the script
|
||||||
mysqldump -u "$dbuser" -p"$dbpass" --no-create-db "$dbname" > ./dump.sql
|
ynh_abort_if_errors
|
||||||
|
|
||||||
# Copy NGINX configuration
|
#=================================================
|
||||||
domain=$(ynh_app_setting_get "$app" domain)
|
# LOAD SETTINGS
|
||||||
ynh_backup "/etc/nginx/conf.d/${domain}.d/${app}.conf" "nginx.conf"
|
#=================================================
|
||||||
|
ynh_print_info --message="Loading installation settings..."
|
||||||
|
|
||||||
|
app=$YNH_APP_INSTANCE_NAME
|
||||||
|
|
||||||
|
final_path=$(ynh_app_setting_get --app=$app --key=final_path)
|
||||||
|
domain=$(ynh_app_setting_get --app=$app --key=domain)
|
||||||
|
db_name=$(ynh_app_setting_get --app=$app --key=db_name)
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# DECLARE DATA AND CONF FILES TO BACKUP
|
||||||
|
#=================================================
|
||||||
|
ynh_print_info --message="Declaring files to be backed up..."
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# BACKUP THE APP MAIN DIR
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_backup --src_path="$final_path"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# BACKUP THE NGINX CONFIGURATION
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_backup --src_path="/etc/nginx/conf.d/$domain.d/$app.conf"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# BACKUP SYSTEMD
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_backup --src_path="/etc/systemd/system/$app.service"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# BACKUP THE POSTQRESQL DATABASE
|
||||||
|
#=================================================
|
||||||
|
ynh_print_info --message="Backing up the PostgreSQL database..."
|
||||||
|
|
||||||
|
ynh_psql_dump_db --database="$db_name" > db.sql
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# END OF SCRIPT
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_print_info --message="Backup script completed for Etherpad. (YunoHost will then actually copy those files to the archive)."
|
||||||
|
|
123
scripts/change_url
Normal file
123
scripts/change_url
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# GENERIC STARTING
|
||||||
|
#=================================================
|
||||||
|
# IMPORT GENERIC HELPERS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
source _common.sh
|
||||||
|
source /usr/share/yunohost/helpers
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# RETRIEVE ARGUMENTS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
old_domain=$YNH_APP_OLD_DOMAIN
|
||||||
|
old_path=$YNH_APP_OLD_PATH
|
||||||
|
|
||||||
|
new_domain=$YNH_APP_NEW_DOMAIN
|
||||||
|
new_path=$YNH_APP_NEW_PATH
|
||||||
|
|
||||||
|
app=$YNH_APP_INSTANCE_NAME
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# LOAD SETTINGS
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Loading installation settings..." --weight=1
|
||||||
|
|
||||||
|
# Needed for helper "ynh_add_nginx_config"
|
||||||
|
final_path=$(ynh_app_setting_get --app=$app --key=final_path)
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# BACKUP BEFORE UPGRADE THEN ACTIVE TRAP
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Backing up Etherpad before changing its URL (may take a while)..." --weight=2
|
||||||
|
|
||||||
|
# Backup the current version of Etherpad
|
||||||
|
ynh_backup_before_upgrade
|
||||||
|
ynh_clean_setup () {
|
||||||
|
# Remove the new domain config file, the remove script won't do it as it doesn't know yet its location.
|
||||||
|
ynh_secure_remove --file="/etc/nginx/conf.d/$new_domain.d/$app.conf"
|
||||||
|
|
||||||
|
# restore it if the upgrade fails
|
||||||
|
ynh_restore_upgradebackup
|
||||||
|
}
|
||||||
|
# Exit if an error occurs during the execution of the script
|
||||||
|
ynh_abort_if_errors
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# CHECK WHICH PARTS SHOULD BE CHANGED
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
change_domain=0
|
||||||
|
if [ "$old_domain" != "$new_domain" ]
|
||||||
|
then
|
||||||
|
change_domain=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
change_path=0
|
||||||
|
if [ "$old_path" != "$new_path" ]
|
||||||
|
then
|
||||||
|
change_path=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# STANDARD MODIFICATIONS
|
||||||
|
#=================================================
|
||||||
|
# STOP SYSTEMD SERVICE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Stopping a systemd service..." --time --weight=1
|
||||||
|
|
||||||
|
ynh_systemd_action --service_name=$app --action="stop" --log_path="/var/log/$app/$app.log"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# MODIFY URL IN NGINX CONF
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Updating NGINX web server configuration..." --time --weight=1
|
||||||
|
|
||||||
|
nginx_conf_path=/etc/nginx/conf.d/$old_domain.d/$app.conf
|
||||||
|
|
||||||
|
# Change the path in the NGINX config file
|
||||||
|
if [ $change_path -eq 1 ]
|
||||||
|
then
|
||||||
|
# Make a backup of the original NGINX config file if modified
|
||||||
|
ynh_backup_if_checksum_is_different --file="$nginx_conf_path"
|
||||||
|
# Set global variables for NGINX helper
|
||||||
|
domain="$old_domain"
|
||||||
|
path_url="$new_path"
|
||||||
|
# Create a dedicated NGINX config
|
||||||
|
ynh_add_nginx_config
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Change the domain for NGINX
|
||||||
|
if [ $change_domain -eq 1 ]
|
||||||
|
then
|
||||||
|
# Delete file checksum for the old conf file location
|
||||||
|
ynh_delete_file_checksum --file="$nginx_conf_path"
|
||||||
|
mv $nginx_conf_path /etc/nginx/conf.d/$new_domain.d/$app.conf
|
||||||
|
# Store file checksum for the new config file location
|
||||||
|
ynh_store_file_checksum --file="/etc/nginx/conf.d/$new_domain.d/$app.conf"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# GENERIC FINALISATION
|
||||||
|
#=================================================
|
||||||
|
# START SYSTEMD SERVICE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Starting a systemd service..." --time --weight=1
|
||||||
|
|
||||||
|
ynh_systemd_action --service_name=$app --action="start" --log_path="/var/log/$app/$app.log"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# RELOAD NGINX
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Reloading NGINX web server..." --weight=2
|
||||||
|
|
||||||
|
ynh_systemd_action --service_name=nginx --action=reload
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# END OF SCRIPT
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_script_progression --message="Change of URL completed for Etherpad" --last
|
278
scripts/install
278
scripts/install
|
@ -1,100 +1,212 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# GENERIC START
|
||||||
|
#=================================================
|
||||||
|
# IMPORT GENERIC HELPERS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
source _common.sh
|
||||||
|
source /usr/share/yunohost/helpers
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# MANAGE SCRIPT FAILURE
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_clean_setup () {
|
||||||
|
ynh_clean_check_starting
|
||||||
|
}
|
||||||
|
# Exit if an error occurs during the execution of the script
|
||||||
|
ynh_abort_if_errors
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# RETRIEVE ARGUMENTS FROM THE MANIFEST
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
domain=$YNH_APP_ARG_DOMAIN
|
||||||
|
path_url=$YNH_APP_ARG_PATH
|
||||||
|
admin=$YNH_APP_ARG_ADMIN
|
||||||
|
is_public=$YNH_APP_ARG_IS_PUBLIC
|
||||||
|
language=$YNH_APP_ARG_LANGUAGE
|
||||||
|
password=$YNH_APP_ARG_PASSWORD
|
||||||
|
|
||||||
app=$YNH_APP_INSTANCE_NAME
|
app=$YNH_APP_INSTANCE_NAME
|
||||||
|
|
||||||
# Source YunoHost helpers
|
#=================================================
|
||||||
source /usr/share/yunohost/helpers
|
# CHECK IF THE APP CAN BE INSTALLED WITH THESE ARGS
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Validating installation parameters..." --weight=2
|
||||||
|
|
||||||
# Retrieve arguments
|
|
||||||
domain=$YNH_APP_ARG_DOMAIN
|
|
||||||
path=$YNH_APP_ARG_PATH
|
|
||||||
is_public=$YNH_APP_ARG_IS_PUBLIC
|
|
||||||
port=9001
|
|
||||||
|
|
||||||
# Check domain/path availability
|
|
||||||
sudo yunohost app checkurl $domain$path -a $app
|
|
||||||
if [[ ! $? -eq 0 ]]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Store config on YunoHost instance
|
|
||||||
ynh_app_setting_set "$app" domain "$domain"
|
|
||||||
ynh_app_setting_set "$app" path "$path"
|
|
||||||
ynh_app_setting_set "$app" is_public "$is_public"
|
|
||||||
|
|
||||||
# Remove trailing "/" for next commands
|
|
||||||
path=${path%/}
|
|
||||||
|
|
||||||
# Generate random key
|
|
||||||
# key=$(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d '[A-Za-z0-9]' | sed -n 's/\(.\{40\}\).*/\1/p')
|
|
||||||
key=$(ynh_string_random 12)
|
|
||||||
|
|
||||||
# Generate MySQL password and create database
|
|
||||||
dbuser=$app
|
|
||||||
dbname=$app
|
|
||||||
dbpass=$(ynh_string_random 12)
|
|
||||||
ynh_mysql_create_db "$dbname" "$dbuser" "$dbpass"
|
|
||||||
ynh_app_setting_set "$app" dbpass "$dbpass"
|
|
||||||
ynh_app_setting_set "$app" dbuser "$dbuser"
|
|
||||||
ynh_app_setting_set "$app" dbname "$dbname"
|
|
||||||
|
|
||||||
|
|
||||||
# Install dependances
|
|
||||||
if ! ynh_package_is_installed "nodejs-legacy" ; then
|
|
||||||
sudo apt-get install nodejs-legacy npm -y
|
|
||||||
fi
|
|
||||||
|
|
||||||
# create app dir and copy sources
|
|
||||||
final_path=/var/www/$app
|
final_path=/var/www/$app
|
||||||
sudo mkdir -p $final_path
|
test ! -e "$final_path" || ynh_die --message="This path already contains a folder"
|
||||||
|
|
||||||
sudo cp -a ../sources/* $final_path
|
# Register (book) web path
|
||||||
|
ynh_webpath_register --app=$app --domain=$domain --path_url=$path_url
|
||||||
|
|
||||||
sudo cp ../conf/settings.json $final_path
|
#=================================================
|
||||||
# Change variables in etherpad configuration
|
# STORE SETTINGS FROM MANIFEST
|
||||||
sudo sed -i "s/yunouser/$dbuser/g" $final_path/settings.json
|
#=================================================
|
||||||
sudo sed -i "s/yunopass/$dbpass/g" $final_path/settings.json
|
ynh_script_progression --message="Storing installation settings..." --weight=2
|
||||||
sudo sed -i "s/yunobase/$dbname/g" $final_path/settings.json
|
|
||||||
sudo sed -i "s/KEY/$key/g" $final_path/settings.json
|
|
||||||
|
|
||||||
sudo npm cache clear
|
ynh_app_setting_set --app=$app --key=domain --value=$domain
|
||||||
sudo $final_path/bin/installDeps.sh > /dev/null 2>&1
|
ynh_app_setting_set --app=$app --key=path --value=$path_url
|
||||||
sudo npm install forever -g > /dev/null 2>&1
|
ynh_app_setting_set --app=$app --key=admin --value=$admin
|
||||||
|
ynh_app_setting_set --app=$app --key=is_public --value=$is_public
|
||||||
|
ynh_app_setting_set --app=$app --key=language --value=$language
|
||||||
|
ynh_app_setting_set --app=$app --key=password --value=$password
|
||||||
|
|
||||||
# Set permissions to etherpad directory
|
#=================================================
|
||||||
sudo chown -R www-data: $final_path
|
# STANDARD MODIFICATIONS
|
||||||
|
#=================================================
|
||||||
|
# FIND AND OPEN A PORT
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Configuring firewall..." --weight=1
|
||||||
|
|
||||||
# service
|
# Find an available port
|
||||||
sudo cp ../conf/etherpad-lite /etc/init.d/$app
|
port=$(ynh_find_port --port=9001)
|
||||||
sudo sed -i "s/APPTOCHANGE/$app/g" /etc/init.d/$app
|
ynh_app_setting_set --app=$app --key=port --value=$port
|
||||||
sudo chmod +x /etc/init.d/$app
|
|
||||||
sudo update-rc.d $app defaults
|
|
||||||
|
|
||||||
# logs
|
#=================================================
|
||||||
sudo mkdir /var/log/$app
|
# INSTALL DEPENDENCIES
|
||||||
sudo touch /var/log/$app/$app.log
|
#=================================================
|
||||||
sudo chown www-data /var/log/$app/$app.log
|
ynh_script_progression --message="Installing dependencies..." --weight=12
|
||||||
|
|
||||||
# nginx
|
ynh_install_app_dependencies $pkg_dependencies
|
||||||
nginx_conf_path=/etc/nginx/conf.d/$domain.d/$app.conf
|
|
||||||
if [ "$path" = "" ]; then
|
|
||||||
sudo cp ../conf/nginx.conf-nosub $nginx_conf_path
|
|
||||||
else
|
|
||||||
sudo cp ../conf/nginx.conf $nginx_conf_path
|
|
||||||
# Modify Nginx configuration file and copy it to Nginx conf directory
|
|
||||||
|
|
||||||
sudo sed -i "s@YNH_PATH@$path@g" $nginx_conf_path
|
ynh_install_nodejs --nodejs_version=$nodejs_version
|
||||||
sudo sed -i "s@YNH_PORT@$port@g" $nginx_conf_path
|
|
||||||
sudo sed -i "s@YNH_DOMAIN@$domain@g" $nginx_conf_path
|
#=================================================
|
||||||
fi
|
# CREATE A POSTQRESQL DATABASE
|
||||||
# Reload Nginx and regenerate SSOwat conf
|
#=================================================
|
||||||
sudo service nginx reload
|
ynh_script_progression --message="Creating a PostgreSQL database..." --weight=5
|
||||||
# If app is public, add url to SSOWat conf as skipped_uris
|
|
||||||
if [[ $is_public -eq 1 ]]; then
|
db_name=$(ynh_sanitize_dbid --db_name=$app)
|
||||||
# unprotected_uris allows SSO credentials to be passed anyway.
|
db_user=$db_name
|
||||||
ynh_app_setting_set "$app" unprotected_uris "/"
|
db_pwd=$(ynh_string_random --length=30)
|
||||||
|
|
||||||
|
ynh_app_setting_set --app=$app --key=db_name --value=$db_name
|
||||||
|
ynh_app_setting_set --app=$app --key=db_pwd --value=$db_pwd
|
||||||
|
|
||||||
|
ynh_psql_test_if_first_run
|
||||||
|
ynh_psql_setup_db --db_user=$db_user --db_name=$db_name --db_pwd=$db_pwd
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# DOWNLOAD, CHECK AND UNPACK SOURCE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Setting up source files..." --weight=1
|
||||||
|
|
||||||
|
ynh_app_setting_set --app=$app --key=final_path --value=$final_path
|
||||||
|
# Download, check integrity, uncompress and patch the source from app.src
|
||||||
|
ynh_setup_source --dest_dir="$final_path"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# NGINX CONFIGURATION
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Configuring NGINX web server..." --weight=4
|
||||||
|
|
||||||
|
# Create a dedicated NGINX config
|
||||||
|
ynh_add_nginx_config
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# CREATE DEDICATED USER
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Configuring system user..." --weight=4
|
||||||
|
|
||||||
|
# Create a system user
|
||||||
|
ynh_system_user_create --username=$app --home_dir=$final_path
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# INSTALL ETHERPAD
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Installing Etherpad..." --weight=90
|
||||||
|
|
||||||
|
chown -R $app: $final_path
|
||||||
|
|
||||||
|
pushd "$final_path" || ynh_die
|
||||||
|
ynh_use_nodejs
|
||||||
|
ynh_exec_as $app env "$ynh_node_load_PATH" bin/installDeps.sh
|
||||||
|
popd || ynh_die
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# MODIFY A CONFIG FILE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Configuring Etherpad..." --weight=6
|
||||||
|
|
||||||
|
cp ../conf/settings.json $final_path/settings.json
|
||||||
|
|
||||||
|
ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$final_path/settings.json"
|
||||||
|
ynh_replace_string --match_string="__LANGUAGE__" --replace_string="$language" --target_file="$final_path/settings.json"
|
||||||
|
|
||||||
|
cp ../conf/credentials.json $final_path/credentials.json
|
||||||
|
|
||||||
|
ynh_replace_string --match_string="__DB_NAME__" --replace_string="$db_name" --target_file="$final_path/credentials.json"
|
||||||
|
ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$final_path/credentials.json"
|
||||||
|
ynh_replace_string --match_string="__ADMIN__" --replace_string="$admin" --target_file="$final_path/credentials.json"
|
||||||
|
ynh_replace_string --match_string="__PASSWORD__" --replace_string="$password" --target_file="$final_path/credentials.json"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# STORE THE CONFIG FILE CHECKSUM
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
# Calculate and store the config file checksum into the app settings
|
||||||
|
ynh_store_file_checksum --file="$final_path/settings.json"
|
||||||
|
ynh_store_file_checksum --file="$final_path/credentials.json"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# GENERIC FINALIZATION
|
||||||
|
#=================================================
|
||||||
|
# SECURE FILES AND DIRECTORIES
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
# Set permissions to app files
|
||||||
|
chown -R $app: $final_path
|
||||||
|
chmod 600 $final_path/credentials.json
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# SETUP SYSTEMD
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Configuring a systemd service..." --weight=4
|
||||||
|
|
||||||
|
# Create a dedicated systemd config
|
||||||
|
ynh_add_systemd_config --others_var="ynh_node_load_PATH"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# INTEGRATE SERVICE IN YUNOHOST
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Integrating service in YunoHost..." --weight=3
|
||||||
|
|
||||||
|
yunohost service add $app --description "Etherpad-lite, the collaborative editor." --log "/var/log/$app/$app.log"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# START SYSTEMD SERVICE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Starting a systemd service..." --weight=1
|
||||||
|
|
||||||
|
# Start a systemd service
|
||||||
|
ynh_systemd_action --service_name=$app --action="start" --log_path="/var/log/$app/$app.log" --line_match="Your Etherpad version is"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# SETUP SSOWAT
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Configuring SSOwat..." --weight=1
|
||||||
|
|
||||||
|
# Make app public if necessary
|
||||||
|
if [ $is_public -eq 1 ]
|
||||||
|
then
|
||||||
|
# Everyone can access the app.
|
||||||
|
# The "main" permission is automatically created before the install script.
|
||||||
|
ynh_permission_update --permission "main" --add "visitors"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo yunohost app ssowatconf
|
#=================================================
|
||||||
|
# RELOAD NGINX
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Reloading NGINX web server..." --weight=1
|
||||||
|
|
||||||
sudo service etherpad-lite start
|
ynh_systemd_action --service_name=nginx --action=reload
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# END OF SCRIPT
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_script_progression --message="Installation of Etherpad completed" --last
|
||||||
|
|
|
@ -1,31 +1,84 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# GENERIC START
|
||||||
|
#=================================================
|
||||||
|
# IMPORT GENERIC HELPERS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
# See comments in install script
|
source _common.sh
|
||||||
app=$YNH_APP_INSTANCE_NAME
|
|
||||||
|
|
||||||
# Source YunoHost helpers
|
|
||||||
source /usr/share/yunohost/helpers
|
source /usr/share/yunohost/helpers
|
||||||
|
|
||||||
# Retrieve app settings
|
#=================================================
|
||||||
domain=$(ynh_app_setting_get "$app" domain)
|
# LOAD SETTINGS
|
||||||
path=$(ynh_app_setting_get "$app" path)
|
#=================================================
|
||||||
dbuser=$(ynh_app_setting_get "$app" dbuser)
|
ynh_script_progression --message="Loading installation settings..." --weight=1
|
||||||
dbname=$(ynh_app_setting_get "$app" dbname)
|
|
||||||
# dbpass=$(ynh_app_setting_get "$app" dbpass)
|
|
||||||
|
|
||||||
|
app=$YNH_APP_INSTANCE_NAME
|
||||||
|
|
||||||
#mysql -u root -p $root_pwd -e "DROP DATABASE $dbname ; DROP USER $dbuser@localhost ;"
|
domain=$(ynh_app_setting_get --app=$app --key=domain)
|
||||||
# Drop MySQL database and user
|
port=$(ynh_app_setting_get --app=$app --key=port)
|
||||||
ynh_mysql_drop_db "$dbname" || true
|
db_name=$(ynh_app_setting_get --app=$app --key=db_name)
|
||||||
ynh_mysql_drop_user "$dbuser" || true
|
db_user=$db_name
|
||||||
|
final_path=$(ynh_app_setting_get --app=$app --key=final_path)
|
||||||
|
|
||||||
sudo rm -rf /var/www/$app
|
#=================================================
|
||||||
|
# STANDARD REMOVE
|
||||||
|
#=================================================
|
||||||
|
# REMOVE SERVICE INTEGRATION IN YUNOHOST
|
||||||
|
#=================================================
|
||||||
|
|
||||||
sudo rm -f /etc/nginx/conf.d/$domain.d/$app.conf
|
# Remove the service from the list of services known by YunoHost (added from `yunohost service add`)
|
||||||
sudo service nginx reload
|
if ynh_exec_warn_less yunohost service status $app >/dev/null
|
||||||
sudo rm -Rf /var/log/$app
|
then
|
||||||
|
ynh_script_progression --message="Removing Etherpad service integration..." --weight=2
|
||||||
|
yunohost service remove $app
|
||||||
|
fi
|
||||||
|
|
||||||
sudo update-rc.d $app remove
|
#=================================================
|
||||||
sudo service $app stop
|
# STOP AND REMOVE SERVICE
|
||||||
sudo rm /etc/init.d/$app
|
#=================================================
|
||||||
|
ynh_script_progression --message="Stopping and removing the systemd service..." --weight=1
|
||||||
|
|
||||||
|
# Remove the dedicated systemd config
|
||||||
|
ynh_remove_systemd_config
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# REMOVE THE POSTQRESQL DATABASE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Removing the PostgreSQL database..." --weight=2
|
||||||
|
|
||||||
|
# Remove a database if it exists, along with the associated user
|
||||||
|
ynh_psql_remove_db --db_user=$db_user --db_name=$db_name
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# REMOVE ETHERPAD MAIN DIR
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Removing Etherpad main directory..." --weight=3
|
||||||
|
|
||||||
|
# Remove the app directory securely
|
||||||
|
ynh_secure_remove --file="$final_path"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# REMOVE NGINX CONFIGURATION
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Removing NGINX web server configuration..." --weight=1
|
||||||
|
|
||||||
|
# Remove the dedicated NGINX config
|
||||||
|
ynh_remove_nginx_config
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# GENERIC FINALIZATION
|
||||||
|
#=================================================
|
||||||
|
# REMOVE DEDICATED USER
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Removing the dedicated system user..." --weight=1
|
||||||
|
|
||||||
|
# Delete a system user
|
||||||
|
ynh_system_user_delete --username=$app
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# END OF SCRIPT
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_script_progression --message="Removal of Etherpad completed" --last
|
||||||
|
|
141
scripts/restore
141
scripts/restore
|
@ -1,42 +1,121 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Note: each files and directories you've saved using the ynh_backup helper
|
#=================================================
|
||||||
# will be located in the current directory, regarding the last argument.
|
# GENERIC START
|
||||||
|
#=================================================
|
||||||
|
# IMPORT GENERIC HELPERS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
# Exit on command errors and treat unset variables as an error
|
source ../settings/scripts/_common.sh
|
||||||
set -eu
|
|
||||||
|
|
||||||
# See comments in install script
|
|
||||||
app=$YNH_APP_INSTANCE_NAME
|
|
||||||
|
|
||||||
# Source YunoHost helpers
|
|
||||||
source /usr/share/yunohost/helpers
|
source /usr/share/yunohost/helpers
|
||||||
|
|
||||||
# Retrieve old app settings
|
#=================================================
|
||||||
domain=$(ynh_app_setting_get "$app" domain)
|
# MANAGE SCRIPT FAILURE
|
||||||
path=$(ynh_app_setting_get "$app" path)
|
#=================================================
|
||||||
|
|
||||||
# Check domain/path availability
|
ynh_clean_setup () {
|
||||||
sudo yunohost app checkurl "${domain}${path}" -a "$app" \
|
ynh_clean_check_starting
|
||||||
|| ynh_die "Path not available: ${domain}${path}"
|
}
|
||||||
|
# Exit if an error occurs during the execution of the script
|
||||||
|
ynh_abort_if_errors
|
||||||
|
|
||||||
# Restore sources & data
|
#=================================================
|
||||||
src_path="/var/www/${app}"
|
# LOAD SETTINGS
|
||||||
sudo cp -a ./sources "$src_path"
|
#=================================================
|
||||||
|
ynh_script_progression --message="Loading installation settings..." --weight=1
|
||||||
|
|
||||||
# Restore permissions to app files
|
app=$YNH_APP_INSTANCE_NAME
|
||||||
# you may need to make some file and/or directory writeable by www-data (nginx user)
|
|
||||||
sudo chown -R root: "$src_path"
|
|
||||||
|
|
||||||
# Create and restore the database
|
domain=$(ynh_app_setting_get --app=$app --key=domain)
|
||||||
dbname=$app
|
path_url=$(ynh_app_setting_get --app=$app --key=path)
|
||||||
dbuser=$app
|
final_path=$(ynh_app_setting_get --app=$app --key=final_path)
|
||||||
dbpass=$(ynh_app_setting_get "$app" dbpass)
|
db_name=$(ynh_app_setting_get --app=$app --key=db_name)
|
||||||
ynh_mysql_create_db "$dbname" "$dbuser" "$dbpass"
|
db_user=$db_name
|
||||||
ynh_mysql_connect_as "$dbuser" "$dbpass" "$dbname" < ./dump.sql
|
db_pwd=$(ynh_app_setting_get --app=$app --key=db_pwd)
|
||||||
|
|
||||||
# Restore NGINX configuration
|
#=================================================
|
||||||
sudo cp -a ./nginx.conf "/etc/nginx/conf.d/${domain}.d/${app}.conf"
|
# CHECK IF ETHERPAD CAN BE RESTORED
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Validating restoration parameters..." --weight=2
|
||||||
|
|
||||||
# Restart webserver
|
ynh_webpath_available --domain=$domain --path_url=$path_url \
|
||||||
sudo service nginx reload
|
|| ynh_die --message="Path not available: ${domain}${path_url}"
|
||||||
|
test ! -d $final_path \
|
||||||
|
|| ynh_die --message="There is already a directory: $final_path "
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# STANDARD RESTORATION STEPS
|
||||||
|
#=================================================
|
||||||
|
# RESTORE THE NGINX CONFIGURATION
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# RESTORE THE APP MAIN DIR
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Restoring the app main directory..." --weight=2
|
||||||
|
|
||||||
|
ynh_restore_file --origin_path="$final_path"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# RECREATE THE DEDICATED USER
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Recreating the dedicated system user..." --weight=1
|
||||||
|
|
||||||
|
# Create the dedicated user (if not existing)
|
||||||
|
ynh_system_user_create --username=$app
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# RESTORE USER RIGHTS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
# Restore permissions on app files
|
||||||
|
chown -R $app: $final_path
|
||||||
|
chmod 600 $final_path/credentials.json
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# RESTORE THE POSTQRESQL DATABASE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Restoring the PostgreSQL database..." --weight=2
|
||||||
|
|
||||||
|
ynh_psql_test_if_first_run
|
||||||
|
ynh_psql_setup_db --db_user=$db_user --db_name=$db_name --db_pwd=$db_pwd
|
||||||
|
ynh_psql_execute_file_as_root --file="./db.sql" --database=$db_name
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# RESTORE SYSTEMD
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Restoring the systemd configuration..." --weight=8
|
||||||
|
|
||||||
|
ynh_restore_file --origin_path="/etc/systemd/system/$app.service"
|
||||||
|
systemctl enable $app.service
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# INTEGRATE SERVICE IN YUNOHOST
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Integrating service in YunoHost..." --weight=3
|
||||||
|
|
||||||
|
yunohost service add $app --description "Etherpad-lite, the collaborative editor." --log "/var/log/$app/$app.log"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# START SYSTEMD SERVICE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Starting a systemd service..." --weight=8
|
||||||
|
|
||||||
|
ynh_systemd_action --service_name=$app --action="start" --log_path="/var/log/$app/$app.log"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# GENERIC FINALIZATION
|
||||||
|
#=================================================
|
||||||
|
# RELOAD NGINX AND PHP-FPM
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Reloading NGINX web server..." --weight=2
|
||||||
|
|
||||||
|
ynh_systemd_action --service_name=nginx --action=reload
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# END OF SCRIPT
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_script_progression --message="Restoration completed for Etherpad" --last
|
||||||
|
|
226
scripts/upgrade
226
scripts/upgrade
|
@ -1,70 +1,182 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# GENERIC START
|
||||||
|
#=================================================
|
||||||
|
# IMPORT GENERIC HELPERS
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
source _common.sh
|
||||||
|
source /usr/share/yunohost/helpers
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# LOAD SETTINGS
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Loading installation settings..." --weight=1
|
||||||
|
|
||||||
app=$YNH_APP_INSTANCE_NAME
|
app=$YNH_APP_INSTANCE_NAME
|
||||||
|
|
||||||
# Source YunoHost helpers
|
domain=$(ynh_app_setting_get --app=$app --key=domain)
|
||||||
source /usr/share/yunohost/helpers
|
path_url=$(ynh_app_setting_get --app=$app --key=path)
|
||||||
|
admin=$(ynh_app_setting_get --app=$app --key=admin)
|
||||||
|
is_public=$(ynh_app_setting_get --app=$app --key=is_public)
|
||||||
|
final_path=$(ynh_app_setting_get --app=$app --key=final_path)
|
||||||
|
language=$(ynh_app_setting_get --app=$app --key=language)
|
||||||
|
db_name=$(ynh_app_setting_get --app=$app --key=db_name)
|
||||||
|
|
||||||
# Retrieve app settings
|
#=================================================
|
||||||
domain=$(ynh_app_setting_get "$app" domain)
|
# CHECK VERSION
|
||||||
path=$(ynh_app_setting_get "$app" path)
|
#=================================================
|
||||||
dbuser=$(ynh_app_setting_get "$app" dbuser)
|
|
||||||
dbname=$(ynh_app_setting_get "$app" dbname)
|
|
||||||
is_public=$(ynh_app_setting_get "$app" is_public)
|
|
||||||
port=9001
|
|
||||||
|
|
||||||
root_pwd=$(sudo cat /etc/yunohost/mysql)
|
upgrade_type=$(ynh_check_app_version_changed)
|
||||||
|
|
||||||
# Remove trailing "/" for next commands
|
#=================================================
|
||||||
path=${path%/}
|
# ENSURE DOWNWARD COMPATIBILITY
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Ensuring downward compatibility..." --weight=2
|
||||||
|
|
||||||
|
# Fix is_public as a boolean value
|
||||||
# create app dir and compy sources
|
if [ "$is_public" = "Yes" ]; then
|
||||||
final_path=/var/www/$app
|
ynh_app_setting_set --app=$app --key=is_public --value=1
|
||||||
sudo mkdir -p $final_path
|
is_public=1
|
||||||
|
elif [ "$is_public" = "No" ]; then
|
||||||
sudo cp -a ../sources/* $final_path
|
ynh_app_setting_set --app=$app --key=is_public --value=0
|
||||||
|
is_public=0
|
||||||
sudo cp ../conf/settings.json $final_path
|
|
||||||
# Change variables in etherpad configuration
|
|
||||||
sudo sed -i "s/yunouser/$dbuser/g" $final_path/settings.json
|
|
||||||
sudo sed -i "s/yunopass/$dbpass/g" $final_path/settings.json
|
|
||||||
sudo sed -i "s/yunobase/$dbname/g" $final_path/settings.json
|
|
||||||
sudo sed -i "s/KEY/$key/g" $final_path/settings.json
|
|
||||||
|
|
||||||
sudo npm cache clear
|
|
||||||
sudo $final_path/bin/installDeps.sh > /dev/null 2>&1
|
|
||||||
sudo npm install forever -g > /dev/null 2>&1
|
|
||||||
|
|
||||||
# Set permissions to etherpad directory
|
|
||||||
sudo chown -R www-data: $final_path
|
|
||||||
|
|
||||||
# service
|
|
||||||
sudo cp ../conf/etherpad-lite /etc/init.d/$app
|
|
||||||
sudo sed -i "s/APPTOCHANGE/$app/g" /etc/init.d/$app
|
|
||||||
sudo chmod +x /etc/init.d/$app
|
|
||||||
sudo update-rc.d $app defaults
|
|
||||||
|
|
||||||
# nginx
|
|
||||||
nginx_conf_path=/etc/nginx/conf.d/$domain.d/$app.conf
|
|
||||||
if [ "$path" = "" ]; then
|
|
||||||
sudo cp ../conf/nginx.conf-nosub $nginx_conf_path
|
|
||||||
else
|
|
||||||
sudo cp ../conf/nginx.conf $nginx_conf_path
|
|
||||||
# Modify Nginx configuration file and copy it to Nginx conf directory
|
|
||||||
sudo sed -i "s@YNH_PATH@$path@g" $nginx_conf_path
|
|
||||||
sudo sed -i "s@YNH_PORT@$port@g" $nginx_conf_path
|
|
||||||
sudo sed -i "s@YNH_DOMAIN@$domain@g" $nginx_conf_path
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Reload Nginx and regenerate SSOwat conf
|
# If db_name doesn't exist, create it
|
||||||
sudo service nginx reload
|
if [ -z "$db_name" ]; then
|
||||||
# If app is public, add url to SSOWat conf as skipped_uris
|
db_name=$(ynh_sanitize_dbid --db_name=$app)
|
||||||
if [[ $is_public -eq 1 ]]; then
|
ynh_app_setting_set --app=$app --key=db_name --value=$db_name
|
||||||
# unprotected_uris allows SSO credentials to be passed anyway.
|
|
||||||
ynh_app_setting_set "$app" unprotected_uris "/"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo yunohost app ssowatconf
|
# If final_path doesn't exist, create it
|
||||||
|
if [ -z "$final_path" ]; then
|
||||||
|
final_path=/var/www/$app
|
||||||
|
ynh_app_setting_set --app=$app --key=final_path --value=$final_path
|
||||||
|
fi
|
||||||
|
|
||||||
sudo service etherpad-lite start
|
#=================================================
|
||||||
|
# BACKUP BEFORE UPGRADE THEN ACTIVE TRAP
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Backing up Etherpad before upgrading (may take a while)..." --weight=1
|
||||||
|
|
||||||
|
# Backup the current version of the app
|
||||||
|
ynh_backup_before_upgrade
|
||||||
|
ynh_clean_setup () {
|
||||||
|
# restore it if the upgrade fails
|
||||||
|
ynh_restore_upgradebackup
|
||||||
|
}
|
||||||
|
# Exit if an error occurs during the execution of the script
|
||||||
|
ynh_abort_if_errors
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# STANDARD UPGRADE STEPS
|
||||||
|
#=================================================
|
||||||
|
# STOP SYSTEMD SERVICE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Stopping a systemd service..." --weight=42
|
||||||
|
|
||||||
|
ynh_systemd_action --service_name=$app --action="stop" --log_path="/var/log/$app/$app.log"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# DOWNLOAD, CHECK AND UNPACK SOURCE
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
if [ "$upgrade_type" == "UPGRADE_APP" ]
|
||||||
|
then
|
||||||
|
ynh_script_progression --message="Upgrading source files..." --weight=1
|
||||||
|
|
||||||
|
# Download, check integrity, uncompress and patch the source from app.src
|
||||||
|
ynh_setup_source --dest_dir="$final_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# NGINX CONFIGURATION
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Upgrading NGINX web server configuration..." --weight=3
|
||||||
|
|
||||||
|
# Create a dedicated NGINX config
|
||||||
|
ynh_add_nginx_config
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# CREATE DEDICATED USER
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Making sure dedicated system user exists..." --time --weight=1
|
||||||
|
|
||||||
|
# Create a dedicated user (if not existing)
|
||||||
|
ynh_system_user_create --username=$app
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# INSTALL ETHERPAD
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Installing Etherpad..." --weight=90
|
||||||
|
|
||||||
|
chown -R $app: $final_path
|
||||||
|
|
||||||
|
pushd "$final_path" || ynh_die
|
||||||
|
ynh_use_nodejs
|
||||||
|
ynh_exec_as $app env $ynh_node_load_PATH bin/installDeps.sh
|
||||||
|
popd || ynh_die
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# MODIFY A CONFIG FILE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Reconfiguring Etherpad..." --weight=6
|
||||||
|
|
||||||
|
cp ../conf/settings.json $final_path/settings.json
|
||||||
|
|
||||||
|
ynh_replace_string --match_string="__PORT__" --replace_string="$port" --target_file="$final_path/settings.json"
|
||||||
|
ynh_replace_string --match_string="__LANGUAGE__" --replace_string="$language" --target_file="$final_path/settings.json"
|
||||||
|
|
||||||
|
cp ../conf/credentials.json $final_path/credentials.json
|
||||||
|
|
||||||
|
ynh_replace_string --match_string="__DB_NAME__" --replace_string="$db_name" --target_file="$final_path/credentials.json"
|
||||||
|
ynh_replace_string --match_string="__DB_PWD__" --replace_string="$db_pwd" --target_file="$final_path/credentials.json"
|
||||||
|
ynh_replace_string --match_string="__ADMIN__" --replace_string="$admin" --target_file="$final_path/credentials.json"
|
||||||
|
ynh_replace_string --match_string="__PASSWORD__" --replace_string="$password" --target_file="$final_path/credentials.json"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# GENERIC FINALIZATION
|
||||||
|
#=================================================
|
||||||
|
# SECURE FILES AND DIRECTORIES
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
# Set permissions on app files
|
||||||
|
chown -R $app: $final_path
|
||||||
|
chmod 600 $final_path/credentials.json
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# SETUP SYSTEMD
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Upgrading systemd configuration..." --time --weight=1
|
||||||
|
|
||||||
|
# Create a dedicated systemd config
|
||||||
|
ynh_add_systemd_config --others_var="ynh_node_load_PATH"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# INTEGRATE SERVICE IN YUNOHOST
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Integrating service in YunoHost..." --time --weight=1
|
||||||
|
|
||||||
|
yunohost service add $app --description "Etherpad-lite, the collaborative editor." --log "/var/log/$app/$app.log"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# START SYSTEMD SERVICE
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Starting a systemd service..." --time --weight=1
|
||||||
|
|
||||||
|
ynh_systemd_action --service_name=$app --action="start" --log_path="/var/log/$app/$app.log" --line_match="Your Etherpad version is"
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# RELOAD NGINX
|
||||||
|
#=================================================
|
||||||
|
ynh_script_progression --message="Reloading NGINX web server..." --weight=2
|
||||||
|
|
||||||
|
ynh_systemd_action --service_name=nginx --action=reload
|
||||||
|
|
||||||
|
#=================================================
|
||||||
|
# END OF SCRIPT
|
||||||
|
#=================================================
|
||||||
|
|
||||||
|
ynh_script_progression --message="Upgrade of Etherpad completed" --last
|
||||||
|
|
21
sources/.gitignore
vendored
21
sources/.gitignore
vendored
|
@ -1,21 +0,0 @@
|
||||||
node_modules
|
|
||||||
settings.json
|
|
||||||
!settings.json.template
|
|
||||||
APIKEY.txt
|
|
||||||
SESSIONKEY.txt
|
|
||||||
bin/abiword.exe
|
|
||||||
bin/node.exe
|
|
||||||
etherpad-lite-win.zip
|
|
||||||
var/dirty.db
|
|
||||||
bin/convertSettings.json
|
|
||||||
*~
|
|
||||||
*.patch
|
|
||||||
src/static/js/jquery.js
|
|
||||||
npm-debug.log
|
|
||||||
*.DS_Store
|
|
||||||
.ep_initialized
|
|
||||||
*.crt
|
|
||||||
*.key
|
|
||||||
bin/etherpad-1.deb
|
|
||||||
credentials.json
|
|
||||||
out/
|
|
|
@ -1,19 +0,0 @@
|
||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- "0.10"
|
|
||||||
install:
|
|
||||||
- "bin/installDeps.sh"
|
|
||||||
- "export GIT_HASH=$(git rev-parse --verify --short HEAD)"
|
|
||||||
- "npm install ep_test_line_attrib"
|
|
||||||
script:
|
|
||||||
- "tests/frontend/travis/runner.sh"
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- secure: "WMGxFkOeTTlhWB+ChMucRtIqVmMbwzYdNHuHQjKCcj8HBEPdZLfCuK/kf4rG\nVLcLQiIsyllqzNhBGVHG1nyqWr0/LTm8JRqSCDDVIhpyzp9KpCJQQJG2Uwjk\n6/HIJJh/wbxsEdLNV2crYU/EiVO3A4Bq0YTHUlbhUqG3mSCr5Ec="
|
|
||||||
- secure: "gejXUAHYscbR6Bodw35XexpToqWkv2ifeECsbeEmjaLkYzXmUUNWJGknKSu7\nEUsSfQV8w+hxApr1Z+jNqk9aX3K1I4btL3cwk2trnNI8XRAvu1c1Iv60eerI\nkE82Rsd5lwUaMEh+/HoL8ztFCZamVndoNgX7HWp5J/NRZZMmh4g="
|
|
||||||
jdk:
|
|
||||||
- oraclejdk6
|
|
||||||
notifications:
|
|
||||||
irc:
|
|
||||||
channels:
|
|
||||||
- "irc.freenode.org#etherpad-lite-dev"
|
|
|
@ -1,527 +0,0 @@
|
||||||
# 1.6.6
|
|
||||||
* FIX: line numbers are aligned with text again (broken in 1.6.4)
|
|
||||||
* FIX: text entered between connection loss and reconnection was not saved
|
|
||||||
* FIX: diagnostic call failed when etherpad was exposed in a subdirectory
|
|
||||||
|
|
||||||
# 1.6.5
|
|
||||||
* SECURITY: Escape data when listing available plugins
|
|
||||||
* FIX: Fix typo in apicalls.js which prevented importing isValidJSONPName
|
|
||||||
* FIX: fixed plugin dependency issue
|
|
||||||
* FIX: Update iframe_editor.css
|
|
||||||
* FIX: unbreak Safari iOS line wrapping
|
|
||||||
|
|
||||||
# 1.6.4
|
|
||||||
* SECURITY: Access Control bypass on /admin - CVE-2018-9845
|
|
||||||
* SECURITY: Remote Code Execution through pad export - CVE-2018-9327
|
|
||||||
* SECURITY: Remote Code Execution through JSONP handling - CVE-2018-9326
|
|
||||||
* SECURITY: Pad data leak - CVE-2018-9325
|
|
||||||
* Fix: Admin redirect URL
|
|
||||||
* Fix: Various script Fixes
|
|
||||||
* Fix: Various CSS/Style/Layout fixes
|
|
||||||
* NEW: Improved Pad contents readability
|
|
||||||
* NEW: Hook: onAccessCheck
|
|
||||||
* NEW: SESSIONKEY and APIKey customizable path
|
|
||||||
* NEW: checkPads script
|
|
||||||
* NEW: Support "cluster mode"
|
|
||||||
|
|
||||||
# 1.6.3
|
|
||||||
* SECURITY: Update ejs
|
|
||||||
* SECURITY: xss vulnerability when reading window.location.href
|
|
||||||
* SECURITY: sanitize jsonp
|
|
||||||
* NEW: Catch SIGTERM for graceful shutdown
|
|
||||||
* NEW: Show actual applied text formatting for caret position
|
|
||||||
* NEW: Add settings to improve scrolling of viewport on line changes
|
|
||||||
|
|
||||||
# 1.6.2
|
|
||||||
* NEW: Added pad shortcut disabling feature
|
|
||||||
* NEW: Create option to automatically reconnect after a few seconds
|
|
||||||
* Update: socket.io to 1.7.3
|
|
||||||
* Update: l10n lib
|
|
||||||
* Update: request to 2.83.0
|
|
||||||
* Update: Node for windows to 8.9.0
|
|
||||||
* Fix: minification of code
|
|
||||||
|
|
||||||
# 1.6.1
|
|
||||||
* NEW: Hook aceRegisterNonScrollableEditEvents to register events that shouldn't scroll
|
|
||||||
* NEW: Added 'item' parameter to registerAceCommand Hook
|
|
||||||
* NEW: Added LibreJS support
|
|
||||||
* Fix: Crash on malformed export url
|
|
||||||
* Fix: Re-enable editor after user is reconnected to server
|
|
||||||
* Fix: minification
|
|
||||||
* Other: Added 'no-referrer' for all pads
|
|
||||||
* Other: Improved cookie security
|
|
||||||
* Other: Fixed compatibility with nodejs 7
|
|
||||||
* Other: Updates
|
|
||||||
- socket.io to 1.6.0
|
|
||||||
- express to 4.13.4
|
|
||||||
- express-session to 1.13.0
|
|
||||||
- clean-css to 3.4.12
|
|
||||||
- uglify-js to 2.6.2
|
|
||||||
- log4js to 0.6.35
|
|
||||||
- cheerio to 0.20.0
|
|
||||||
- ejs to 2.4.1
|
|
||||||
- graceful-fs to 4.1.3
|
|
||||||
- semver to 5.1.0
|
|
||||||
- unorm to 1.4.1
|
|
||||||
- jsonminify to 0.4.1
|
|
||||||
- measured to 1.1.0
|
|
||||||
- mocha to 2.4.5
|
|
||||||
- supertest to 1.2.0
|
|
||||||
- npm to 4.0.2
|
|
||||||
- Node.js for Windows to 6.9.2
|
|
||||||
|
|
||||||
# 1.6.0
|
|
||||||
* SECURITY: Fix a possible xss attack in iframe link
|
|
||||||
* NEW: Add a aceSelectionChanged hook to allow plugins to react when the cursor location changes.
|
|
||||||
* NEW: Accepting Arrays on 'exportHtmlAdditionalTags' to handle attributes stored as ['key', 'value']
|
|
||||||
* NEW: Allow admin to run on a sub-directory
|
|
||||||
* NEW: Support version 5 of node.js
|
|
||||||
* NEW: Update windows build to node version 4.4.3
|
|
||||||
* NEW: Create setting to control if a new line will be indented or not
|
|
||||||
* NEW: Add an appendText API
|
|
||||||
* NEW: Allow LibreOffice to be used when exporting a pad
|
|
||||||
* NEW: Create hook exportHtmlAdditionalTagsWithData
|
|
||||||
* NEW: Improve DB migration performance
|
|
||||||
* NEW: allow settings to be applied from the filesystem
|
|
||||||
* NEW: remove applySettings hook and allow credentials.json to be part of core
|
|
||||||
* NEW: Use exec to switch to node process
|
|
||||||
* NEW: Validate incoming color codes
|
|
||||||
* Fix: Avoid space removal when pasting text from word processor.
|
|
||||||
* Fix: Removing style that makes editor scroll to the top on iOS without any action from the user
|
|
||||||
* Fix: Fix API call appendChatMessage to send new message to all connected clients
|
|
||||||
* Fix: Timeslider "Return to pad" button
|
|
||||||
* Fix: Generating pad HTML with tags like <span data-TAG="VALUE"> instead of <TAG:VALUE>
|
|
||||||
* Fix: Get git commit hash even if the repo only points to a bare repo.
|
|
||||||
* Fix: Fix decode error if pad name contains special characters and is sanitized
|
|
||||||
* Fix: Fix handleClientMessage_USER_* payloads not containing user info
|
|
||||||
* Fix: Set language cookie on initial load
|
|
||||||
* Fix: Timeslider Not Translated
|
|
||||||
* Other: set charset for mysql connection in settings.json
|
|
||||||
* Other: Dropped support for io.js
|
|
||||||
* Other: Add support to store credentials in credentials.json
|
|
||||||
* Other: Support node version 4 or higher
|
|
||||||
* Other: Update uberDB to version 0.3.0
|
|
||||||
|
|
||||||
# 1.5.7
|
|
||||||
* NEW: Add support for intermediate CA certificates for ssl
|
|
||||||
* NEW: Provide a script to clean up before running etherpad
|
|
||||||
* NEW: Use ctrl+shift+1 to do a ordered list
|
|
||||||
* NEW: Show versions of plugins on startup
|
|
||||||
* NEW: Add author on padCreate and padUpdate hook
|
|
||||||
* Fix: switchToPad method
|
|
||||||
* Fix: Dead keys
|
|
||||||
* Fix: Preserve new lines in copy-pasted text
|
|
||||||
* Fix: Compatibility mode on IE
|
|
||||||
* Fix: Content Collector to get the class of the DOM-node
|
|
||||||
* Fix: Timeslider export links
|
|
||||||
* Fix: Double prompt on file upload
|
|
||||||
* Fix: setText() replaces the entire pad text
|
|
||||||
* Fix: Accessibility features on embedded pads
|
|
||||||
* Fix: Tidy HTML before abiword conversion
|
|
||||||
* Fix: Remove edit buttons in read-only view
|
|
||||||
* Fix: Disable user input in read-only view
|
|
||||||
* Fix: Pads end with a single newline, rather than two newlines
|
|
||||||
* Fix: Toolbar and chat for mobile devices
|
|
||||||
|
|
||||||
# 1.5.6
|
|
||||||
* Fix: Error on windows installations
|
|
||||||
|
|
||||||
# 1.5.5
|
|
||||||
* SECURITY: Also don't allow read files on directory traversal on minify paths
|
|
||||||
* NEW: padOptions can be set in settings.json now
|
|
||||||
* Fix: Add check for special characters in createPad API function
|
|
||||||
* Fix: Middle click on a link in firefox don't paste text anymore
|
|
||||||
* Fix: Made setPadRaw async to import larger etherpad files
|
|
||||||
* Fix: rtl
|
|
||||||
* Fix: Problem in older IEs
|
|
||||||
* Other: Update to express 4.x
|
|
||||||
* Other: Dropped support for node 0.8
|
|
||||||
* Other: Update ejs to version 2.x
|
|
||||||
* Other: Moved sessionKey from settings.json to a new auto-generated SESSIONKEY.txt file
|
|
||||||
|
|
||||||
# 1.5.4
|
|
||||||
* SECURITY: Also don't allow read files on directory traversal on frontend tests path
|
|
||||||
|
|
||||||
# 1.5.3
|
|
||||||
* NEW: Accessibility support for Screen readers, includes new fonts and keyboard shortcuts
|
|
||||||
* NEW: API endpoint for Append Chat Message and Chat Backend Tests
|
|
||||||
* NEW: Error messages displayed on load are included in Default Pad Text (can be supressed)
|
|
||||||
* NEW: Content Collector can handle key values
|
|
||||||
* NEW: getAttributesOnPosition Method
|
|
||||||
* FIX: Firefox keeps attributes (bold etc) on cut/copy -> paste
|
|
||||||
* Fix: showControls=false now works
|
|
||||||
* Fix: Cut and Paste works...
|
|
||||||
* SECURITY: Don't allow read files on directory traversal
|
|
||||||
|
|
||||||
# 1.5.2
|
|
||||||
* NEW: Support for node version 0.12.x
|
|
||||||
* NEW: API endpoint saveRevision, getSavedRevisionCount and listSavedRevisions
|
|
||||||
* NEW: setting to allow load testing
|
|
||||||
* Fix: Rare scroll issue
|
|
||||||
* Fix: Handling of custom pad path
|
|
||||||
* Fix: Better error handling of imports and exports of type "etherpad"
|
|
||||||
* Fix: Walking caret in chrome
|
|
||||||
* Fix: Better handling for changeset problems
|
|
||||||
* SECURITY Fix: Information leak for etherpad exports (CVE-2015-2298)
|
|
||||||
|
|
||||||
# 1.5.1
|
|
||||||
* NEW: High resolution Icon
|
|
||||||
* NEW: Use HTTPS for plugins.json download
|
|
||||||
* NEW: Add 'last update' column
|
|
||||||
* NEW: Show users and chat at the same time
|
|
||||||
* NEW: Support io.js
|
|
||||||
* Fix: removeAttributeOnLine now works properly
|
|
||||||
* Fix: Plugin search and list
|
|
||||||
* Fix: Issue where unauthed request could cause error
|
|
||||||
* Fix: Privacy issue with .etherpad export
|
|
||||||
* Fix: Freeze deps to improve bisectability
|
|
||||||
* Fix: IE, everything. IE is so broken.
|
|
||||||
* Fix: Timeslider proxy
|
|
||||||
* Fix: All backend tests pass
|
|
||||||
* Fix: Better support for Export into HTML
|
|
||||||
* Fix: Timeslider stars
|
|
||||||
* Fix: Translation update
|
|
||||||
* Fix: Check filesystem if Abiword exists
|
|
||||||
* Fix: Docs formatting
|
|
||||||
* Fix: Move Save Revision notification to a gritter message
|
|
||||||
* Fix: UeberDB MySQL Timeout issue
|
|
||||||
* Fix: Indented +9 list items
|
|
||||||
* Fix: Don't paste on middle click of link
|
|
||||||
* SECURITY Fix: Issue where a malformed URL could cause EP to disclose installation location
|
|
||||||
|
|
||||||
# 1.5.0
|
|
||||||
* NEW: Lots of performance improvements for page load times
|
|
||||||
* NEW: Hook for adding CSS to Exports
|
|
||||||
* NEW: Allow shardable socket io
|
|
||||||
* NEW: Allow UI to show when attr/prop is applied (CSS)
|
|
||||||
* NEW: Various scripts
|
|
||||||
* NEW: Export full fidelity pads (including authors etc.)
|
|
||||||
* NEW: Various front end tests
|
|
||||||
* NEW: Backend tests
|
|
||||||
* NEW: switchPad hook to instantly switch between pads
|
|
||||||
* NEW: Various translations
|
|
||||||
* NEW: Icon sets instead of images to provide quality high DPI experience
|
|
||||||
* Fix: HTML Import blocking / hanging server
|
|
||||||
* Fix: Export Bullet / Numbered lists HTML
|
|
||||||
* Fix: Swagger deprecated warning
|
|
||||||
* Fix: Bad session from crashing server
|
|
||||||
* Fix: Allow relative settings path
|
|
||||||
* Fix: Stop attributes being improperly assigned between 2 lines
|
|
||||||
* Fix: Copy / Move Pad API race condition
|
|
||||||
* Fix: Save all user preferences
|
|
||||||
* Fix: Upgrade majority of dependency inc upgrade to SocketIO1+
|
|
||||||
* Fix: Provide UI button to restore maximized chat window
|
|
||||||
* Fix: Timeslider UI Fix
|
|
||||||
* Fix: Remove Dokuwiki
|
|
||||||
* Fix: Remove long paths from windows build (stops error during extract)
|
|
||||||
* Fix: Various globals remvoed
|
|
||||||
* Fix: Move all scripts into bin/
|
|
||||||
* Fix: Various CSS bugfixes for Mobile devices
|
|
||||||
* Fix: Overflow Toolbar
|
|
||||||
* Fix: Line Attribute management
|
|
||||||
|
|
||||||
# 1.4.1
|
|
||||||
* NEW: Translations
|
|
||||||
* NEW: userLeave Hook
|
|
||||||
* NEW: Script to reinsert all DB values of a Pad
|
|
||||||
* NEW: Allow for absolute settings paths
|
|
||||||
* NEW: API: Get Pad ID from read Only Pad ID
|
|
||||||
* NEW: Huge improvement on MySQL database read/write (InnoDB to MyISAM)
|
|
||||||
* NEW: Hook for Export File Name
|
|
||||||
* NEW: Preprocessor Hook for DOMLine attributes (allows plugins to wrap entire line contents)
|
|
||||||
* Fix: Exception on Plugin Search and fix for plugins not being fetched
|
|
||||||
* Fix: Font on innerdoc body can be arial on paste
|
|
||||||
* Fix: Fix Dropping of messages in handleMessage
|
|
||||||
* Fix: Don't use Abiword for HTML exports
|
|
||||||
* Fix: Color issues with user Icon
|
|
||||||
* Fix: Timeslider Button
|
|
||||||
* Fix: Session Deletion error
|
|
||||||
* Fix: Allow browser tabs to be cycled when focus is in editor
|
|
||||||
* Fix: Various Editor issues with Easysync potentially entering forever loop on bad changeset
|
|
||||||
|
|
||||||
# 1.4
|
|
||||||
* NEW: Disable toolbar items through settings.json
|
|
||||||
* NEW: Internal stats/metrics engine
|
|
||||||
* NEW: Copy/Move Pad API functions
|
|
||||||
* NEW: getAttributeOnSelection method
|
|
||||||
* NEW: CSS function when an attribute is active on caret location
|
|
||||||
* NEW: Various new eejs blocks
|
|
||||||
* NEW: Ace afterEditHook
|
|
||||||
* NEW: Import hook to introduce alternative export methods
|
|
||||||
* NEW: preProcessDomLine allows Domline attributes to be processed before native attributes
|
|
||||||
* Fix: Allow for lighter author colors
|
|
||||||
* Fix: Improved randomness of session tokens
|
|
||||||
* Fix: Don't panic if an author2session/group2session no longer exists
|
|
||||||
* Fix: Gracefully fallback to related languages if chosen language is unavailable
|
|
||||||
* Fix: Various changeset/stability bugs
|
|
||||||
* Fix: Re-enable import buttons after failed import
|
|
||||||
* Fix: Allow browser tabs to be cycled when in editor
|
|
||||||
* Fix: Better Protocol detection
|
|
||||||
* Fix: padList API Fix
|
|
||||||
* Fix: Caret walking issue
|
|
||||||
* Fix: Better settings.json parsing
|
|
||||||
* Fix: Improved import/export handling
|
|
||||||
* Other: Various whitespace/code clean-up
|
|
||||||
* Other: .deb packaging creator
|
|
||||||
* Other: More API Documentation
|
|
||||||
* Other: Lots more translations
|
|
||||||
* Other: Support Node 0.11
|
|
||||||
|
|
||||||
# 1.3
|
|
||||||
* NEW: We now follow the semantic versioning scheme!
|
|
||||||
* NEW: Option to disable IP logging
|
|
||||||
* NEW: Localisation updates from http://translatewiki.net.
|
|
||||||
* Fix: Fix readOnly group pads
|
|
||||||
* Fix: don't fetch padList on every request
|
|
||||||
|
|
||||||
# 1.2.12
|
|
||||||
* NEW: Add explanations for more disconnect scenarios
|
|
||||||
* NEW: export sessioninfos so plugins can access it
|
|
||||||
* NEW: pass pad in postAceInit hook
|
|
||||||
* NEW: Add trustProxy setting. ALlows to make ep use X-forwarded-for as remoteAddress
|
|
||||||
* NEW: userLeave hook (UNDOCUMENTED)
|
|
||||||
* NEW: Plural macro for translations
|
|
||||||
* NEW: backlinks to main page in Admin pages
|
|
||||||
* NEW: New translations from translatewiki.net
|
|
||||||
* SECURITY FIX: Filter author data sent to clients
|
|
||||||
* FIX: Never keep processing a changeset if it's corrupted
|
|
||||||
* FIX: Some client-side performance fixes for webkit browsers
|
|
||||||
* FIX: Only execute listAllPads query on demand (not on start-up)
|
|
||||||
* FIX: HTML import (don't crash on malformed or blank HTML input; strip title out of html during import)
|
|
||||||
* FIX: check if uploaded file only contains ascii chars when abiword disabled
|
|
||||||
* FIX: Plugin search in /admin/plugins
|
|
||||||
* FIX: Don't create new pad if a non-existant read-only pad is accessed
|
|
||||||
* FIX: Drop messages from unknown connections (would lead to a crash after a restart)
|
|
||||||
* FIX: API: fix createGroupFor endpoint, if mapped group is deleted
|
|
||||||
* FIX: Import form for other locales
|
|
||||||
* FIX: Don't stop processing changeset queue if there is an error
|
|
||||||
* FIX: Caret movement. Chrome detects blank rows line heights as incorrect
|
|
||||||
* FIX: allow colons in password
|
|
||||||
* FIX: Polish logging of client-side errors on the server
|
|
||||||
* FIX: Username url param
|
|
||||||
* FIX: Make start script POSIX ompatible
|
|
||||||
|
|
||||||
|
|
||||||
# 1.2.11
|
|
||||||
* NEW: New Hook for outer_ace dynamic css manager and author style hook
|
|
||||||
* NEW: Bump log4js for improved logging
|
|
||||||
* Fix: Remove URL schemes which don't have RFC standard
|
|
||||||
* Fix: Fix safeRun subsequent restarts issue
|
|
||||||
* Fix: Allow safeRun to pass arguements to run.sh
|
|
||||||
* Fix: Include script for more efficient import
|
|
||||||
* Fix: Fix sysv comptibile script
|
|
||||||
* Fix: Fix client side changeset spamming
|
|
||||||
* Fix: Don't crash on no-auth
|
|
||||||
* Fix: Fix some IE8 errors
|
|
||||||
* Fix: Fix authorship sanitation
|
|
||||||
|
|
||||||
# 1.2.10
|
|
||||||
* NEW: Broadcast slider is exposed in timeslider so plugins can interact with it
|
|
||||||
* Fix: IE issue where pads wouldn't load due to missing console from i18n
|
|
||||||
* Fix: console issue in collab client would error on cross domain embeds in IE
|
|
||||||
* Fix: Only Restart Etherpad once plugin is installed
|
|
||||||
* Fix: Only redraw lines that exist after drag and drop
|
|
||||||
* Fix: Pasting into ordered list
|
|
||||||
* Fix: Import browser detection
|
|
||||||
* Fix: 2 Part Locale Specs
|
|
||||||
* Fix: Remove language string from chat element
|
|
||||||
* Fix: Make Saved revision Star fade back out on non Top frames
|
|
||||||
* Other: Remove some cruft legacy JS from old Etherpad
|
|
||||||
* Other: Express 3.1.2 breaks sessions, set Express to 3.1.0
|
|
||||||
|
|
||||||
# 1.2.91
|
|
||||||
* NEW: Authors can now send custom object messages to other Authors making 3 way conversations possible. This introduces WebRTC plugin support.
|
|
||||||
* NEW: Hook for Chat Messages Allows for Desktop Notification support
|
|
||||||
* NEW: FreeBSD installation docs
|
|
||||||
* NEW: Ctrl S for save revision makes the Icon glow for a few sconds.
|
|
||||||
* NEW: Various hooks and expose the document ACE object
|
|
||||||
* NEW: Plugin page revamp makes finding and installing plugins more sane.
|
|
||||||
* NEW: Icon to enable sticky chat from the Chat box
|
|
||||||
* Fix: Cookies inside of plugins
|
|
||||||
* Fix: Don't leak event emitters when accessing admin/plugins
|
|
||||||
* Fix: Don't allow user to send messages after they have been "kicked" from a pad
|
|
||||||
* Fix: Refactor Caret navigation with Arrow and Pageup/down keys stops cursor being lost
|
|
||||||
* Fix: Long lines in Firefox now wrap properly
|
|
||||||
* Fix: Session Disconnect limit is increased from 10 to 20 to support slower restarts
|
|
||||||
* Fix: Support Node 0.10
|
|
||||||
* Fix: Log HTTP on DEBUG log level
|
|
||||||
* Fix: Server wont crash on import fails on 0 file import.
|
|
||||||
* Fix: Import no longer fails consistantly
|
|
||||||
* Fix: Language support for non existing languages
|
|
||||||
* Fix: Mobile support for chat notifications are now usable
|
|
||||||
* Fix: Re-Enable Editbar buttons on reconnect
|
|
||||||
* Fix: Clearing authorship colors no longer disconnects all clients
|
|
||||||
* Other: New debug information for sessions
|
|
||||||
|
|
||||||
# 1.2.9
|
|
||||||
* Fix: MAJOR Security issue, where a hacker could submit content as another user
|
|
||||||
* Fix: security issue due to unescaped user input
|
|
||||||
* Fix: Admin page at /admin redirects to /admin/ now to prevent breaking relative links
|
|
||||||
* Fix: indentation in chrome on linux
|
|
||||||
* Fix: PadUsers API endpoint
|
|
||||||
* NEW: A script to import data to all dbms
|
|
||||||
* NEW: Add authorId to chat and userlist as a data attribute
|
|
||||||
* NEW: Refactor and fix our frontend tests
|
|
||||||
* NEW: Localisation updates
|
|
||||||
|
|
||||||
|
|
||||||
# 1.2.81
|
|
||||||
* Fix: CtrlZ-Y for Undo Redo
|
|
||||||
* Fix: RTL functionality on contents & fix RTL/LTR tests and RTL in Safari
|
|
||||||
* Fix: Various other tests fixed in Android
|
|
||||||
|
|
||||||
# 1.2.8
|
|
||||||
! IMPORTANT: New setting.json value is required to automatically reconnect clients on disconnect
|
|
||||||
* NEW: Use Socket IO for rooms (allows for pads to be load balanced with sticky rooms)
|
|
||||||
* NEW: Plugins can now provide their own frontend tests
|
|
||||||
* NEW: Improved server-side logging
|
|
||||||
* NEW: Admin dashboard mobile device support and new hooks for Admin dashboard
|
|
||||||
* NEW: Get current API version from API
|
|
||||||
* NEW: CLI script to delete pads
|
|
||||||
* Fix: Automatic client reconnection on disonnect
|
|
||||||
* Fix: Text Export indentation now supports multiple indentations
|
|
||||||
* Fix: Bugfix getChatHistory API method
|
|
||||||
* Fix: Stop Chrome losing caret after paste is texted
|
|
||||||
* Fix: Make colons on end of line create 4 spaces on indent
|
|
||||||
* Fix: Stop the client disconnecting if a rev is in the wrong order
|
|
||||||
* Fix: Various server crash issues based on rev in wrong order
|
|
||||||
* Fix: Various tests
|
|
||||||
* Fix: Make indent when on middle of the line stop creating list
|
|
||||||
* Fix: Stop long strings breaking the UX by moving focus away from beginning of line
|
|
||||||
* Fix: Redis findKeys support
|
|
||||||
* Fix: padUsersCount no longer hangs server
|
|
||||||
* Fix: Issue with two part locale specs not working
|
|
||||||
* Fix: Make plugin search case insensitive
|
|
||||||
* Fix: Indentation and bullets on text export
|
|
||||||
* Fix: Resolve various warnings on dependencies during install
|
|
||||||
* Fix: Page up / Page down now works in all browsers
|
|
||||||
* Fix: Stop Opera browser inserting two new lines on enter keypress
|
|
||||||
* Fix: Stop timeslider from showing NaN on pads with only one revision
|
|
||||||
* Other: Allow timeslider tests to run and provide & fix various other frontend-tests
|
|
||||||
* Other: Begin dropping referene to Lite. Etherpad Lite is now named "Etherpad"
|
|
||||||
* Other: Update to latest jQuery
|
|
||||||
* Other: Change loading message asking user to please wait on first build
|
|
||||||
* Other: Allow etherpad to use global npm installation (Safe since node 6.3)
|
|
||||||
* Other: Better documentation for log rotation and log message handling
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 1.2.7
|
|
||||||
* NEW: notifications are now modularized and can be stacked
|
|
||||||
* NEW: Visit a specific revision in the timeslider by suffixing #%revNumber% IE http://localhost/p/test/timeslider#12
|
|
||||||
* NEW: Link to plugin on Admin page allows admins to easily see plugin details in a new window by clicking on the plugin name
|
|
||||||
* NEW: Automatically see plugins that require update and be able to one click update
|
|
||||||
* NEW: API endpoints for Chat .. getChatHistory, getChatHead
|
|
||||||
* NEW: API endpoint to see a pad diff in HTML format from revision x to revision y .. createPadDiffHTML
|
|
||||||
* NEW: Real time plugin search & unified menu UI for admin pages
|
|
||||||
* Fix: MAJOR issue where server could be crashed by malformed client message
|
|
||||||
* Fix: AuthorID is now included in padUsers API response
|
|
||||||
* Fix: make docs
|
|
||||||
* Fix: Timeslider UI bug with slider not being in position
|
|
||||||
* Fix: IE8 language issue where it wouldn't load pads due to IE8 suckling on the bussum of hatrid
|
|
||||||
* Fix: Import timeout issue
|
|
||||||
* Fix: Import now works if Params are set in pad URL
|
|
||||||
* Fix: Convert script
|
|
||||||
* Other: Various new language strings and update/bugfixes of others
|
|
||||||
* Other: Clean up the getParams functionality
|
|
||||||
* Other: Various new EEJS blocks: index, timeslider, html etc.
|
|
||||||
|
|
||||||
# 1.2.6
|
|
||||||
* Fix: Package file UeberDB reference
|
|
||||||
* New #users EEJS block for plugins
|
|
||||||
|
|
||||||
# 1.2.5
|
|
||||||
* Create timeslider EEJS blocks for plugins
|
|
||||||
* Allow for "more messages" to be loaded in chat
|
|
||||||
* Introduce better logging
|
|
||||||
* API endpoint for "listAllPads"
|
|
||||||
* Fix: Stop highlight of timeslider when dragging mouse
|
|
||||||
* Fix: Time Delta on Timeslider make date update properly
|
|
||||||
* Fix: Prevent empty chat messages from being sent
|
|
||||||
* Fix: checkPad script
|
|
||||||
* Fix: IE onLoad listener for i18n
|
|
||||||
|
|
||||||
# 1.2.4
|
|
||||||
* Fix IE console issue created in 1.2.3
|
|
||||||
* Allow CI Tests to pass by ignoring timeslider test
|
|
||||||
* Fix broken placeholders in locales
|
|
||||||
* Fix extractPadData script
|
|
||||||
* Fix documentation for checkToken
|
|
||||||
* Fix hitting enter on form in admin/plugins
|
|
||||||
|
|
||||||
# 1.2.3
|
|
||||||
* Fix #1307: Chrome needs console.log to be called on console obj
|
|
||||||
* Fix #1309: We had broken support for node v0.6 in the last release
|
|
||||||
|
|
||||||
# 1.2.2
|
|
||||||
* More translations and better language support. See https://translatewiki.net/wiki/Translating:Etherpad_lite for more details
|
|
||||||
* Add a checkToken Method to the API
|
|
||||||
* Bugfix for Internal Caching issue that was causing some 404s on images.
|
|
||||||
* Bugfix for IE Import
|
|
||||||
* Bugfix for Node 0.6 compatibility
|
|
||||||
* Bugfix for multiple cookie support
|
|
||||||
* Bugfix for API when requireAuth is enabled.
|
|
||||||
* Plugin page now shows plugin version #
|
|
||||||
* Show color of Author in Chat messages
|
|
||||||
* Allow plugin search by description
|
|
||||||
* Allow for different socket IO transports
|
|
||||||
* Allow for custom favicon path
|
|
||||||
* Control S now does Create new Revision functionality
|
|
||||||
* Focus on password when required
|
|
||||||
* Frontend Timeslider test
|
|
||||||
* Allow for basic HTML etc. import without abiword
|
|
||||||
* Native HTTPS support
|
|
||||||
|
|
||||||
# 1.2.1
|
|
||||||
* Allow ! in urls inside the editor (Not Pad urls)
|
|
||||||
* Allow comments in language files
|
|
||||||
* More languages (Finish, Spanish, Bengali, Dutch) Thanks to TranslateWiki.net team. See https://translatewiki.net/w/i.php?title=Special:MessageGroupStats&group=out-etherpad-lite for more details
|
|
||||||
* Bugfix for IE7/8 issue with a JS error #1186
|
|
||||||
* Bugfix windows package extraction issue and make the .zip file smaller
|
|
||||||
* Bugfix group pad API export
|
|
||||||
* Kristen Stewart is a terrible actress and Twilight sucks.
|
|
||||||
|
|
||||||
# v1.2
|
|
||||||
* Internationalization / Language / Translation support (i18n) with support for German/French
|
|
||||||
* A frontend/client side testing framework and backend build tests
|
|
||||||
* Customizable robots.txt
|
|
||||||
* Customizable app title (finally you can name your epl instance!)
|
|
||||||
* eejs render arguments are now passed on to eejs hooks through the newly introduced `renderContext` argument.
|
|
||||||
* Plugin-specific settings in settings.json (finally allowing for things like a google analytics plugin)
|
|
||||||
* Serve admin dashboard at /admin (still very limited, though)
|
|
||||||
* Modify your settings.json through the newly created UI at /admin/settings
|
|
||||||
* Fix: Import <ol>'s as <ol>'s and not as <ul>'s!
|
|
||||||
* Added solaris compatibility (bin/installDeps.sh was broken on solaris)
|
|
||||||
* Fix a bug with IE9 and Password Protected Pads using HTTPS
|
|
||||||
|
|
||||||
# v1.1.5
|
|
||||||
* We updated to express v3 (please [make sure](https://github.com/visionmedia/express/wiki/Migrating-from-2.x-to-3.x) your plugin works under express v3)
|
|
||||||
* `userColor` URL parameter which sets the initial author color
|
|
||||||
* Hooks for "padCreate", "padRemove", "padUpdate" and "padLoad" events
|
|
||||||
* Security patches concerning the handling of messages originating from clients
|
|
||||||
* Our database abstraction layer now natively supports couchDB, levelDB, mongoDB, postgres, and redis!
|
|
||||||
* We now provide a script helping you to migrate from dirtyDB to MySQL
|
|
||||||
* Support running Etherpad Lite behind IIS, using [iisnode](https://github.com/tjanczuk/iisnode/wiki)
|
|
||||||
* LibreJS Licensing information in headers of HTML templates
|
|
||||||
* Default port number to PORT env var, if port isn't specified in settings
|
|
||||||
* Fix for `convert.js`
|
|
||||||
* Raise upper char limit in chat to 999 characters
|
|
||||||
* Fixes for mobile layout
|
|
||||||
* Fixes for usage behind reverse proxy
|
|
||||||
* Improved documentation
|
|
||||||
* Fixed some opera style bugs
|
|
||||||
* Update npm and fix some bugs, this introduces
|
|
||||||
|
|
||||||
# v1.1
|
|
||||||
* Introduced Plugin framework
|
|
||||||
* Many bugfixes
|
|
||||||
* Faster page loading
|
|
||||||
* Various UI polishes
|
|
||||||
* Saved Revisions
|
|
||||||
* Read only Real time view
|
|
||||||
* More API functionality
|
|
||||||
|
|
||||||
# v 1.0.1
|
|
||||||
|
|
||||||
* Updated MySQL driver, this fixes some problems with mysql
|
|
||||||
* Fixed export,import and timeslider link when embed parameters are used
|
|
|
@ -1,110 +0,0 @@
|
||||||
# Contributor Guidelines
|
|
||||||
(Please talk to people on the mailing list before you change this page, see our section on [how to get in touch](https://github.com/ether/etherpad-lite#get-in-touch))
|
|
||||||
|
|
||||||
## How to write a bug report
|
|
||||||
|
|
||||||
* Please be polite, we all are humans and problems can occur.
|
|
||||||
* Please add as much information as possible, for example
|
|
||||||
* client os(s) and version(s)
|
|
||||||
* browser(s) and version(s), is the problem reproducible on different clients
|
|
||||||
* special environments like firewalls or antivirus
|
|
||||||
* host os and version
|
|
||||||
* npm and nodejs version
|
|
||||||
* Logfiles if available
|
|
||||||
* steps to reproduce
|
|
||||||
* what you expected to happen
|
|
||||||
* what actually happened
|
|
||||||
* Please format logfiles and code examples with markdown see github Markdown help below the issue textarea for more information.
|
|
||||||
|
|
||||||
If you send logfiles, please set the loglevel switch DEBUG in your settings.json file:
|
|
||||||
|
|
||||||
```
|
|
||||||
/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
|
|
||||||
"loglevel": "DEBUG",
|
|
||||||
```
|
|
||||||
|
|
||||||
The logfile location is defined in startup script or the log is directly shown in the commandline after you have started etherpad.
|
|
||||||
|
|
||||||
|
|
||||||
## Important note for pull requests
|
|
||||||
**Pull requests should be issued against the develop branch**. We never pull directly into master.
|
|
||||||
|
|
||||||
**Our goal is to iterate in small steps. Release often, release early. Evolution instead of a revolution**
|
|
||||||
|
|
||||||
## General goals of Etherpad
|
|
||||||
To make sure everybody is going in the same direction:
|
|
||||||
* easy to install for admins and easy to use for people
|
|
||||||
* easy to integrate into other apps, but also usable as standalone
|
|
||||||
* lightweight and scalable
|
|
||||||
* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core.
|
|
||||||
Also, keep it maintainable. We don't wanna end up as the monster Etherpad was!
|
|
||||||
|
|
||||||
## How to work with git?
|
|
||||||
* Don't work in your master branch.
|
|
||||||
* Make a new branch for every feature you're working on. (This ensures that you can work you can do lots of small, independent pull requests instead of one big one with complete different features)
|
|
||||||
* Don't use the online edit function of github (this only creates ugly and not working commits!)
|
|
||||||
* Try to make clean commits that are easy readable (including descriptive commit messages!)
|
|
||||||
* Test before you push. Sounds easy, it isn't!
|
|
||||||
* Don't check in stuff that gets generated during build or runtime
|
|
||||||
* Make small pull requests that are easy to review but make sure they do add value by themselves / individually
|
|
||||||
|
|
||||||
## Coding style
|
|
||||||
* Do write comments. (You don't have to comment every line, but if you come up with something that's a bit complex/weird, just leave a comment. Bear in mind that you will probably leave the project at some point and that other people will read your code. Undocumented huge amounts of code are worthless!)
|
|
||||||
* Never ever use tabs
|
|
||||||
* Indentation: JS/CSS: 2 spaces; HTML: 4 spaces
|
|
||||||
* Don't overengineer. Don't try to solve any possible problem in one step, but try to solve problems as easy as possible and improve the solution over time!
|
|
||||||
* Do generalize sooner or later! (if an old solution, quickly hacked together, poses more problems than it solves today, refactor it!)
|
|
||||||
* Keep it compatible. Do not introduce changes to the public API, db schema or configurations too lightly. Don't make incompatible changes without good reasons!
|
|
||||||
* If you do make changes, document them! (see below)
|
|
||||||
* Use protocol independent urls "//"
|
|
||||||
|
|
||||||
## Branching model / git workflow
|
|
||||||
see git flow http://nvie.com/posts/a-successful-git-branching-model/
|
|
||||||
|
|
||||||
### `master` branch
|
|
||||||
* the stable
|
|
||||||
* This is the branch everyone should use for production stuff
|
|
||||||
|
|
||||||
### `develop`branch
|
|
||||||
* everything that is READY to go into master at some point in time
|
|
||||||
* This stuff is tested and ready to go out
|
|
||||||
|
|
||||||
### release branches
|
|
||||||
* stuff that should go into master very soon
|
|
||||||
* only bugfixes go into these (see http://nvie.com/posts/a-successful-git-branching-model/ for why)
|
|
||||||
* we should not be blocking new features to develop, just because we feel that we should be releasing it to master soon. This is the situation that release branches solve/handle.
|
|
||||||
|
|
||||||
### hotfix branches
|
|
||||||
* fixes for bugs in master
|
|
||||||
|
|
||||||
### feature branches (in your own repos)
|
|
||||||
* these are the branches where you develop your features in
|
|
||||||
* If it's ready to go out, it will be merged into develop
|
|
||||||
|
|
||||||
Over the time we pull features from feature branches into the develop branch. Every month we pull from develop into master. Bugs in master get fixed in hotfix branches. These branches will get merged into master AND develop. There should never be commits in master that aren't in develop
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
The docs are in the `doc/` folder in the git repository, so people can easily find the suitable docs for the current git revision.
|
|
||||||
|
|
||||||
Documentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request.
|
|
||||||
|
|
||||||
You can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
Front-end tests are found in the `tests/frontend/` folder in the repository. Run them by pointing your browser to `<yourdomainhere>/tests/frontend`.
|
|
||||||
|
|
||||||
## Things you can help with
|
|
||||||
Etherpad is much more than software. So if you aren't a developer then worry not, there is still a LOT you can do! A big part of what we do is community engagement. You can help in the following ways
|
|
||||||
* Triage bugs (applying labels) and confirming their existance
|
|
||||||
* Testing fixes (simply applying them and seeing if it fixes your issue or not) - Some git experience required
|
|
||||||
* Notifying large site admins of new releases
|
|
||||||
* Writing Changelogs for releases
|
|
||||||
* Creating Windows packages
|
|
||||||
* Creating releases
|
|
||||||
* Bumping dependencies periodically and checking they don't break anything
|
|
||||||
* Write proposals for grants
|
|
||||||
* Co-Author and Publish CVEs
|
|
||||||
* Work with SFC to maintain legal side of project
|
|
||||||
* Maintain TODO page - https://github.com/ether/etherpad-lite/wiki/TODO#IMPORTANT_TODOS
|
|
||||||
* Replying to messages on IRC / The Mailing list / Emails
|
|
||||||
|
|
201
sources/LICENSE
201
sources/LICENSE
|
@ -1,201 +0,0 @@
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright 2013 THE ETHERPAD FOUNDATION
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
|
@ -1,25 +0,0 @@
|
||||||
doc_sources = $(wildcard doc/*/*.md) $(wildcard doc/*.md)
|
|
||||||
outdoc_files = $(addprefix out/,$(doc_sources:.md=.html))
|
|
||||||
|
|
||||||
docassets = $(addprefix out/,$(wildcard doc/assets/*))
|
|
||||||
|
|
||||||
VERSION = $(shell node -e "console.log( require('./src/package.json').version )")
|
|
||||||
UNAME := $(shell uname -s)
|
|
||||||
|
|
||||||
docs: $(outdoc_files) $(docassets)
|
|
||||||
|
|
||||||
out/doc/assets/%: doc/assets/%
|
|
||||||
mkdir -p $(@D)
|
|
||||||
cp $< $@
|
|
||||||
|
|
||||||
out/doc/%.html: doc/%.md
|
|
||||||
mkdir -p $(@D)
|
|
||||||
node bin/doc/generate.js --format=html --template=doc/template.html $< > $@
|
|
||||||
ifeq ($(UNAME),Darwin)
|
|
||||||
sed -i '' 's/__VERSION__/${VERSION}/' $@
|
|
||||||
else
|
|
||||||
sed -i 's/__VERSION__/${VERSION}/' $@
|
|
||||||
endif
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf out/
|
|
|
@ -1,131 +0,0 @@
|
||||||
### This project is looking for a new project lead. If you wish to help steer Etherpad forward please email contact@etherpad.org
|
|
||||||
|
|
||||||
[](https://david-dm.org/ether/etherpad-lite)
|
|
||||||
[](https://nodesecurity.io/orgs/etherpad/projects/635f6185-35c6-4ed7-931a-0bc62758ece7)
|
|
||||||
|
|
||||||
# A really-real time collaborative word processor for the web
|
|
||||||

|
|
||||||
|
|
||||||
# About
|
|
||||||
Etherpad is a really-real time collaborative editor scalable to thousands of simultanious real time users. Unlike all other collaborative tools Etherpad provides full fidelity data export and portability making it fully GDPR compliant.
|
|
||||||
|
|
||||||
**[Try it out](http://beta.etherpad.org)**
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
## Uber-Quick Ubuntu
|
|
||||||
```
|
|
||||||
curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
|
|
||||||
sudo apt-get install -y nodejs
|
|
||||||
git clone https://github.com/ether/etherpad-lite.git && cd etherpad-lite && bin/run.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## GNU/Linux and other UNIX-like systems
|
|
||||||
You'll need gzip, git, curl, libssl develop libraries, python and gcc.
|
|
||||||
- *For Debian/Ubuntu*: `apt install gzip git curl python libssl-dev pkg-config build-essential`
|
|
||||||
- *For Fedora/CentOS*: `yum install gzip git curl python openssl-devel && yum groupinstall "Development Tools"`
|
|
||||||
- *For FreeBSD*: `portinstall node, npm, curl, git (optional)`
|
|
||||||
|
|
||||||
Additionally, you'll need [node.js](https://nodejs.org) installed, Ideally the latest stable version, we recommend installing/compiling nodejs from source (avoiding apt).
|
|
||||||
|
|
||||||
**As any user (we recommend creating a separate user called etherpad):**
|
|
||||||
|
|
||||||
1. Move to a folder where you want to install Etherpad. Clone the git repository `git clone git://github.com/ether/etherpad-lite.git`
|
|
||||||
2. Change into the new directory containing the cloned source code `cd etherpad-lite`
|
|
||||||
|
|
||||||
Now, run `bin/run.sh` and open <http://127.0.0.1:9001> in your browser.
|
|
||||||
|
|
||||||
Update to the latest version with `git pull origin`. The next start with bin/run.sh will update the dependencies.
|
|
||||||
|
|
||||||
[Next steps](#next-steps).
|
|
||||||
|
|
||||||
## Windows
|
|
||||||
|
|
||||||
### Prebuilt windows package
|
|
||||||
This package works out of the box on any windows machine, but it's not very useful for developing purposes...
|
|
||||||
|
|
||||||
1. [Download the latest windows package](http://etherpad.org/#download)
|
|
||||||
2. Extract the folder
|
|
||||||
|
|
||||||
Now, run `start.bat` and open <http://localhost:9001> in your browser. You like it? [Next steps](#next-steps).
|
|
||||||
|
|
||||||
### Fancy install
|
|
||||||
You'll need [node.js](https://nodejs.org) and (optionally, though recommended) git.
|
|
||||||
|
|
||||||
1. Grab the source, either
|
|
||||||
- download <https://github.com/ether/etherpad-lite/zipball/master>
|
|
||||||
- or `git clone https://github.com/ether/etherpad-lite.git` (for this you need git, obviously)
|
|
||||||
2. start `bin\installOnWindows.bat`
|
|
||||||
|
|
||||||
Now, run `start.bat` and open <http://localhost:9001> in your browser.
|
|
||||||
|
|
||||||
Update to the latest version with `git pull origin`, then run `bin\installOnWindows.bat`, again.
|
|
||||||
|
|
||||||
If cloning to a subdirectory within another project, you may need to do the following:
|
|
||||||
|
|
||||||
1. Start the server manually (e.g. `node/node_modules/ep_etherpad-lite/node/server.js]`)
|
|
||||||
2. Edit the db `filename` in `settings.json` to the relative directory with the file (e.g. `application/lib/etherpad-lite/var/dirty.db`)
|
|
||||||
3. Add auto-generated files to the main project `.gitignore`
|
|
||||||
|
|
||||||
# Next Steps
|
|
||||||
|
|
||||||
## Tweak the settings
|
|
||||||
You can initially modify the settings in `settings.json`. (If you need to handle multiple settings files, you can pass the path to a settings file to `bin/run.sh` using the `-s|--settings` option. This allows you to run multiple Etherpad instances from the same installation.) Once you have access to your /admin section settings can be modified through the web browser.
|
|
||||||
|
|
||||||
You should use a dedicated database such as "mysql", if you are planning on using etherpad-in a production environment, since the "dirtyDB" database driver is only for testing and/or development purposes.
|
|
||||||
|
|
||||||
## Plugins and themes
|
|
||||||
|
|
||||||
Etherpad is very customizable through plugins. Instructions for installing themes and plugins can be found in [the plugin wiki article](https://github.com/ether/etherpad-lite/wiki/Available-Plugins).
|
|
||||||
|
|
||||||
## Helpful resources
|
|
||||||
The [wiki](https://github.com/ether/etherpad-lite/wiki) is your one-stop resource for Tutorials and How-to's.
|
|
||||||
|
|
||||||
Documentation can be found in `doc/`.
|
|
||||||
|
|
||||||
# Development
|
|
||||||
|
|
||||||
## Things you should know
|
|
||||||
Understand [git](https://training.github.com/) and watch this [video on getting started with Etherpad Development](https://youtu.be/67-Q26YH97E).
|
|
||||||
|
|
||||||
If you're new to node.js, start with Ryan Dahl's [Introduction to Node.js](https://youtu.be/jo_B4LTHi3I).
|
|
||||||
|
|
||||||
You can debug Etherpad using `bin/debugRun.sh`.
|
|
||||||
|
|
||||||
If you want to find out how Etherpad's `Easysync` works (the library that makes it really realtime), start with this [PDF](https://github.com/ether/etherpad-lite/raw/master/doc/easysync/easysync-full-description.pdf) (complex, but worth reading).
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
Read our [**Developer Guidelines**](https://github.com/ether/etherpad-lite/blob/master/CONTRIBUTING.md)
|
|
||||||
|
|
||||||
# Get in touch
|
|
||||||
[mailinglist](https://groups.google.com/group/etherpad-lite-dev)
|
|
||||||
[#etherpad-lite-dev freenode IRC](https://webchat.freenode.net?channels=#etherpad-lite-dev)!
|
|
||||||
|
|
||||||
# Languages
|
|
||||||
Etherpad is written in JavaScript on both the server and client so it's easy for developers to maintain and add new features.
|
|
||||||
|
|
||||||
# HTTP API
|
|
||||||
Etherpad is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API)
|
|
||||||
that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API.
|
|
||||||
|
|
||||||
# jQuery plugin
|
|
||||||
There is a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website.
|
|
||||||
|
|
||||||
# Plugin Framework
|
|
||||||
Etherpad offers a plugin framework, allowing you to easily add your own features. By default your Etherpad is extremely light-weight and it's up to you to customize your experience. Once you have Etherpad installed you should visit the plugin page and take control.
|
|
||||||
|
|
||||||
# Translations / Localizations (i18n / l10n)
|
|
||||||
Etherpad comes with translations into all languages thanks to the team at TranslateWiki.
|
|
||||||
|
|
||||||
# FAQ
|
|
||||||
Visit the **[FAQ](https://github.com/ether/etherpad-lite/wiki/FAQ)**.
|
|
||||||
|
|
||||||
# Donate!
|
|
||||||
* [Flattr](https://flattr.com/thing/71378/Etherpad-Foundation)
|
|
||||||
* Paypal - Press the donate button on [etherpad.org](http://etherpad.org)
|
|
||||||
* [Bitcoin](https://coinbase.com/checkouts/1e572bf8a82e4663499f7f1f66c2d15a)
|
|
||||||
|
|
||||||
All donations go to the Etherpad foundation which is part of Software Freedom Conservency
|
|
||||||
|
|
||||||
# License
|
|
||||||
[Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html)
|
|
|
@ -1 +0,0 @@
|
||||||
src/node_modules/mocha/bin/mocha --timeout 5000 --reporter nyan tests/backend/specs/api
|
|
|
@ -1,44 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# IMPORTANT
|
|
||||||
# Protect against misspelling a var and rm -rf /
|
|
||||||
set -u
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SRC=/tmp/etherpad-deb-src
|
|
||||||
DIST=/tmp/etherpad-deb-dist
|
|
||||||
SYSROOT=${SRC}/sysroot
|
|
||||||
DEBIAN=${SRC}/DEBIAN
|
|
||||||
|
|
||||||
rm -rf ${DIST}
|
|
||||||
mkdir -p ${DIST}/
|
|
||||||
|
|
||||||
rm -rf ${SRC}
|
|
||||||
rsync -a bin/deb-src/ ${SRC}/
|
|
||||||
mkdir -p ${SYSROOT}/opt/
|
|
||||||
|
|
||||||
rsync --exclude '.git' -a . ${SYSROOT}/opt/etherpad/ --delete
|
|
||||||
mkdir -p ${SYSROOT}/usr/share/doc
|
|
||||||
cp README.md ${SYSROOT}/usr/share/doc/etherpad
|
|
||||||
find ${SRC}/ -type d -exec chmod 0755 {} \;
|
|
||||||
find ${SRC}/ -type f -exec chmod go-w {} \;
|
|
||||||
chown -R root:root ${SRC}/
|
|
||||||
|
|
||||||
let SIZE=`du -s ${SYSROOT} | sed s'/\s\+.*//'`+8
|
|
||||||
pushd ${SYSROOT}/
|
|
||||||
tar czf ${DIST}/data.tar.gz [a-z]*
|
|
||||||
popd
|
|
||||||
sed s"/SIZE/${SIZE}/" -i ${DEBIAN}/control
|
|
||||||
pushd ${DEBIAN}
|
|
||||||
tar czf ${DIST}/control.tar.gz *
|
|
||||||
popd
|
|
||||||
|
|
||||||
pushd ${DIST}/
|
|
||||||
echo 2.0 > ./debian-binary
|
|
||||||
|
|
||||||
find ${DIST}/ -type d -exec chmod 0755 {} \;
|
|
||||||
find ${DIST}/ -type f -exec chmod go-w {} \;
|
|
||||||
chown -R root:root ${DIST}/
|
|
||||||
ar r ${DIST}/etherpad-1.deb debian-binary control.tar.gz data.tar.gz
|
|
||||||
popd
|
|
||||||
rsync -a ${DIST}/etherpad-1.deb ./
|
|
|
@ -1,69 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
NODE_VERSION="8.9.0"
|
|
||||||
|
|
||||||
#Move to the folder where ep-lite is installed
|
|
||||||
cd `dirname $0`
|
|
||||||
|
|
||||||
#Was this script started in the bin folder? if yes move out
|
|
||||||
if [ -d "../bin" ]; then
|
|
||||||
cd "../"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Is wget installed?
|
|
||||||
hash wget > /dev/null 2>&1 || {
|
|
||||||
echo "Please install wget" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#Is zip installed?
|
|
||||||
hash zip > /dev/null 2>&1 || {
|
|
||||||
echo "Please install zip" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#Is zip installed?
|
|
||||||
hash unzip > /dev/null 2>&1 || {
|
|
||||||
echo "Please install unzip" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
START_FOLDER=$(pwd);
|
|
||||||
TMP_FOLDER=$(mktemp -d)
|
|
||||||
|
|
||||||
echo "create a clean environment in $TMP_FOLDER..."
|
|
||||||
cp -ar . $TMP_FOLDER
|
|
||||||
cd $TMP_FOLDER
|
|
||||||
rm -rf node_modules
|
|
||||||
rm -f etherpad-lite-win.zip
|
|
||||||
|
|
||||||
echo "do a normal unix install first..."
|
|
||||||
bin/installDeps.sh || exit 1
|
|
||||||
|
|
||||||
echo "copy the windows settings template..."
|
|
||||||
cp settings.json.template settings.json
|
|
||||||
|
|
||||||
echo "resolve symbolic links..."
|
|
||||||
cp -rL node_modules node_modules_resolved
|
|
||||||
rm -rf node_modules
|
|
||||||
mv node_modules_resolved node_modules
|
|
||||||
|
|
||||||
echo "download windows node..."
|
|
||||||
cd bin
|
|
||||||
wget "https://nodejs.org/dist/v$NODE_VERSION/win-x86/node.exe" -O ../node.exe
|
|
||||||
|
|
||||||
echo "remove git history to reduce folder size"
|
|
||||||
rm -rf .git/objects
|
|
||||||
|
|
||||||
echo "remove windows jsdom-nocontextify/test folder"
|
|
||||||
rm -rf $TMP_FOLDER/src/node_modules/wd/node_modules/request/node_modules/form-data/node_modules/combined-stream/test
|
|
||||||
rm -rf $TMP_FOLDER/src/node_modules/nodemailer/node_modules/mailcomposer/node_modules/mimelib/node_modules/encoding/node_modules/iconv-lite/encodings/tables
|
|
||||||
|
|
||||||
echo "create the zip..."
|
|
||||||
cd $TMP_FOLDER
|
|
||||||
zip -9 -r $START_FOLDER/etherpad-lite-win.zip ./*
|
|
||||||
|
|
||||||
echo "clean up..."
|
|
||||||
rm -rf $TMP_FOLDER
|
|
||||||
|
|
||||||
echo "Finished. You can find the zip in the Etherpad root folder, it's called etherpad-lite-win.zip"
|
|
|
@ -1,145 +0,0 @@
|
||||||
/*
|
|
||||||
This is a debug tool. It checks all revisions for data corruption
|
|
||||||
*/
|
|
||||||
|
|
||||||
if(process.argv.length != 2)
|
|
||||||
{
|
|
||||||
console.error("Use: node bin/checkAllPads.js");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//initalize the variables
|
|
||||||
var db, settings, padManager;
|
|
||||||
var npm = require("../src/node_modules/npm");
|
|
||||||
var async = require("../src/node_modules/async");
|
|
||||||
|
|
||||||
var Changeset = require("../src/static/js/Changeset");
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
//load npm
|
|
||||||
function(callback) {
|
|
||||||
npm.load({}, callback);
|
|
||||||
},
|
|
||||||
//load modules
|
|
||||||
function(callback) {
|
|
||||||
settings = require('../src/node/utils/Settings');
|
|
||||||
db = require('../src/node/db/DB');
|
|
||||||
|
|
||||||
//initalize the database
|
|
||||||
db.init(callback);
|
|
||||||
},
|
|
||||||
//load pads
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
padManager = require('../src/node/db/PadManager');
|
|
||||||
|
|
||||||
padManager.listAllPads(function(err, res)
|
|
||||||
{
|
|
||||||
padIds = res.padIDs;
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
async.forEach(padIds, function(padId, callback)
|
|
||||||
{
|
|
||||||
padManager.getPad(padId, function(err, pad) {
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
//check if the pad has a pool
|
|
||||||
if(pad.pool === undefined )
|
|
||||||
{
|
|
||||||
console.error("[" + pad.id + "] Missing attribute pool");
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//create an array with key kevisions
|
|
||||||
//key revisions always save the full pad atext
|
|
||||||
var head = pad.getHeadRevisionNumber();
|
|
||||||
var keyRevisions = [];
|
|
||||||
for(var i=0;i<head;i+=100)
|
|
||||||
{
|
|
||||||
keyRevisions.push(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
//run trough all key revisions
|
|
||||||
async.forEachSeries(keyRevisions, function(keyRev, callback)
|
|
||||||
{
|
|
||||||
//create an array of revisions we need till the next keyRevision or the End
|
|
||||||
var revisionsNeeded = [];
|
|
||||||
for(var i=keyRev;i<=keyRev+100 && i<=head; i++)
|
|
||||||
{
|
|
||||||
revisionsNeeded.push(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
//this array will hold all revision changesets
|
|
||||||
var revisions = [];
|
|
||||||
|
|
||||||
//run trough all needed revisions and get them from the database
|
|
||||||
async.forEach(revisionsNeeded, function(revNum, callback)
|
|
||||||
{
|
|
||||||
db.db.get("pad:"+pad.id+":revs:" + revNum, function(err, revision)
|
|
||||||
{
|
|
||||||
revisions[revNum] = revision;
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}, function(err)
|
|
||||||
{
|
|
||||||
if(err)
|
|
||||||
{
|
|
||||||
callback(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//check if the revision exists
|
|
||||||
if (revisions[keyRev] == null) {
|
|
||||||
console.error("[" + pad.id + "] Missing revision " + keyRev);
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//check if there is a atext in the keyRevisions
|
|
||||||
if(revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined)
|
|
||||||
{
|
|
||||||
console.error("[" + pad.id + "] Missing atext in revision " + keyRev);
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var apool = pad.pool;
|
|
||||||
var atext = revisions[keyRev].meta.atext;
|
|
||||||
|
|
||||||
for(var i=keyRev+1;i<=keyRev+100 && i<=head; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//console.log("[" + pad.id + "] check revision " + i);
|
|
||||||
var cs = revisions[i].changeset;
|
|
||||||
atext = Changeset.applyToAText(cs, atext, apool);
|
|
||||||
}
|
|
||||||
catch(e)
|
|
||||||
{
|
|
||||||
console.error("[" + pad.id + "] Bad changeset at revision " + i + " - " + e.message);
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}, callback);
|
|
||||||
});
|
|
||||||
}, callback);
|
|
||||||
}
|
|
||||||
], function (err)
|
|
||||||
{
|
|
||||||
if(err) throw err;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.log("finished");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,141 +0,0 @@
|
||||||
/*
|
|
||||||
This is a debug tool. It checks all revisions for data corruption
|
|
||||||
*/
|
|
||||||
|
|
||||||
if(process.argv.length != 3)
|
|
||||||
{
|
|
||||||
console.error("Use: node bin/checkPad.js $PADID");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
//get the padID
|
|
||||||
var padId = process.argv[2];
|
|
||||||
|
|
||||||
//initialize the variables
|
|
||||||
var db, settings, padManager;
|
|
||||||
var npm = require("../src/node_modules/npm");
|
|
||||||
var async = require("../src/node_modules/async");
|
|
||||||
|
|
||||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
//load npm
|
|
||||||
function(callback) {
|
|
||||||
npm.load({}, function(er) {
|
|
||||||
callback(er);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
//load modules
|
|
||||||
function(callback) {
|
|
||||||
settings = require('../src/node/utils/Settings');
|
|
||||||
db = require('../src/node/db/DB');
|
|
||||||
|
|
||||||
//initialize the database
|
|
||||||
db.init(callback);
|
|
||||||
},
|
|
||||||
//get the pad
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
padManager = require('../src/node/db/PadManager');
|
|
||||||
|
|
||||||
padManager.doesPadExists(padId, function(err, exists)
|
|
||||||
{
|
|
||||||
if(!exists)
|
|
||||||
{
|
|
||||||
console.error("Pad does not exist");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
padManager.getPad(padId, function(err, _pad)
|
|
||||||
{
|
|
||||||
pad = _pad;
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
//create an array with key revisions
|
|
||||||
//key revisions always save the full pad atext
|
|
||||||
var head = pad.getHeadRevisionNumber();
|
|
||||||
var keyRevisions = [];
|
|
||||||
for(var i=0;i<head;i+=100)
|
|
||||||
{
|
|
||||||
keyRevisions.push(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
//run trough all key revisions
|
|
||||||
async.forEachSeries(keyRevisions, function(keyRev, callback)
|
|
||||||
{
|
|
||||||
//create an array of revisions we need till the next keyRevision or the End
|
|
||||||
var revisionsNeeded = [];
|
|
||||||
for(var i=keyRev;i<=keyRev+100 && i<=head; i++)
|
|
||||||
{
|
|
||||||
revisionsNeeded.push(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
//this array will hold all revision changesets
|
|
||||||
var revisions = [];
|
|
||||||
|
|
||||||
//run trough all needed revisions and get them from the database
|
|
||||||
async.forEach(revisionsNeeded, function(revNum, callback)
|
|
||||||
{
|
|
||||||
db.db.get("pad:"+padId+":revs:" + revNum, function(err, revision)
|
|
||||||
{
|
|
||||||
revisions[revNum] = revision;
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}, function(err)
|
|
||||||
{
|
|
||||||
if(err)
|
|
||||||
{
|
|
||||||
callback(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//check if the pad has a pool
|
|
||||||
if(pad.pool === undefined )
|
|
||||||
{
|
|
||||||
console.error("Attribute pool is missing");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//check if there is an atext in the keyRevisions
|
|
||||||
if(revisions[keyRev] === undefined || revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined)
|
|
||||||
{
|
|
||||||
console.error("No atext in key revision " + keyRev);
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var apool = pad.pool;
|
|
||||||
var atext = revisions[keyRev].meta.atext;
|
|
||||||
|
|
||||||
for(var i=keyRev+1;i<=keyRev+100 && i<=head; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//console.log("check revision " + i);
|
|
||||||
var cs = revisions[i].changeset;
|
|
||||||
atext = Changeset.applyToAText(cs, atext, apool);
|
|
||||||
}
|
|
||||||
catch(e)
|
|
||||||
{
|
|
||||||
console.error("Bad changeset at revision " + i + " - " + e.message);
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}, callback);
|
|
||||||
}
|
|
||||||
], function (err)
|
|
||||||
{
|
|
||||||
if(err) throw err;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.log("finished");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,41 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#Move to the folder where ep-lite is installed
|
|
||||||
cd `dirname $0`
|
|
||||||
|
|
||||||
#Was this script started in the bin folder? if yes move out
|
|
||||||
if [ -d "../bin" ]; then
|
|
||||||
cd "../"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ignoreRoot=0
|
|
||||||
for ARG in $*
|
|
||||||
do
|
|
||||||
if [ "$ARG" = "--root" ]; then
|
|
||||||
ignoreRoot=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
#Stop the script if it's started as root
|
|
||||||
if [ "$(id -u)" -eq 0 ] && [ $ignoreRoot -eq 0 ]; then
|
|
||||||
echo "You shouldn't start Etherpad as root!"
|
|
||||||
echo "Please type 'Etherpad rocks my socks' or supply the '--root' argument if you still want to start it as root"
|
|
||||||
read rocks
|
|
||||||
if [ ! $rocks = "Etherpad rocks my socks" ]
|
|
||||||
then
|
|
||||||
echo "Your input was incorrect"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Clean the current environment
|
|
||||||
rm -rf src/node_modules
|
|
||||||
|
|
||||||
#Prepare the environment
|
|
||||||
bin/installDeps.sh $* || exit 1
|
|
||||||
|
|
||||||
#Move to the node folder and start
|
|
||||||
echo "Started Etherpad..."
|
|
||||||
|
|
||||||
SCRIPTPATH=`pwd -P`
|
|
||||||
node "${$SCRIPTPATH}/node_modules/ep_etherpad-lite/node/server.js" $*
|
|
|
@ -1,453 +0,0 @@
|
||||||
var startTime = new Date().getTime();
|
|
||||||
var fs = require("fs");
|
|
||||||
var ueberDB = require("../src/node_modules/ueberDB");
|
|
||||||
var mysql = require("../src/node_modules/ueberDB/node_modules/mysql");
|
|
||||||
var async = require("../src/node_modules/async");
|
|
||||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
|
||||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
|
||||||
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
|
|
||||||
|
|
||||||
var settingsFile = process.argv[2];
|
|
||||||
var sqlOutputFile = process.argv[3];
|
|
||||||
|
|
||||||
//stop if the settings file is not set
|
|
||||||
if(!settingsFile || !sqlOutputFile)
|
|
||||||
{
|
|
||||||
console.error("Use: node convert.js $SETTINGSFILE $SQLOUTPUT");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
log("read settings file...");
|
|
||||||
//read the settings file and parse the json
|
|
||||||
var settings = JSON.parse(fs.readFileSync(settingsFile, "utf8"));
|
|
||||||
log("done");
|
|
||||||
|
|
||||||
log("open output file...");
|
|
||||||
var sqlOutput = fs.openSync(sqlOutputFile, "w");
|
|
||||||
var sql = "SET CHARACTER SET UTF8;\n" +
|
|
||||||
"CREATE TABLE IF NOT EXISTS `store` ( \n" +
|
|
||||||
"`key` VARCHAR( 100 ) NOT NULL , \n" +
|
|
||||||
"`value` LONGTEXT NOT NULL , \n" +
|
|
||||||
"PRIMARY KEY ( `key` ) \n" +
|
|
||||||
") ENGINE = INNODB;\n" +
|
|
||||||
"START TRANSACTION;\n\n";
|
|
||||||
fs.writeSync(sqlOutput, sql);
|
|
||||||
log("done");
|
|
||||||
|
|
||||||
var etherpadDB = mysql.createConnection({
|
|
||||||
host : settings.etherpadDB.host,
|
|
||||||
user : settings.etherpadDB.user,
|
|
||||||
password : settings.etherpadDB.password,
|
|
||||||
database : settings.etherpadDB.database,
|
|
||||||
port : settings.etherpadDB.port
|
|
||||||
});
|
|
||||||
|
|
||||||
//get the timestamp once
|
|
||||||
var timestamp = new Date().getTime();
|
|
||||||
|
|
||||||
var padIDs;
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
//get all padids out of the database...
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
log("get all padIds out of the database...");
|
|
||||||
|
|
||||||
etherpadDB.query("SELECT ID FROM PAD_META", [], function(err, _padIDs)
|
|
||||||
{
|
|
||||||
padIDs = _padIDs;
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
log("done");
|
|
||||||
|
|
||||||
//create a queue with a concurrency 100
|
|
||||||
var queue = async.queue(function (padId, callback)
|
|
||||||
{
|
|
||||||
convertPad(padId, function(err)
|
|
||||||
{
|
|
||||||
incrementPadStats();
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
//set the step callback as the queue callback
|
|
||||||
queue.drain = callback;
|
|
||||||
|
|
||||||
//add the padids to the worker queue
|
|
||||||
for(var i=0,length=padIDs.length;i<length;i++)
|
|
||||||
{
|
|
||||||
queue.push(padIDs[i].ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
], function(err)
|
|
||||||
{
|
|
||||||
if(err) throw err;
|
|
||||||
|
|
||||||
//write the groups
|
|
||||||
var sql = "";
|
|
||||||
for(var proID in proID2groupID)
|
|
||||||
{
|
|
||||||
var groupID = proID2groupID[proID];
|
|
||||||
var subdomain = proID2subdomain[proID];
|
|
||||||
|
|
||||||
sql+="REPLACE INTO store VALUES (" + etherpadDB.escape("group:" + groupID) + ", " + etherpadDB.escape(JSON.stringify(groups[groupID]))+ ");\n";
|
|
||||||
sql+="REPLACE INTO store VALUES (" + etherpadDB.escape("mapper2group:subdomain:" + subdomain) + ", " + etherpadDB.escape(groupID)+ ");\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
//close transaction
|
|
||||||
sql+="COMMIT;";
|
|
||||||
|
|
||||||
//end the sql file
|
|
||||||
fs.writeSync(sqlOutput, sql, undefined, "utf-8");
|
|
||||||
fs.closeSync(sqlOutput);
|
|
||||||
|
|
||||||
log("finished.");
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
function log(str)
|
|
||||||
{
|
|
||||||
console.log((new Date().getTime() - startTime)/1000 + "\t" + str);
|
|
||||||
}
|
|
||||||
|
|
||||||
var padsDone = 0;
|
|
||||||
|
|
||||||
function incrementPadStats()
|
|
||||||
{
|
|
||||||
padsDone++;
|
|
||||||
|
|
||||||
if(padsDone%100 == 0)
|
|
||||||
{
|
|
||||||
var averageTime = Math.round(padsDone/((new Date().getTime() - startTime)/1000));
|
|
||||||
log(padsDone + "/" + padIDs.length + "\t" + averageTime + " pad/s")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var proID2groupID = {};
|
|
||||||
var proID2subdomain = {};
|
|
||||||
var groups = {};
|
|
||||||
|
|
||||||
function convertPad(padId, callback)
|
|
||||||
{
|
|
||||||
var changesets = [];
|
|
||||||
var changesetsMeta = [];
|
|
||||||
var chatMessages = [];
|
|
||||||
var authors = [];
|
|
||||||
var apool;
|
|
||||||
var subdomain;
|
|
||||||
var padmeta;
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
//get all needed db values
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
async.parallel([
|
|
||||||
//get the pad revisions
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
var sql = "SELECT * FROM `PAD_CHAT_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_CHAT_META` WHERE ID=?)";
|
|
||||||
|
|
||||||
etherpadDB.query(sql, [padId], function(err, results)
|
|
||||||
{
|
|
||||||
if(!err)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//parse the pages
|
|
||||||
for(var i=0,length=results.length;i<length;i++)
|
|
||||||
{
|
|
||||||
parsePage(chatMessages, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, true);
|
|
||||||
}
|
|
||||||
}catch(e) {err = e}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
//get the chat entries
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
var sql = "SELECT * FROM `PAD_REVS_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_REVS_META` WHERE ID=?)";
|
|
||||||
|
|
||||||
etherpadDB.query(sql, [padId], function(err, results)
|
|
||||||
{
|
|
||||||
if(!err)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//parse the pages
|
|
||||||
for(var i=0,length=results.length;i<length;i++)
|
|
||||||
{
|
|
||||||
parsePage(changesets, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, false);
|
|
||||||
}
|
|
||||||
}catch(e) {err = e}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
//get the pad revisions meta data
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
var sql = "SELECT * FROM `PAD_REVMETA_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_REVMETA_META` WHERE ID=?)";
|
|
||||||
|
|
||||||
etherpadDB.query(sql, [padId], function(err, results)
|
|
||||||
{
|
|
||||||
if(!err)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//parse the pages
|
|
||||||
for(var i=0,length=results.length;i<length;i++)
|
|
||||||
{
|
|
||||||
parsePage(changesetsMeta, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, true);
|
|
||||||
}
|
|
||||||
}catch(e) {err = e}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
//get the attribute pool of this pad
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
var sql = "SELECT `JSON` FROM `PAD_APOOL` WHERE `ID` = ?";
|
|
||||||
|
|
||||||
etherpadDB.query(sql, [padId], function(err, results)
|
|
||||||
{
|
|
||||||
if(!err)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
apool=JSON.parse(results[0].JSON).x;
|
|
||||||
}catch(e) {err = e}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
//get the authors informations
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
var sql = "SELECT * FROM `PAD_AUTHORS_TEXT` WHERE NUMID = (SELECT `NUMID` FROM `PAD_AUTHORS_META` WHERE ID=?)";
|
|
||||||
|
|
||||||
etherpadDB.query(sql, [padId], function(err, results)
|
|
||||||
{
|
|
||||||
if(!err)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//parse the pages
|
|
||||||
for(var i=0, length=results.length;i<length;i++)
|
|
||||||
{
|
|
||||||
parsePage(authors, results[i].PAGESTART, results[i].OFFSETS, results[i].DATA, true);
|
|
||||||
}
|
|
||||||
}catch(e) {err = e}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
//get the pad information
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
var sql = "SELECT JSON FROM `PAD_META` WHERE ID=?";
|
|
||||||
|
|
||||||
etherpadDB.query(sql, [padId], function(err, results)
|
|
||||||
{
|
|
||||||
if(!err)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
padmeta = JSON.parse(results[0].JSON).x;
|
|
||||||
}catch(e) {err = e}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
//get the subdomain
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
//skip if this is no proPad
|
|
||||||
if(padId.indexOf("$") == -1)
|
|
||||||
{
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//get the proID out of this padID
|
|
||||||
var proID = padId.split("$")[0];
|
|
||||||
|
|
||||||
var sql = "SELECT subDomain FROM pro_domains WHERE ID = ?";
|
|
||||||
|
|
||||||
etherpadDB.query(sql, [proID], function(err, results)
|
|
||||||
{
|
|
||||||
if(!err)
|
|
||||||
{
|
|
||||||
subdomain = results[0].subDomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
], callback);
|
|
||||||
},
|
|
||||||
function(callback)
|
|
||||||
{
|
|
||||||
//saves all values that should be written to the database
|
|
||||||
var values = {};
|
|
||||||
|
|
||||||
//this is a pro pad, let's convert it to a group pad
|
|
||||||
if(padId.indexOf("$") != -1)
|
|
||||||
{
|
|
||||||
var padIdParts = padId.split("$");
|
|
||||||
var proID = padIdParts[0];
|
|
||||||
var padName = padIdParts[1];
|
|
||||||
|
|
||||||
var groupID
|
|
||||||
|
|
||||||
//this proID is not converted so far, do it
|
|
||||||
if(proID2groupID[proID] == null)
|
|
||||||
{
|
|
||||||
groupID = "g." + randomString(16);
|
|
||||||
|
|
||||||
//create the mappers for this new group
|
|
||||||
proID2groupID[proID] = groupID;
|
|
||||||
proID2subdomain[proID] = subdomain;
|
|
||||||
groups[groupID] = {pads: {}};
|
|
||||||
}
|
|
||||||
|
|
||||||
//use the generated groupID;
|
|
||||||
groupID = proID2groupID[proID];
|
|
||||||
|
|
||||||
//rename the pad
|
|
||||||
padId = groupID + "$" + padName;
|
|
||||||
|
|
||||||
//set the value for this pad in the group
|
|
||||||
groups[groupID].pads[padId] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var newAuthorIDs = {};
|
|
||||||
var oldName2newName = {};
|
|
||||||
|
|
||||||
//replace the authors with generated authors
|
|
||||||
// we need to do that cause where the original etherpad saves pad local authors, the new (lite) etherpad uses them global
|
|
||||||
for(var i in apool.numToAttrib)
|
|
||||||
{
|
|
||||||
var key = apool.numToAttrib[i][0];
|
|
||||||
var value = apool.numToAttrib[i][1];
|
|
||||||
|
|
||||||
//skip non authors and anonymous authors
|
|
||||||
if(key != "author" || value == "")
|
|
||||||
continue;
|
|
||||||
|
|
||||||
//generate new author values
|
|
||||||
var authorID = "a." + randomString(16);
|
|
||||||
var authorColorID = authors[i].colorId || Math.floor(Math.random()*32);
|
|
||||||
var authorName = authors[i].name || null;
|
|
||||||
|
|
||||||
//overwrite the authorID of the attribute pool
|
|
||||||
apool.numToAttrib[i][1] = authorID;
|
|
||||||
|
|
||||||
//write the author to the database
|
|
||||||
values["globalAuthor:" + authorID] = {"colorId" : authorColorID, "name": authorName, "timestamp": timestamp};
|
|
||||||
|
|
||||||
//save in mappers
|
|
||||||
newAuthorIDs[i] = authorID;
|
|
||||||
oldName2newName[value] = authorID;
|
|
||||||
}
|
|
||||||
|
|
||||||
//save all revisions
|
|
||||||
for(var i=0;i<changesets.length;i++)
|
|
||||||
{
|
|
||||||
values["pad:" + padId + ":revs:" + i] = {changeset: changesets[i],
|
|
||||||
meta : {
|
|
||||||
author: newAuthorIDs[changesetsMeta[i].a],
|
|
||||||
timestamp: changesetsMeta[i].t,
|
|
||||||
atext: changesetsMeta[i].atext || undefined
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
//save all chat messages
|
|
||||||
for(var i=0;i<chatMessages.length;i++)
|
|
||||||
{
|
|
||||||
values["pad:" + padId + ":chat:" + i] = {"text": chatMessages[i].lineText,
|
|
||||||
"userId": oldName2newName[chatMessages[i].userId],
|
|
||||||
"time": chatMessages[i].time}
|
|
||||||
}
|
|
||||||
|
|
||||||
//generate the latest atext
|
|
||||||
var fullAPool = (new AttributePool()).fromJsonable(apool);
|
|
||||||
var keyRev = Math.floor(padmeta.head / padmeta.keyRevInterval) * padmeta.keyRevInterval;
|
|
||||||
var atext = changesetsMeta[keyRev].atext;
|
|
||||||
var curRev = keyRev;
|
|
||||||
while (curRev < padmeta.head)
|
|
||||||
{
|
|
||||||
curRev++;
|
|
||||||
var changeset = changesets[curRev];
|
|
||||||
atext = Changeset.applyToAText(changeset, atext, fullAPool);
|
|
||||||
}
|
|
||||||
|
|
||||||
values["pad:" + padId] = {atext: atext,
|
|
||||||
pool: apool,
|
|
||||||
head: padmeta.head,
|
|
||||||
chatHead: padmeta.numChatMessages }
|
|
||||||
|
|
||||||
}
|
|
||||||
catch(e)
|
|
||||||
{
|
|
||||||
console.error("Error while converting pad " + padId + ", pad skipped");
|
|
||||||
console.error(e.stack ? e.stack : JSON.stringify(e));
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sql = "";
|
|
||||||
for(var key in values)
|
|
||||||
{
|
|
||||||
sql+="REPLACE INTO store VALUES (" + etherpadDB.escape(key) + ", " + etherpadDB.escape(JSON.stringify(values[key]))+ ");\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeSync(sqlOutput, sql, undefined, "utf-8");
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
], callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This parses a Page like Etherpad uses them in the databases
|
|
||||||
* The offsets descripes the length of a unit in the page, the data are
|
|
||||||
* all values behind each other
|
|
||||||
*/
|
|
||||||
function parsePage(array, pageStart, offsets, data, json)
|
|
||||||
{
|
|
||||||
var start = 0;
|
|
||||||
var lengths = offsets.split(",");
|
|
||||||
|
|
||||||
for(var i=0;i<lengths.length;i++)
|
|
||||||
{
|
|
||||||
var unitLength = lengths[i];
|
|
||||||
|
|
||||||
//skip empty units
|
|
||||||
if(unitLength == "")
|
|
||||||
continue;
|
|
||||||
|
|
||||||
//parse the number
|
|
||||||
unitLength = Number(unitLength);
|
|
||||||
|
|
||||||
//cut the unit out of data
|
|
||||||
var unit = data.substr(start, unitLength);
|
|
||||||
|
|
||||||
//put it into the array
|
|
||||||
array[pageStart + i] = json ? JSON.parse(unit) : unit;
|
|
||||||
|
|
||||||
//update start
|
|
||||||
start+=unitLength;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"etherpadDB":
|
|
||||||
{
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 3306,
|
|
||||||
"database": "etherpad",
|
|
||||||
"user": "etherpaduser",
|
|
||||||
"password": "yourpassword"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,188 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# This script is used to publish a new release/version of etherpad on github
|
|
||||||
#
|
|
||||||
# Work that is done by this script:
|
|
||||||
# ETHER_REPO:
|
|
||||||
# - Add text to CHANGELOG.md
|
|
||||||
# - Replace version of etherpad in src/package.json
|
|
||||||
# - Create a release branch and push it to github
|
|
||||||
# - Merges this release branch into master branch
|
|
||||||
# - Creating the windows build and the docs
|
|
||||||
# ETHER_WEB_REPO:
|
|
||||||
# - Creating a new branch with the docs and the windows build
|
|
||||||
# - Replacing the version numbers in the index.html
|
|
||||||
# - Push this branch and merge it to master
|
|
||||||
# ETHER_REPO:
|
|
||||||
# - Create a new release on github
|
|
||||||
|
|
||||||
ETHER_REPO="https://github.com/ether/etherpad-lite.git"
|
|
||||||
ETHER_WEB_REPO="https://github.com/ether/ether.github.com.git"
|
|
||||||
TMP_DIR="/tmp/"
|
|
||||||
|
|
||||||
echo "WARNING: You can only run this script if your github api token is allowed to create and merge branches on $ETHER_REPO and $ETHER_WEB_REPO."
|
|
||||||
echo "This script automatically changes the version number in package.json and adds a text to CHANGELOG.md."
|
|
||||||
echo "When you use this script you should be in the branch that you want to release (develop probably) on latest version. Any changes that are currently not committed will be committed."
|
|
||||||
echo "-----"
|
|
||||||
|
|
||||||
# Get the latest version
|
|
||||||
LATEST_GIT_TAG=$(git tag | tail -n 1)
|
|
||||||
|
|
||||||
# Current environment
|
|
||||||
echo "Current environment: "
|
|
||||||
echo "- branch: $(git branch | grep '* ')"
|
|
||||||
echo "- last commit date: $(git show --quiet --pretty=format:%ad)"
|
|
||||||
echo "- current version: $LATEST_GIT_TAG"
|
|
||||||
echo "- temp dir: $TMP_DIR"
|
|
||||||
|
|
||||||
# Get new version number
|
|
||||||
# format: x.x.x
|
|
||||||
echo -n "Enter new version (x.x.x): "
|
|
||||||
read VERSION
|
|
||||||
|
|
||||||
# Get the message for the changelogs
|
|
||||||
read -p "Enter new changelog entries (press enter): "
|
|
||||||
tmp=$(mktemp)
|
|
||||||
"${EDITOR:-vi}" $tmp
|
|
||||||
changelogText=$(<$tmp)
|
|
||||||
echo "$changelogText"
|
|
||||||
rm $tmp
|
|
||||||
|
|
||||||
if [ "$changelogText" != "" ]; then
|
|
||||||
changelogText="# $VERSION\n$changelogText"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# get the token for the github api
|
|
||||||
echo -n "Enter your github api token: "
|
|
||||||
read API_TOKEN
|
|
||||||
|
|
||||||
function check_api_token {
|
|
||||||
echo "Checking if github api token is valid..."
|
|
||||||
CURL_RESPONSE=$(curl --silent -i https://api.github.com/user?access_token=$API_TOKEN | iconv -f utf8)
|
|
||||||
HTTP_STATUS=$(echo $CURL_RESPONSE | head -1 | sed -r 's/.* ([0-9]{3}) .*/\1/')
|
|
||||||
[[ $HTTP_STATUS != "200" ]] && echo "Aborting: Invalid github api token" && exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function modify_files {
|
|
||||||
# Add changelog text to first line of CHANGELOG.md
|
|
||||||
|
|
||||||
msg=""
|
|
||||||
# source: https://unix.stackexchange.com/questions/9784/how-can-i-read-line-by-line-from-a-variable-in-bash#9789
|
|
||||||
while IFS= read -r line
|
|
||||||
do
|
|
||||||
# replace newlines with literal "\n" for using with sed
|
|
||||||
msg+="$line\n"
|
|
||||||
done < <(printf '%s\n' "${changelogText}")
|
|
||||||
|
|
||||||
sed -i "1s/^/${msg}\n/" CHANGELOG.md
|
|
||||||
[[ $? != 0 ]] && echo "Aborting: Error modifying CHANGELOG.md" && exit 1
|
|
||||||
|
|
||||||
# Replace version number of etherpad in package.json
|
|
||||||
sed -i -r "s/(\"version\"[ ]*: \").*(\")/\1$VERSION\2/" src/package.json
|
|
||||||
[[ $? != 0 ]] && echo "Aborting: Error modifying package.json" && exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_release_branch {
|
|
||||||
echo "Creating new release branch..."
|
|
||||||
git rev-parse --verify release/$VERSION 2>/dev/null
|
|
||||||
if [ $? == 0 ]; then
|
|
||||||
echo "Aborting: Release branch already present"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
git checkout -b release/$VERSION
|
|
||||||
[[ $? != 0 ]] && echo "Aborting: Error creating release branch" && exit 1
|
|
||||||
|
|
||||||
echo "Committing CHANGELOG.md and package.json"
|
|
||||||
git add CHANGELOG.md
|
|
||||||
git add src/package.json
|
|
||||||
git commit -m "Release version $VERSION"
|
|
||||||
|
|
||||||
echo "Pushing release branch to github..."
|
|
||||||
git push -u $ETHER_REPO release/$VERSION
|
|
||||||
[[ $? != 0 ]] && echo "Aborting: Error pushing release branch to github" && exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function merge_release_branch {
|
|
||||||
echo "Merging release to master branch on github..."
|
|
||||||
API_JSON=$(printf '{"base": "master","head": "release/%s","commit_message": "Merge new release into master branch!"}' $VERSION)
|
|
||||||
CURL_RESPONSE=$(curl --silent -i -N --data "$API_JSON" https://api.github.com/repos/ether/etherpad-lite/merges?access_token=$API_TOKEN | iconv -f utf8)
|
|
||||||
echo $CURL_RESPONSE
|
|
||||||
HTTP_STATUS=$(echo $CURL_RESPONSE | head -1 | sed -r 's/.* ([0-9]{3}) .*/\1/')
|
|
||||||
[[ $HTTP_STATUS != "200" ]] && echo "Aborting: Error merging release branch on github" && exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_builds {
|
|
||||||
echo "Cloning etherpad-lite repo and ether.github.com repo..."
|
|
||||||
cd $TMP_DIR
|
|
||||||
rm -rf etherpad-lite ether.github.com
|
|
||||||
git clone $ETHER_REPO --branch master
|
|
||||||
git clone $ETHER_WEB_REPO
|
|
||||||
echo "Creating windows build..."
|
|
||||||
cd etherpad-lite
|
|
||||||
bin/buildForWindows.sh
|
|
||||||
[[ $? != 0 ]] && echo "Aborting: Error creating build for windows" && exit 1
|
|
||||||
echo "Creating docs..."
|
|
||||||
make docs
|
|
||||||
[[ $? != 0 ]] && echo "Aborting: Error generating docs" && exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function push_builds {
|
|
||||||
cd $TMP_DIR/etherpad-lite/
|
|
||||||
echo "Copying windows build and docs to website repo..."
|
|
||||||
GIT_SHA=$(git rev-parse HEAD | cut -c1-10)
|
|
||||||
mv etherpad-lite-win.zip $TMP_DIR/ether.github.com/downloads/etherpad-lite-win-$VERSION-$GIT_SHA.zip
|
|
||||||
|
|
||||||
mv out/doc $TMP_DIR/ether.github.com/doc/v$VERSION
|
|
||||||
|
|
||||||
cd $TMP_DIR/ether.github.com/
|
|
||||||
sed -i "s/etherpad-lite-win.*\.zip/etherpad-lite-win-$VERSION-$GIT_SHA.zip/" index.html
|
|
||||||
sed -i "s/$LATEST_GIT_TAG/$VERSION/g" index.html
|
|
||||||
git checkout -b release_$VERSION
|
|
||||||
[[ $? != 0 ]] && echo "Aborting: Error creating new release branch" && exit 1
|
|
||||||
git add doc/
|
|
||||||
git add downloads/
|
|
||||||
git commit -a -m "Release version $VERSION"
|
|
||||||
git push -u $ETHER_WEB_REPO release_$VERSION
|
|
||||||
[[ $? != 0 ]] && echo "Aborting: Error pushing release branch to github" && exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function merge_web_branch {
|
|
||||||
echo "Merging release to master branch on github..."
|
|
||||||
API_JSON=$(printf '{"base": "master","head": "release_%s","commit_message": "Release version %s"}' $VERSION $VERSION)
|
|
||||||
CURL_RESPONSE=$(curl --silent -i -N --data "$API_JSON" https://api.github.com/repos/ether/ether.github.com/merges?access_token=$API_TOKEN | iconv -f utf8)
|
|
||||||
echo $CURL_RESPONSE
|
|
||||||
HTTP_STATUS=$(echo $CURL_RESPONSE | head -1 | sed -r 's/.* ([0-9]{3}) .*/\1/')
|
|
||||||
[[ $HTTP_STATUS != "200" ]] && echo "Aborting: Error merging release branch" && exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
function publish_release {
|
|
||||||
echo -n "Do you want to publish a new release on github (y/n)? "
|
|
||||||
read PUBLISH_RELEASE
|
|
||||||
if [ $PUBLISH_RELEASE = "y" ]; then
|
|
||||||
# create a new release on github
|
|
||||||
API_JSON=$(printf '{"tag_name": "%s","target_commitish": "master","name": "Release %s","body": "%s","draft": false,"prerelease": false}' $VERSION $VERSION $changelogText)
|
|
||||||
CURL_RESPONSE=$(curl --silent -i -N --data "$API_JSON" https://api.github.com/repos/ether/etherpad-lite/releases?access_token=$API_TOKEN | iconv -f utf8)
|
|
||||||
HTTP_STATUS=$(echo $CURL_RESPONSE | head -1 | sed -r 's/.* ([0-9]{3}) .*/\1/')
|
|
||||||
[[ $HTTP_STATUS != "201" ]] && echo "Aborting: Error publishing release on github" && exit 1
|
|
||||||
else
|
|
||||||
echo "No release published on github!"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function todo_notification {
|
|
||||||
echo "Release procedure was successful, but you have to do some steps manually:"
|
|
||||||
echo "- Update the wiki at https://github.com/ether/etherpad-lite/wiki"
|
|
||||||
echo "- Create a pull request on github to merge the master branch back to develop"
|
|
||||||
echo "- Announce the new release on the mailing list, blog.etherpad.org and Twitter"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Call functions
|
|
||||||
check_api_token
|
|
||||||
modify_files
|
|
||||||
create_release_branch
|
|
||||||
merge_release_branch
|
|
||||||
create_builds
|
|
||||||
push_builds
|
|
||||||
merge_web_branch
|
|
||||||
publish_release
|
|
||||||
todo_notification
|
|
|
@ -1,9 +0,0 @@
|
||||||
Package: etherpad
|
|
||||||
Version: 1.3
|
|
||||||
Section: base
|
|
||||||
Priority: optional
|
|
||||||
Architecture: i386
|
|
||||||
Installed-Size: SIZE
|
|
||||||
Depends:
|
|
||||||
Maintainer: John McLear <john@mclear.co.uk>
|
|
||||||
Description: Etherpad is a collaborative editor.
|
|
|
@ -1,7 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# Start the services!
|
|
||||||
|
|
||||||
service etherpad start
|
|
||||||
echo "Give Etherpad about 3 minutes to install dependencies then visit http://localhost:9001 in your web browser"
|
|
||||||
echo "To stop etherpad type 'service etherpad stop', To restart type 'service etherpad restart'".
|
|
||||||
rm -f /tmp/etherpad.log /tmp/etherpad.err
|
|
|
@ -1,26 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Installs node if it isn't already installed
|
|
||||||
#
|
|
||||||
# Don't steamroll over a previously installed node version
|
|
||||||
# TODO provide a local version of node?
|
|
||||||
|
|
||||||
VER="0.10.4"
|
|
||||||
ARCH="x86"
|
|
||||||
if [ `arch | grep 64` ]
|
|
||||||
then
|
|
||||||
ARCH="x64"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# TODO test version
|
|
||||||
if [ ! -f /usr/local/bin/node ]
|
|
||||||
then
|
|
||||||
pushd /tmp
|
|
||||||
wget -c "http://nodejs.org/dist/v${VER}/node-v${VER}-linux-${ARCH}.tar.gz"
|
|
||||||
rm -rf /tmp/node-v${VER}-linux-${ARCH}
|
|
||||||
tar xf node-v${VER}-linux-${ARCH}.tar.gz -C /tmp/
|
|
||||||
cp -a /tmp/node-v${VER}-linux-${ARCH}/* /usr/local/
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create Etherpad user
|
|
||||||
adduser --system etherpad
|
|
|
@ -1,4 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Stop the appserver:
|
|
||||||
service etherpad stop || true
|
|
|
@ -1,28 +0,0 @@
|
||||||
description "etherpad"
|
|
||||||
|
|
||||||
start on started networking
|
|
||||||
stop on runlevel [!2345]
|
|
||||||
|
|
||||||
env EPHOME=/opt/etherpad
|
|
||||||
env EPLOGS=/var/log/etherpad
|
|
||||||
env EPUSER=etherpad
|
|
||||||
|
|
||||||
respawn
|
|
||||||
|
|
||||||
pre-start script
|
|
||||||
cd $EPHOME
|
|
||||||
mkdir $EPLOGS ||true
|
|
||||||
chown $EPUSER $EPLOGS ||true
|
|
||||||
chmod 0755 $EPLOGS ||true
|
|
||||||
chown -R $EPUSER $EPHOME/var ||true
|
|
||||||
$EPHOME/bin/installDeps.sh >> $EPLOGS/error.log || { stop; exit 1; }
|
|
||||||
end script
|
|
||||||
|
|
||||||
script
|
|
||||||
cd $EPHOME/
|
|
||||||
exec su -s /bin/sh -c 'exec "$0" "$@"' $EPUSER -- node node_modules/ep_etherpad-lite/node/server.js \
|
|
||||||
>> $EPLOGS/access.log \
|
|
||||||
2>> $EPLOGS/error.log
|
|
||||||
echo "Etherpad is running on http://localhost:9001 - To change settings edit /opt/etherpad/settings.json"
|
|
||||||
|
|
||||||
end script
|
|
|
@ -1,28 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#Move to the folder where ep-lite is installed
|
|
||||||
cd `dirname $0`
|
|
||||||
|
|
||||||
#Was this script started in the bin folder? if yes move out
|
|
||||||
if [ -d "../bin" ]; then
|
|
||||||
cd "../"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Prepare the environment
|
|
||||||
bin/installDeps.sh || exit 1
|
|
||||||
|
|
||||||
hash node-inspector > /dev/null 2>&1 || {
|
|
||||||
echo "You need to install node-inspector to run the tests!" >&2
|
|
||||||
echo "You can install it with npm" >&2
|
|
||||||
echo "Run: npm install -g node-inspector" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
node-inspector &
|
|
||||||
|
|
||||||
echo "If you are new to node-inspector, take a look at this video: https://youtu.be/AOnK3NVnxL8"
|
|
||||||
|
|
||||||
node --debug node_modules/ep_etherpad-lite/node/server.js $*
|
|
||||||
|
|
||||||
#Kill node-inspector before ending
|
|
||||||
kill $!
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
A tool for deleting pads from the CLI, because sometimes a brick is required to fix a window.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if(process.argv.length != 3)
|
|
||||||
{
|
|
||||||
console.error("Use: node deletePad.js $PADID");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
//get the padID
|
|
||||||
var padId = process.argv[2];
|
|
||||||
|
|
||||||
var db, padManager, pad, settings;
|
|
||||||
var neededDBValues = ["pad:"+padId];
|
|
||||||
|
|
||||||
var npm = require("../src/node_modules/npm");
|
|
||||||
var async = require("../src/node_modules/async");
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
// load npm
|
|
||||||
function(callback) {
|
|
||||||
npm.load({}, function(er) {
|
|
||||||
if(er)
|
|
||||||
{
|
|
||||||
console.error("Could not load NPM: " + er)
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// load modules
|
|
||||||
function(callback) {
|
|
||||||
settings = require('../src/node/utils/Settings');
|
|
||||||
db = require('../src/node/db/DB');
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
// initialize the database
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
db.init(callback);
|
|
||||||
},
|
|
||||||
// delete the pad and its links
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
padManager = require('../src/node/db/PadManager');
|
|
||||||
|
|
||||||
padManager.removePad(padId, function(err){
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
], function (err)
|
|
||||||
{
|
|
||||||
if(err) throw err;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.log("Finished deleting padId: "+padId);
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,45 +0,0 @@
|
||||||
#!/usr/bin/env PYTHONUNBUFFERED=1 python2
|
|
||||||
#
|
|
||||||
# Created by Bjarni R. Einarsson, placed in the public domain. Go wild!
|
|
||||||
#
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
|
||||||
dirtydb_input = sys.argv[1]
|
|
||||||
dirtydb_output = '%s.new' % dirtydb_input
|
|
||||||
assert(os.path.exists(dirtydb_input))
|
|
||||||
assert(not os.path.exists(dirtydb_output))
|
|
||||||
except:
|
|
||||||
print
|
|
||||||
print 'Usage: %s /path/to/dirty.db' % sys.argv[0]
|
|
||||||
print
|
|
||||||
print 'Note: Will create a file named dirty.db.new in the same folder,'
|
|
||||||
print ' please make sure permissions are OK and a file by that'
|
|
||||||
print ' name does not exist already. This script works by omitting'
|
|
||||||
print ' duplicate lines from the dirty.db file, keeping only the'
|
|
||||||
print ' last (latest) instance. No revision data should be lost,'
|
|
||||||
print ' but be careful, make backups. If it breaks you get to keep'
|
|
||||||
print ' both pieces!'
|
|
||||||
print
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
dirtydb = {}
|
|
||||||
lines = 0
|
|
||||||
with open(dirtydb_input, 'r') as fd:
|
|
||||||
print 'Reading %s' % dirtydb_input
|
|
||||||
for line in fd:
|
|
||||||
lines += 1
|
|
||||||
data = json.loads(line)
|
|
||||||
dirtydb[data['key']] = line
|
|
||||||
if lines % 10000 == 0:
|
|
||||||
sys.stderr.write('.')
|
|
||||||
print
|
|
||||||
print 'OK, found %d unique keys in %d lines' % (len(dirtydb), lines)
|
|
||||||
|
|
||||||
with open(dirtydb_output, 'w') as fd:
|
|
||||||
for data in dirtydb.values():
|
|
||||||
fd.write(data)
|
|
||||||
|
|
||||||
print 'Wrote data to %s. All done!' % dirtydb_output
|
|
|
@ -1,18 +0,0 @@
|
||||||
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to
|
|
||||||
deal in the Software without restriction, including without limitation the
|
|
||||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
IN THE SOFTWARE.
|
|
|
@ -1,76 +0,0 @@
|
||||||
Here's how the node docs work.
|
|
||||||
|
|
||||||
Each type of heading has a description block.
|
|
||||||
|
|
||||||
|
|
||||||
## module
|
|
||||||
|
|
||||||
Stability: 3 - Stable
|
|
||||||
|
|
||||||
description and examples.
|
|
||||||
|
|
||||||
### module.property
|
|
||||||
|
|
||||||
* Type
|
|
||||||
|
|
||||||
description of the property.
|
|
||||||
|
|
||||||
### module.someFunction(x, y, [z=100])
|
|
||||||
|
|
||||||
* `x` {String} the description of the string
|
|
||||||
* `y` {Boolean} Should I stay or should I go?
|
|
||||||
* `z` {Number} How many zebras to bring.
|
|
||||||
|
|
||||||
A description of the function.
|
|
||||||
|
|
||||||
### Event: 'blerg'
|
|
||||||
|
|
||||||
* Argument: SomeClass object.
|
|
||||||
|
|
||||||
Modules don't usually raise events on themselves. `cluster` is the
|
|
||||||
only exception.
|
|
||||||
|
|
||||||
## Class: SomeClass
|
|
||||||
|
|
||||||
description of the class.
|
|
||||||
|
|
||||||
### Class Method: SomeClass.classMethod(anArg)
|
|
||||||
|
|
||||||
* `anArg` {Object} Just an argument
|
|
||||||
* `field` {String} anArg can have this field.
|
|
||||||
* `field2` {Boolean} Another field. Default: `false`.
|
|
||||||
* Return: {Boolean} `true` if it worked.
|
|
||||||
|
|
||||||
Description of the method for humans.
|
|
||||||
|
|
||||||
### someClass.nextSibling()
|
|
||||||
|
|
||||||
* Return: {SomeClass object | null} The next someClass in line.
|
|
||||||
|
|
||||||
### someClass.someProperty
|
|
||||||
|
|
||||||
* String
|
|
||||||
|
|
||||||
The indication of what someProperty is.
|
|
||||||
|
|
||||||
### Event: 'grelb'
|
|
||||||
|
|
||||||
* `isBlerg` {Boolean}
|
|
||||||
|
|
||||||
This event is emitted on instances of SomeClass, not on the module itself.
|
|
||||||
|
|
||||||
|
|
||||||
* Modules have (description, Properties, Functions, Classes, Examples)
|
|
||||||
* Properties have (type, description)
|
|
||||||
* Functions have (list of arguments, description)
|
|
||||||
* Classes have (description, Properties, Methods, Events)
|
|
||||||
* Events have (list of arguments, description)
|
|
||||||
* Methods have (list of arguments, description)
|
|
||||||
* Properties have (type, description)
|
|
||||||
|
|
||||||
# CLI usage
|
|
||||||
|
|
||||||
Run the following from the etherpad-lite root directory:
|
|
||||||
```sh
|
|
||||||
$ node bin/doc/generate doc/index.md --format=html --template=doc/template.html > out.html
|
|
||||||
```
|
|
|
@ -1,120 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
// Copyright Joyent, Inc. and other Node contributors.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
// copy of this software and associated documentation files (the
|
|
||||||
// "Software"), to deal in the Software without restriction, including
|
|
||||||
// without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
||||||
// persons to whom the Software is furnished to do so, subject to the
|
|
||||||
// following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included
|
|
||||||
// in all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
||||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
||||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
||||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
var marked = require('marked');
|
|
||||||
var fs = require('fs');
|
|
||||||
var path = require('path');
|
|
||||||
|
|
||||||
// parse the args.
|
|
||||||
// Don't use nopt or whatever for this. It's simple enough.
|
|
||||||
|
|
||||||
var args = process.argv.slice(2);
|
|
||||||
var format = 'json';
|
|
||||||
var template = null;
|
|
||||||
var inputFile = null;
|
|
||||||
|
|
||||||
args.forEach(function (arg) {
|
|
||||||
if (!arg.match(/^\-\-/)) {
|
|
||||||
inputFile = arg;
|
|
||||||
} else if (arg.match(/^\-\-format=/)) {
|
|
||||||
format = arg.replace(/^\-\-format=/, '');
|
|
||||||
} else if (arg.match(/^\-\-template=/)) {
|
|
||||||
template = arg.replace(/^\-\-template=/, '');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
if (!inputFile) {
|
|
||||||
throw new Error('No input file specified');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
console.error('Input file = %s', inputFile);
|
|
||||||
fs.readFile(inputFile, 'utf8', function(er, input) {
|
|
||||||
if (er) throw er;
|
|
||||||
// process the input for @include lines
|
|
||||||
processIncludes(inputFile, input, next);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var includeExpr = /^@include\s+([A-Za-z0-9-_\/]+)(?:\.)?([a-zA-Z]*)$/gmi;
|
|
||||||
var includeData = {};
|
|
||||||
function processIncludes(inputFile, input, cb) {
|
|
||||||
var includes = input.match(includeExpr);
|
|
||||||
if (includes === null) return cb(null, input);
|
|
||||||
var errState = null;
|
|
||||||
console.error(includes);
|
|
||||||
var incCount = includes.length;
|
|
||||||
if (incCount === 0) cb(null, input);
|
|
||||||
|
|
||||||
includes.forEach(function(include) {
|
|
||||||
var fname = include.replace(/^@include\s+/, '');
|
|
||||||
if (!fname.match(/\.md$/)) fname += '.md';
|
|
||||||
|
|
||||||
if (includeData.hasOwnProperty(fname)) {
|
|
||||||
input = input.split(include).join(includeData[fname]);
|
|
||||||
incCount--;
|
|
||||||
if (incCount === 0) {
|
|
||||||
return cb(null, input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fullFname = path.resolve(path.dirname(inputFile), fname);
|
|
||||||
fs.readFile(fullFname, 'utf8', function(er, inc) {
|
|
||||||
if (errState) return;
|
|
||||||
if (er) return cb(errState = er);
|
|
||||||
processIncludes(fullFname, inc, function(er, inc) {
|
|
||||||
if (errState) return;
|
|
||||||
if (er) return cb(errState = er);
|
|
||||||
incCount--;
|
|
||||||
includeData[fname] = inc;
|
|
||||||
input = input.split(include).join(includeData[fname]);
|
|
||||||
if (incCount === 0) {
|
|
||||||
return cb(null, input);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function next(er, input) {
|
|
||||||
if (er) throw er;
|
|
||||||
switch (format) {
|
|
||||||
case 'json':
|
|
||||||
require('./json.js')(input, inputFile, function(er, obj) {
|
|
||||||
console.log(JSON.stringify(obj, null, 2));
|
|
||||||
if (er) throw er;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'html':
|
|
||||||
require('./html.js')(input, inputFile, template, function(er, html) {
|
|
||||||
if (er) throw er;
|
|
||||||
console.log(html);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error('Invalid format: ' + format);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,173 +0,0 @@
|
||||||
// Copyright Joyent, Inc. and other Node contributors.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
// copy of this software and associated documentation files (the
|
|
||||||
// "Software"), to deal in the Software without restriction, including
|
|
||||||
// without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
||||||
// persons to whom the Software is furnished to do so, subject to the
|
|
||||||
// following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included
|
|
||||||
// in all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
||||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
||||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
||||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
var fs = require('fs');
|
|
||||||
var marked = require('marked');
|
|
||||||
var path = require('path');
|
|
||||||
|
|
||||||
module.exports = toHTML;
|
|
||||||
|
|
||||||
function toHTML(input, filename, template, cb) {
|
|
||||||
var lexed = marked.lexer(input);
|
|
||||||
fs.readFile(template, 'utf8', function(er, template) {
|
|
||||||
if (er) return cb(er);
|
|
||||||
render(lexed, filename, template, cb);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function render(lexed, filename, template, cb) {
|
|
||||||
// get the section
|
|
||||||
var section = getSection(lexed);
|
|
||||||
|
|
||||||
filename = path.basename(filename, '.md');
|
|
||||||
|
|
||||||
lexed = parseLists(lexed);
|
|
||||||
|
|
||||||
// generate the table of contents.
|
|
||||||
// this mutates the lexed contents in-place.
|
|
||||||
buildToc(lexed, filename, function(er, toc) {
|
|
||||||
if (er) return cb(er);
|
|
||||||
|
|
||||||
template = template.replace(/__FILENAME__/g, filename);
|
|
||||||
template = template.replace(/__SECTION__/g, section);
|
|
||||||
template = template.replace(/__TOC__/g, toc);
|
|
||||||
|
|
||||||
// content has to be the last thing we do with
|
|
||||||
// the lexed tokens, because it's destructive.
|
|
||||||
content = marked.parser(lexed);
|
|
||||||
template = template.replace(/__CONTENT__/g, content);
|
|
||||||
|
|
||||||
cb(null, template);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// just update the list item text in-place.
|
|
||||||
// lists that come right after a heading are what we're after.
|
|
||||||
function parseLists(input) {
|
|
||||||
var state = null;
|
|
||||||
var depth = 0;
|
|
||||||
var output = [];
|
|
||||||
output.links = input.links;
|
|
||||||
input.forEach(function(tok) {
|
|
||||||
if (state === null) {
|
|
||||||
if (tok.type === 'heading') {
|
|
||||||
state = 'AFTERHEADING';
|
|
||||||
}
|
|
||||||
output.push(tok);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (state === 'AFTERHEADING') {
|
|
||||||
if (tok.type === 'list_start') {
|
|
||||||
state = 'LIST';
|
|
||||||
if (depth === 0) {
|
|
||||||
output.push({ type:'html', text: '<div class="signature">' });
|
|
||||||
}
|
|
||||||
depth++;
|
|
||||||
output.push(tok);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state = null;
|
|
||||||
output.push(tok);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (state === 'LIST') {
|
|
||||||
if (tok.type === 'list_start') {
|
|
||||||
depth++;
|
|
||||||
output.push(tok);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tok.type === 'list_end') {
|
|
||||||
depth--;
|
|
||||||
if (depth === 0) {
|
|
||||||
state = null;
|
|
||||||
output.push({ type:'html', text: '</div>' });
|
|
||||||
}
|
|
||||||
output.push(tok);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tok.text) {
|
|
||||||
tok.text = parseListItem(tok.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output.push(tok);
|
|
||||||
});
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function parseListItem(text) {
|
|
||||||
text = text.replace(/\{([^\}]+)\}/, '<span class="type">$1</span>');
|
|
||||||
//XXX maybe put more stuff here?
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// section is just the first heading
|
|
||||||
function getSection(lexed) {
|
|
||||||
var section = '';
|
|
||||||
for (var i = 0, l = lexed.length; i < l; i++) {
|
|
||||||
var tok = lexed[i];
|
|
||||||
if (tok.type === 'heading') return tok.text;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function buildToc(lexed, filename, cb) {
|
|
||||||
var indent = 0;
|
|
||||||
var toc = [];
|
|
||||||
var depth = 0;
|
|
||||||
lexed.forEach(function(tok) {
|
|
||||||
if (tok.type !== 'heading') return;
|
|
||||||
if (tok.depth - depth > 1) {
|
|
||||||
return cb(new Error('Inappropriate heading level\n' +
|
|
||||||
JSON.stringify(tok)));
|
|
||||||
}
|
|
||||||
|
|
||||||
depth = tok.depth;
|
|
||||||
var id = getId(filename + '_' + tok.text.trim());
|
|
||||||
toc.push(new Array((depth - 1) * 2 + 1).join(' ') +
|
|
||||||
'* <a href="#' + id + '">' +
|
|
||||||
tok.text + '</a>');
|
|
||||||
tok.text += '<span><a class="mark" href="#' + id + '" ' +
|
|
||||||
'id="' + id + '">#</a></span>';
|
|
||||||
});
|
|
||||||
|
|
||||||
toc = marked.parse(toc.join('\n'));
|
|
||||||
cb(null, toc);
|
|
||||||
}
|
|
||||||
|
|
||||||
var idCounters = {};
|
|
||||||
function getId(text) {
|
|
||||||
text = text.toLowerCase();
|
|
||||||
text = text.replace(/[^a-z0-9]+/g, '_');
|
|
||||||
text = text.replace(/^_+|_+$/, '');
|
|
||||||
text = text.replace(/^([^a-z])/, '_$1');
|
|
||||||
if (idCounters.hasOwnProperty(text)) {
|
|
||||||
text += '_' + (++idCounters[text]);
|
|
||||||
} else {
|
|
||||||
idCounters[text] = 0;
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,557 +0,0 @@
|
||||||
// Copyright Joyent, Inc. and other Node contributors.
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
// copy of this software and associated documentation files (the
|
|
||||||
// "Software"), to deal in the Software without restriction, including
|
|
||||||
// without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
||||||
// persons to whom the Software is furnished to do so, subject to the
|
|
||||||
// following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included
|
|
||||||
// in all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
||||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
||||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
||||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
module.exports = doJSON;
|
|
||||||
|
|
||||||
// Take the lexed input, and return a JSON-encoded object
|
|
||||||
// A module looks like this: https://gist.github.com/1777387
|
|
||||||
|
|
||||||
var marked = require('marked');
|
|
||||||
|
|
||||||
function doJSON(input, filename, cb) {
|
|
||||||
var root = {source: filename};
|
|
||||||
var stack = [root];
|
|
||||||
var depth = 0;
|
|
||||||
var current = root;
|
|
||||||
var state = null;
|
|
||||||
var lexed = marked.lexer(input);
|
|
||||||
lexed.forEach(function (tok) {
|
|
||||||
var type = tok.type;
|
|
||||||
var text = tok.text;
|
|
||||||
|
|
||||||
// <!-- type = module -->
|
|
||||||
// This is for cases where the markdown semantic structure is lacking.
|
|
||||||
if (type === 'paragraph' || type === 'html') {
|
|
||||||
var metaExpr = /<!--([^=]+)=([^\-]+)-->\n*/g;
|
|
||||||
text = text.replace(metaExpr, function(_0, k, v) {
|
|
||||||
current[k.trim()] = v.trim();
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
text = text.trim();
|
|
||||||
if (!text) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'heading' &&
|
|
||||||
!text.trim().match(/^example/i)) {
|
|
||||||
if (tok.depth - depth > 1) {
|
|
||||||
return cb(new Error('Inappropriate heading level\n'+
|
|
||||||
JSON.stringify(tok)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sometimes we have two headings with a single
|
|
||||||
// blob of description. Treat as a clone.
|
|
||||||
if (current &&
|
|
||||||
state === 'AFTERHEADING' &&
|
|
||||||
depth === tok.depth) {
|
|
||||||
var clone = current;
|
|
||||||
current = newSection(tok);
|
|
||||||
current.clone = clone;
|
|
||||||
// don't keep it around on the stack.
|
|
||||||
stack.pop();
|
|
||||||
} else {
|
|
||||||
// if the level is greater than the current depth,
|
|
||||||
// then it's a child, so we should just leave the stack
|
|
||||||
// as it is.
|
|
||||||
// However, if it's a sibling or higher, then it implies
|
|
||||||
// the closure of the other sections that came before.
|
|
||||||
// root is always considered the level=0 section,
|
|
||||||
// and the lowest heading is 1, so this should always
|
|
||||||
// result in having a valid parent node.
|
|
||||||
var d = tok.depth;
|
|
||||||
while (d <= depth) {
|
|
||||||
finishSection(stack.pop(), stack[stack.length - 1]);
|
|
||||||
d++;
|
|
||||||
}
|
|
||||||
current = newSection(tok);
|
|
||||||
}
|
|
||||||
|
|
||||||
depth = tok.depth;
|
|
||||||
stack.push(current);
|
|
||||||
state = 'AFTERHEADING';
|
|
||||||
return;
|
|
||||||
} // heading
|
|
||||||
|
|
||||||
// Immediately after a heading, we can expect the following
|
|
||||||
//
|
|
||||||
// { type: 'code', text: 'Stability: ...' },
|
|
||||||
//
|
|
||||||
// a list: starting with list_start, ending with list_end,
|
|
||||||
// maybe containing other nested lists in each item.
|
|
||||||
//
|
|
||||||
// If one of these isn't found, then anything that comes between
|
|
||||||
// here and the next heading should be parsed as the desc.
|
|
||||||
var stability
|
|
||||||
if (state === 'AFTERHEADING') {
|
|
||||||
if (type === 'code' &&
|
|
||||||
(stability = text.match(/^Stability: ([0-5])(?:\s*-\s*)?(.*)$/))) {
|
|
||||||
current.stability = parseInt(stability[1], 10);
|
|
||||||
current.stabilityText = stability[2].trim();
|
|
||||||
return;
|
|
||||||
} else if (type === 'list_start' && !tok.ordered) {
|
|
||||||
state = 'AFTERHEADING_LIST';
|
|
||||||
current.list = current.list || [];
|
|
||||||
current.list.push(tok);
|
|
||||||
current.list.level = 1;
|
|
||||||
} else {
|
|
||||||
current.desc = current.desc || [];
|
|
||||||
if (!Array.isArray(current.desc)) {
|
|
||||||
current.shortDesc = current.desc;
|
|
||||||
current.desc = [];
|
|
||||||
}
|
|
||||||
current.desc.push(tok);
|
|
||||||
state = 'DESC';
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === 'AFTERHEADING_LIST') {
|
|
||||||
current.list.push(tok);
|
|
||||||
if (type === 'list_start') {
|
|
||||||
current.list.level++;
|
|
||||||
} else if (type === 'list_end') {
|
|
||||||
current.list.level--;
|
|
||||||
}
|
|
||||||
if (current.list.level === 0) {
|
|
||||||
state = 'AFTERHEADING';
|
|
||||||
processList(current);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
current.desc = current.desc || [];
|
|
||||||
current.desc.push(tok);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// finish any sections left open
|
|
||||||
while (root !== (current = stack.pop())) {
|
|
||||||
finishSection(current, stack[stack.length - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cb(null, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// go from something like this:
|
|
||||||
// [ { type: 'list_item_start' },
|
|
||||||
// { type: 'text',
|
|
||||||
// text: '`settings` Object, Optional' },
|
|
||||||
// { type: 'list_start', ordered: false },
|
|
||||||
// { type: 'list_item_start' },
|
|
||||||
// { type: 'text',
|
|
||||||
// text: 'exec: String, file path to worker file. Default: `__filename`' },
|
|
||||||
// { type: 'list_item_end' },
|
|
||||||
// { type: 'list_item_start' },
|
|
||||||
// { type: 'text',
|
|
||||||
// text: 'args: Array, string arguments passed to worker.' },
|
|
||||||
// { type: 'text',
|
|
||||||
// text: 'Default: `process.argv.slice(2)`' },
|
|
||||||
// { type: 'list_item_end' },
|
|
||||||
// { type: 'list_item_start' },
|
|
||||||
// { type: 'text',
|
|
||||||
// text: 'silent: Boolean, whether or not to send output to parent\'s stdio.' },
|
|
||||||
// { type: 'text', text: 'Default: `false`' },
|
|
||||||
// { type: 'space' },
|
|
||||||
// { type: 'list_item_end' },
|
|
||||||
// { type: 'list_end' },
|
|
||||||
// { type: 'list_item_end' },
|
|
||||||
// { type: 'list_end' } ]
|
|
||||||
// to something like:
|
|
||||||
// [ { name: 'settings',
|
|
||||||
// type: 'object',
|
|
||||||
// optional: true,
|
|
||||||
// settings:
|
|
||||||
// [ { name: 'exec',
|
|
||||||
// type: 'string',
|
|
||||||
// desc: 'file path to worker file',
|
|
||||||
// default: '__filename' },
|
|
||||||
// { name: 'args',
|
|
||||||
// type: 'array',
|
|
||||||
// default: 'process.argv.slice(2)',
|
|
||||||
// desc: 'string arguments passed to worker.' },
|
|
||||||
// { name: 'silent',
|
|
||||||
// type: 'boolean',
|
|
||||||
// desc: 'whether or not to send output to parent\'s stdio.',
|
|
||||||
// default: 'false' } ] } ]
|
|
||||||
|
|
||||||
function processList(section) {
|
|
||||||
var list = section.list;
|
|
||||||
var values = [];
|
|
||||||
var current;
|
|
||||||
var stack = [];
|
|
||||||
|
|
||||||
// for now, *just* build the hierarchical list
|
|
||||||
list.forEach(function(tok) {
|
|
||||||
var type = tok.type;
|
|
||||||
if (type === 'space') return;
|
|
||||||
if (type === 'list_item_start') {
|
|
||||||
if (!current) {
|
|
||||||
var n = {};
|
|
||||||
values.push(n);
|
|
||||||
current = n;
|
|
||||||
} else {
|
|
||||||
current.options = current.options || [];
|
|
||||||
stack.push(current);
|
|
||||||
var n = {};
|
|
||||||
current.options.push(n);
|
|
||||||
current = n;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else if (type === 'list_item_end') {
|
|
||||||
if (!current) {
|
|
||||||
throw new Error('invalid list - end without current item\n' +
|
|
||||||
JSON.stringify(tok) + '\n' +
|
|
||||||
JSON.stringify(list));
|
|
||||||
}
|
|
||||||
current = stack.pop();
|
|
||||||
} else if (type === 'text') {
|
|
||||||
if (!current) {
|
|
||||||
throw new Error('invalid list - text without current item\n' +
|
|
||||||
JSON.stringify(tok) + '\n' +
|
|
||||||
JSON.stringify(list));
|
|
||||||
}
|
|
||||||
current.textRaw = current.textRaw || '';
|
|
||||||
current.textRaw += tok.text + ' ';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// shove the name in there for properties, since they are always
|
|
||||||
// just going to be the value etc.
|
|
||||||
if (section.type === 'property' && values[0]) {
|
|
||||||
values[0].textRaw = '`' + section.name + '` ' + values[0].textRaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
// now pull the actual values out of the text bits.
|
|
||||||
values.forEach(parseListItem);
|
|
||||||
|
|
||||||
// Now figure out what this list actually means.
|
|
||||||
// depending on the section type, the list could be different things.
|
|
||||||
|
|
||||||
switch (section.type) {
|
|
||||||
case 'ctor':
|
|
||||||
case 'classMethod':
|
|
||||||
case 'method':
|
|
||||||
// each item is an argument, unless the name is 'return',
|
|
||||||
// in which case it's the return value.
|
|
||||||
section.signatures = section.signatures || [];
|
|
||||||
var sig = {}
|
|
||||||
section.signatures.push(sig);
|
|
||||||
sig.params = values.filter(function(v) {
|
|
||||||
if (v.name === 'return') {
|
|
||||||
sig.return = v;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
parseSignature(section.textRaw, sig);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'property':
|
|
||||||
// there should be only one item, which is the value.
|
|
||||||
// copy the data up to the section.
|
|
||||||
var value = values[0] || {};
|
|
||||||
delete value.name;
|
|
||||||
section.typeof = value.type;
|
|
||||||
delete value.type;
|
|
||||||
Object.keys(value).forEach(function(k) {
|
|
||||||
section[k] = value[k];
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'event':
|
|
||||||
// event: each item is an argument.
|
|
||||||
section.params = values;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// section.listParsed = values;
|
|
||||||
delete section.list;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// textRaw = "someobject.someMethod(a, [b=100], [c])"
|
|
||||||
function parseSignature(text, sig) {
|
|
||||||
var params = text.match(paramExpr);
|
|
||||||
if (!params) return;
|
|
||||||
params = params[1];
|
|
||||||
// the ] is irrelevant. [ indicates optionalness.
|
|
||||||
params = params.replace(/\]/g, '');
|
|
||||||
params = params.split(/,/)
|
|
||||||
params.forEach(function(p, i, _) {
|
|
||||||
p = p.trim();
|
|
||||||
if (!p) return;
|
|
||||||
var param = sig.params[i];
|
|
||||||
var optional = false;
|
|
||||||
var def;
|
|
||||||
// [foo] -> optional
|
|
||||||
if (p.charAt(0) === '[') {
|
|
||||||
optional = true;
|
|
||||||
p = p.substr(1);
|
|
||||||
}
|
|
||||||
var eq = p.indexOf('=');
|
|
||||||
if (eq !== -1) {
|
|
||||||
def = p.substr(eq + 1);
|
|
||||||
p = p.substr(0, eq);
|
|
||||||
}
|
|
||||||
if (!param) {
|
|
||||||
param = sig.params[i] = { name: p };
|
|
||||||
}
|
|
||||||
// at this point, the name should match.
|
|
||||||
if (p !== param.name) {
|
|
||||||
console.error('Warning: invalid param "%s"', p);
|
|
||||||
console.error(' > ' + JSON.stringify(param));
|
|
||||||
console.error(' > ' + text);
|
|
||||||
}
|
|
||||||
if (optional) param.optional = true;
|
|
||||||
if (def !== undefined) param.default = def;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function parseListItem(item) {
|
|
||||||
if (item.options) item.options.forEach(parseListItem);
|
|
||||||
if (!item.textRaw) return;
|
|
||||||
|
|
||||||
// the goal here is to find the name, type, default, and optional.
|
|
||||||
// anything left over is 'desc'
|
|
||||||
var text = item.textRaw.trim();
|
|
||||||
// text = text.replace(/^(Argument|Param)s?\s*:?\s*/i, '');
|
|
||||||
|
|
||||||
text = text.replace(/^, /, '').trim();
|
|
||||||
var retExpr = /^returns?\s*:?\s*/i;
|
|
||||||
var ret = text.match(retExpr);
|
|
||||||
if (ret) {
|
|
||||||
item.name = 'return';
|
|
||||||
text = text.replace(retExpr, '');
|
|
||||||
} else {
|
|
||||||
var nameExpr = /^['`"]?([^'`": \{]+)['`"]?\s*:?\s*/;
|
|
||||||
var name = text.match(nameExpr);
|
|
||||||
if (name) {
|
|
||||||
item.name = name[1];
|
|
||||||
text = text.replace(nameExpr, '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
text = text.trim();
|
|
||||||
var defaultExpr = /\(default\s*[:=]?\s*['"`]?([^, '"`]*)['"`]?\)/i;
|
|
||||||
var def = text.match(defaultExpr);
|
|
||||||
if (def) {
|
|
||||||
item.default = def[1];
|
|
||||||
text = text.replace(defaultExpr, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
text = text.trim();
|
|
||||||
var typeExpr = /^\{([^\}]+)\}/;
|
|
||||||
var type = text.match(typeExpr);
|
|
||||||
if (type) {
|
|
||||||
item.type = type[1];
|
|
||||||
text = text.replace(typeExpr, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
text = text.trim();
|
|
||||||
var optExpr = /^Optional\.|(?:, )?Optional$/;
|
|
||||||
var optional = text.match(optExpr);
|
|
||||||
if (optional) {
|
|
||||||
item.optional = true;
|
|
||||||
text = text.replace(optExpr, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
text = text.replace(/^\s*-\s*/, '');
|
|
||||||
text = text.trim();
|
|
||||||
if (text) item.desc = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function finishSection(section, parent) {
|
|
||||||
if (!section || !parent) {
|
|
||||||
throw new Error('Invalid finishSection call\n'+
|
|
||||||
JSON.stringify(section) + '\n' +
|
|
||||||
JSON.stringify(parent));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!section.type) {
|
|
||||||
section.type = 'module';
|
|
||||||
if (parent && (parent.type === 'misc')) {
|
|
||||||
section.type = 'misc';
|
|
||||||
}
|
|
||||||
section.displayName = section.name;
|
|
||||||
section.name = section.name.toLowerCase()
|
|
||||||
.trim().replace(/\s+/g, '_');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (section.desc && Array.isArray(section.desc)) {
|
|
||||||
section.desc.links = section.desc.links || [];
|
|
||||||
section.desc = marked.parser(section.desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!section.list) section.list = [];
|
|
||||||
processList(section);
|
|
||||||
|
|
||||||
// classes sometimes have various 'ctor' children
|
|
||||||
// which are actually just descriptions of a constructor
|
|
||||||
// class signature.
|
|
||||||
// Merge them into the parent.
|
|
||||||
if (section.type === 'class' && section.ctors) {
|
|
||||||
section.signatures = section.signatures || [];
|
|
||||||
var sigs = section.signatures;
|
|
||||||
section.ctors.forEach(function(ctor) {
|
|
||||||
ctor.signatures = ctor.signatures || [{}];
|
|
||||||
ctor.signatures.forEach(function(sig) {
|
|
||||||
sig.desc = ctor.desc;
|
|
||||||
});
|
|
||||||
sigs.push.apply(sigs, ctor.signatures);
|
|
||||||
});
|
|
||||||
delete section.ctors;
|
|
||||||
}
|
|
||||||
|
|
||||||
// properties are a bit special.
|
|
||||||
// their "type" is the type of object, not "property"
|
|
||||||
if (section.properties) {
|
|
||||||
section.properties.forEach(function (p) {
|
|
||||||
if (p.typeof) p.type = p.typeof;
|
|
||||||
else delete p.type;
|
|
||||||
delete p.typeof;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle clones
|
|
||||||
if (section.clone) {
|
|
||||||
var clone = section.clone;
|
|
||||||
delete section.clone;
|
|
||||||
delete clone.clone;
|
|
||||||
deepCopy(section, clone);
|
|
||||||
finishSection(clone, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
var plur;
|
|
||||||
if (section.type.slice(-1) === 's') {
|
|
||||||
plur = section.type + 'es';
|
|
||||||
} else if (section.type.slice(-1) === 'y') {
|
|
||||||
plur = section.type.replace(/y$/, 'ies');
|
|
||||||
} else {
|
|
||||||
plur = section.type + 's';
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the parent's type is 'misc', then it's just a random
|
|
||||||
// collection of stuff, like the "globals" section.
|
|
||||||
// Make the children top-level items.
|
|
||||||
if (section.type === 'misc') {
|
|
||||||
Object.keys(section).forEach(function(k) {
|
|
||||||
switch (k) {
|
|
||||||
case 'textRaw':
|
|
||||||
case 'name':
|
|
||||||
case 'type':
|
|
||||||
case 'desc':
|
|
||||||
case 'miscs':
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
if (parent.type === 'misc') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Array.isArray(k) && parent[k]) {
|
|
||||||
parent[k] = parent[k].concat(section[k]);
|
|
||||||
} else if (!parent[k]) {
|
|
||||||
parent[k] = section[k];
|
|
||||||
} else {
|
|
||||||
// parent already has, and it's not an array.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
parent[plur] = parent[plur] || [];
|
|
||||||
parent[plur].push(section);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Not a general purpose deep copy.
|
|
||||||
// But sufficient for these basic things.
|
|
||||||
function deepCopy(src, dest) {
|
|
||||||
Object.keys(src).filter(function(k) {
|
|
||||||
return !dest.hasOwnProperty(k);
|
|
||||||
}).forEach(function(k) {
|
|
||||||
dest[k] = deepCopy_(src[k]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deepCopy_(src) {
|
|
||||||
if (!src) return src;
|
|
||||||
if (Array.isArray(src)) {
|
|
||||||
var c = new Array(src.length);
|
|
||||||
src.forEach(function(v, i) {
|
|
||||||
c[i] = deepCopy_(v);
|
|
||||||
});
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
if (typeof src === 'object') {
|
|
||||||
var c = {};
|
|
||||||
Object.keys(src).forEach(function(k) {
|
|
||||||
c[k] = deepCopy_(src[k]);
|
|
||||||
});
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
return src;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// these parse out the contents of an H# tag
|
|
||||||
var eventExpr = /^Event(?::|\s)+['"]?([^"']+).*$/i;
|
|
||||||
var classExpr = /^Class:\s*([^ ]+).*?$/i;
|
|
||||||
var propExpr = /^(?:property:?\s*)?[^\.]+\.([^ \.\(\)]+)\s*?$/i;
|
|
||||||
var braceExpr = /^(?:property:?\s*)?[^\.\[]+(\[[^\]]+\])\s*?$/i;
|
|
||||||
var classMethExpr =
|
|
||||||
/^class\s*method\s*:?[^\.]+\.([^ \.\(\)]+)\([^\)]*\)\s*?$/i;
|
|
||||||
var methExpr =
|
|
||||||
/^(?:method:?\s*)?(?:[^\.]+\.)?([^ \.\(\)]+)\([^\)]*\)\s*?$/i;
|
|
||||||
var newExpr = /^new ([A-Z][a-z]+)\([^\)]*\)\s*?$/;
|
|
||||||
var paramExpr = /\((.*)\);?$/;
|
|
||||||
|
|
||||||
function newSection(tok) {
|
|
||||||
var section = {};
|
|
||||||
// infer the type from the text.
|
|
||||||
var text = section.textRaw = tok.text;
|
|
||||||
if (text.match(eventExpr)) {
|
|
||||||
section.type = 'event';
|
|
||||||
section.name = text.replace(eventExpr, '$1');
|
|
||||||
} else if (text.match(classExpr)) {
|
|
||||||
section.type = 'class';
|
|
||||||
section.name = text.replace(classExpr, '$1');
|
|
||||||
} else if (text.match(braceExpr)) {
|
|
||||||
section.type = 'property';
|
|
||||||
section.name = text.replace(braceExpr, '$1');
|
|
||||||
} else if (text.match(propExpr)) {
|
|
||||||
section.type = 'property';
|
|
||||||
section.name = text.replace(propExpr, '$1');
|
|
||||||
} else if (text.match(classMethExpr)) {
|
|
||||||
section.type = 'classMethod';
|
|
||||||
section.name = text.replace(classMethExpr, '$1');
|
|
||||||
} else if (text.match(methExpr)) {
|
|
||||||
section.type = 'method';
|
|
||||||
section.name = text.replace(methExpr, '$1');
|
|
||||||
} else if (text.match(newExpr)) {
|
|
||||||
section.type = 'ctor';
|
|
||||||
section.name = text.replace(newExpr, '$1');
|
|
||||||
} else {
|
|
||||||
section.name = text;
|
|
||||||
}
|
|
||||||
return section;
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
|
|
||||||
"name": "node-doc-generator",
|
|
||||||
"description": "Internal tool for generating Node.js API docs",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.6.10"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"marked": ">=0.3.6"
|
|
||||||
},
|
|
||||||
"devDependencies": {},
|
|
||||||
"optionalDependencies": {},
|
|
||||||
"bin": "./generate.js"
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
/*
|
|
||||||
This is a debug tool. It helps to extract all datas of a pad and move it from an productive environment and to a develop environment to reproduce bugs there. It outputs a dirtydb file
|
|
||||||
*/
|
|
||||||
|
|
||||||
if(process.argv.length != 3)
|
|
||||||
{
|
|
||||||
console.error("Use: node extractPadData.js $PADID");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
//get the padID
|
|
||||||
var padId = process.argv[2];
|
|
||||||
|
|
||||||
var db, dirty, padManager, pad, settings;
|
|
||||||
var neededDBValues = ["pad:"+padId];
|
|
||||||
|
|
||||||
var npm = require("../node_modules/ep_etherpad-lite/node_modules/npm");
|
|
||||||
var async = require("../node_modules/ep_etherpad-lite/node_modules/async");
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
// load npm
|
|
||||||
function(callback) {
|
|
||||||
npm.load({}, function(er) {
|
|
||||||
if(er)
|
|
||||||
{
|
|
||||||
console.error("Could not load NPM: " + er)
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// load modules
|
|
||||||
function(callback) {
|
|
||||||
settings = require('../node_modules/ep_etherpad-lite/node/utils/Settings');
|
|
||||||
db = require('../node_modules/ep_etherpad-lite/node/db/DB');
|
|
||||||
dirty = require("../node_modules/ep_etherpad-lite/node_modules/ueberDB/node_modules/dirty")(padId + ".db");
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
//initialize the database
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
db.init(callback);
|
|
||||||
},
|
|
||||||
//get the pad
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
padManager = require('../node_modules/ep_etherpad-lite/node/db/PadManager');
|
|
||||||
|
|
||||||
padManager.getPad(padId, function(err, _pad)
|
|
||||||
{
|
|
||||||
pad = _pad;
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
//add all authors
|
|
||||||
var authors = pad.getAllAuthors();
|
|
||||||
for(var i=0;i<authors.length;i++)
|
|
||||||
{
|
|
||||||
neededDBValues.push("globalAuthor:" + authors[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
//add all revisions
|
|
||||||
var revHead = pad.head;
|
|
||||||
for(var i=0;i<=revHead;i++)
|
|
||||||
{
|
|
||||||
neededDBValues.push("pad:"+padId+":revs:" + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
//get all chat values
|
|
||||||
var chatHead = pad.chatHead;
|
|
||||||
for(var i=0;i<=chatHead;i++)
|
|
||||||
{
|
|
||||||
neededDBValues.push("pad:"+padId+":chat:" + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
//get and set all values
|
|
||||||
async.forEach(neededDBValues, function(dbkey, callback)
|
|
||||||
{
|
|
||||||
db.db.db.wrappedDB.get(dbkey, function(err, dbvalue)
|
|
||||||
{
|
|
||||||
if(err) { callback(err); return}
|
|
||||||
|
|
||||||
if(dbvalue && typeof dbvalue != 'object'){
|
|
||||||
dbvalue=JSON.parse(dbvalue); // if it's not json then parse it as json
|
|
||||||
}
|
|
||||||
|
|
||||||
dirty.set(dbkey, dbvalue, callback);
|
|
||||||
});
|
|
||||||
}, callback);
|
|
||||||
}
|
|
||||||
], function (err)
|
|
||||||
{
|
|
||||||
if(err) throw err;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.log("finished");
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//get the pad object
|
|
||||||
//get all revisions of this pad
|
|
||||||
//get all authors related to this pad
|
|
||||||
//get the readonly link related to this pad
|
|
||||||
//get the chat entries related to this pad
|
|
|
@ -1,110 +0,0 @@
|
||||||
var startTime = new Date().getTime();
|
|
||||||
|
|
||||||
require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) {
|
|
||||||
|
|
||||||
var fs = require("fs");
|
|
||||||
|
|
||||||
var ueberDB = require("ep_etherpad-lite/node_modules/ueberDB");
|
|
||||||
var settings = require("ep_etherpad-lite/node/utils/Settings");
|
|
||||||
var log4js = require('ep_etherpad-lite/node_modules/log4js');
|
|
||||||
|
|
||||||
var dbWrapperSettings = {
|
|
||||||
cache: 0,
|
|
||||||
writeInterval: 100,
|
|
||||||
json: false // data is already json encoded
|
|
||||||
};
|
|
||||||
var db = new ueberDB.database(settings.dbType, settings.dbSettings, dbWrapperSettings, log4js.getLogger("ueberDB"));
|
|
||||||
|
|
||||||
var sqlFile = process.argv[2];
|
|
||||||
|
|
||||||
//stop if the settings file is not set
|
|
||||||
if(!sqlFile)
|
|
||||||
{
|
|
||||||
console.error("Use: node importSqlFile.js $SQLFILE");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
log("initializing db");
|
|
||||||
db.init(function(err)
|
|
||||||
{
|
|
||||||
//there was an error while initializing the database, output it and stop
|
|
||||||
if(err)
|
|
||||||
{
|
|
||||||
console.error("ERROR: Problem while initializing the database");
|
|
||||||
console.error(err.stack ? err.stack : err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
log("done");
|
|
||||||
|
|
||||||
log("open output file...");
|
|
||||||
var lines = fs.readFileSync(sqlFile, 'utf8').split("\n");
|
|
||||||
|
|
||||||
var count = lines.length;
|
|
||||||
var keyNo = 0;
|
|
||||||
|
|
||||||
process.stdout.write("Start importing " + count + " keys...\n");
|
|
||||||
lines.forEach(function(l) {
|
|
||||||
if (l.substr(0, 27) == "REPLACE INTO store VALUES (") {
|
|
||||||
var pos = l.indexOf("', '");
|
|
||||||
var key = l.substr(28, pos - 28);
|
|
||||||
var value = l.substr(pos + 3);
|
|
||||||
value = value.substr(0, value.length - 2);
|
|
||||||
console.log("key: " + key + " val: " + value);
|
|
||||||
console.log("unval: " + unescape(value));
|
|
||||||
db.set(key, unescape(value), null);
|
|
||||||
keyNo++;
|
|
||||||
if (keyNo % 1000 == 0) {
|
|
||||||
process.stdout.write(" " + keyNo + "/" + count + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
process.stdout.write("\n");
|
|
||||||
process.stdout.write("done. waiting for db to finish transaction. depended on dbms this may take some time...\n");
|
|
||||||
|
|
||||||
db.doShutdown(function() {
|
|
||||||
log("finished, imported " + keyNo + " keys.");
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function log(str)
|
|
||||||
{
|
|
||||||
console.log((new Date().getTime() - startTime)/1000 + "\t" + str);
|
|
||||||
}
|
|
||||||
|
|
||||||
unescape = function(val) {
|
|
||||||
// value is a string
|
|
||||||
if (val.substr(0, 1) == "'") {
|
|
||||||
val = val.substr(0, val.length - 1).substr(1);
|
|
||||||
|
|
||||||
return val.replace(/\\[0nrbtZ\\'"]/g, function(s) {
|
|
||||||
switch(s) {
|
|
||||||
case "\\0": return "\0";
|
|
||||||
case "\\n": return "\n";
|
|
||||||
case "\\r": return "\r";
|
|
||||||
case "\\b": return "\b";
|
|
||||||
case "\\t": return "\t";
|
|
||||||
case "\\Z": return "\x1a";
|
|
||||||
default: return s.substr(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// value is a boolean or NULL
|
|
||||||
if (val == 'NULL') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (val == 'true') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (val == 'false') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// value is a number
|
|
||||||
return val;
|
|
||||||
};
|
|
|
@ -1,118 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#Move to the folder where ep-lite is installed
|
|
||||||
cd `dirname $0`
|
|
||||||
|
|
||||||
#Was this script started in the bin folder? if yes move out
|
|
||||||
if [ -d "../bin" ]; then
|
|
||||||
cd "../"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Is gnu-grep (ggrep) installed on SunOS (Solaris)
|
|
||||||
if [ $(uname) = "SunOS" ]; then
|
|
||||||
hash ggrep > /dev/null 2>&1 || {
|
|
||||||
echo "Please install ggrep (pkg install gnu-grep)" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Is curl installed?
|
|
||||||
hash curl > /dev/null 2>&1 || {
|
|
||||||
echo "Please install curl" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#Is node installed?
|
|
||||||
#Not checking io.js, default installation creates a symbolic link to node
|
|
||||||
hash node > /dev/null 2>&1 || {
|
|
||||||
echo "Please install node.js ( https://nodejs.org )" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#Is npm installed?
|
|
||||||
hash npm > /dev/null 2>&1 || {
|
|
||||||
echo "Please install npm ( https://npmjs.org )" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
#Check npm version
|
|
||||||
NPM_VERSION=$(npm --version)
|
|
||||||
NPM_MAIN_VERSION=$(echo $NPM_VERSION | cut -d "." -f 1)
|
|
||||||
if [ $(echo $NPM_MAIN_VERSION) = "0" ]; then
|
|
||||||
echo "You're running a wrong version of npm, you're using $NPM_VERSION, we need 1.x or higher" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Check node version
|
|
||||||
NODE_VERSION=$(node --version)
|
|
||||||
NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2)
|
|
||||||
NODE_V_MAIN=$(echo $NODE_VERSION | cut -d "." -f 1)
|
|
||||||
NODE_V_MAIN=${NODE_V_MAIN#"v"}
|
|
||||||
if [ ! $NODE_V_MINOR = "v0.10" ] && [ ! $NODE_V_MINOR = "v0.11" ] && [ ! $NODE_V_MINOR = "v0.12" ] && [ ! $NODE_V_MAIN -ge 4 ]; then
|
|
||||||
echo "You're running a wrong version of node. You're using $NODE_VERSION, we need node v0.10.x or higher" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Get the name of the settings file
|
|
||||||
settings="settings.json"
|
|
||||||
a='';
|
|
||||||
for arg in $*; do
|
|
||||||
if [ "$a" = "--settings" ] || [ "$a" = "-s" ]; then settings=$arg; fi
|
|
||||||
a=$arg
|
|
||||||
done
|
|
||||||
|
|
||||||
#Does a $settings exist? if not copy the template
|
|
||||||
if [ ! -f $settings ]; then
|
|
||||||
echo "Copy the settings template to $settings..."
|
|
||||||
cp settings.json.template $settings || exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Ensure that all dependencies are up to date... If this is the first time you have run Etherpad please be patient."
|
|
||||||
(
|
|
||||||
mkdir -p node_modules
|
|
||||||
cd node_modules
|
|
||||||
[ -e ep_etherpad-lite ] || ln -s ../src ep_etherpad-lite
|
|
||||||
cd ep_etherpad-lite
|
|
||||||
npm install --loglevel warn
|
|
||||||
) || {
|
|
||||||
rm -rf node_modules
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Ensure jQuery is downloaded and up to date..."
|
|
||||||
DOWNLOAD_JQUERY="true"
|
|
||||||
NEEDED_VERSION="1.9.1"
|
|
||||||
if [ -f "src/static/js/jquery.js" ]; then
|
|
||||||
if [ $(uname) = "SunOS" ]; then
|
|
||||||
VERSION=$(head -n 3 src/static/js/jquery.js | ggrep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?")
|
|
||||||
else
|
|
||||||
VERSION=$(head -n 3 src/static/js/jquery.js | grep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ${VERSION#v} = $NEEDED_VERSION ]; then
|
|
||||||
DOWNLOAD_JQUERY="false"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $DOWNLOAD_JQUERY = "true" ]; then
|
|
||||||
curl -lo src/static/js/jquery.js https://code.jquery.com/jquery-$NEEDED_VERSION.js || exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Remove all minified data to force node creating it new
|
|
||||||
echo "Clearing minified cache..."
|
|
||||||
rm -f var/minified*
|
|
||||||
|
|
||||||
echo "Ensure custom css/js files are created..."
|
|
||||||
|
|
||||||
for f in "index" "pad" "timeslider"
|
|
||||||
do
|
|
||||||
if [ ! -f "src/static/custom/$f.js" ]; then
|
|
||||||
cp "src/static/custom/js.template" "src/static/custom/$f.js" || exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "src/static/custom/$f.css" ]; then
|
|
||||||
cp "src/static/custom/css.template" "src/static/custom/$f.css" || exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
exit 0
|
|
|
@ -1,42 +0,0 @@
|
||||||
@echo off
|
|
||||||
|
|
||||||
:: Change directory to etherpad-lite root
|
|
||||||
cd /D "%~dp0\.."
|
|
||||||
|
|
||||||
:: Is node installed?
|
|
||||||
cmd /C node -e "" || ( echo "Please install node.js ( https://nodejs.org )" && exit /B 1 )
|
|
||||||
|
|
||||||
echo _
|
|
||||||
echo Ensure that all dependencies are up to date... If this is the first time you have run Etherpad please be patient.
|
|
||||||
|
|
||||||
mkdir node_modules
|
|
||||||
cd /D node_modules
|
|
||||||
mklink /D "ep_etherpad-lite" "..\src"
|
|
||||||
|
|
||||||
cd /D "ep_etherpad-lite"
|
|
||||||
cmd /C npm install --loglevel warn || exit /B 1
|
|
||||||
|
|
||||||
cd /D "%~dp0\.."
|
|
||||||
|
|
||||||
echo _
|
|
||||||
echo Copying custom templates...
|
|
||||||
set custom_dir=node_modules\ep_etherpad-lite\static\custom
|
|
||||||
FOR %%f IN (index pad timeslider) DO (
|
|
||||||
if NOT EXIST "%custom_dir%\%%f.js" copy "%custom_dir%\js.template" "%custom_dir%\%%f.js"
|
|
||||||
if NOT EXIST "%custom_dir%\%%f.css" copy "%custom_dir%\css.template" "%custom_dir%\%%f.css"
|
|
||||||
)
|
|
||||||
|
|
||||||
echo _
|
|
||||||
echo Clearing cache...
|
|
||||||
del /S var\minified*
|
|
||||||
|
|
||||||
echo _
|
|
||||||
echo Setting up settings.json...
|
|
||||||
IF NOT EXIST settings.json (
|
|
||||||
echo Can't find settings.json.
|
|
||||||
echo Copying settings.json.template...
|
|
||||||
cmd /C copy settings.json.template settings.json || exit /B 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo _
|
|
||||||
echo Installed Etherpad! To run Etherpad type start.bat
|
|
|
@ -1,38 +0,0 @@
|
||||||
require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) {
|
|
||||||
|
|
||||||
process.chdir(npm.root+'/..')
|
|
||||||
|
|
||||||
// This script requires that you have modified your settings.json file
|
|
||||||
// to work with a real database. Please make a backup of your dirty.db
|
|
||||||
// file before using this script, just to be safe.
|
|
||||||
|
|
||||||
var settings = require("ep_etherpad-lite/node/utils/Settings");
|
|
||||||
var dirty = require("../src/node_modules/dirty")('var/dirty.db');
|
|
||||||
var ueberDB = require("../src/node_modules/ueberdb2");
|
|
||||||
var log4js = require("../src/node_modules/log4js");
|
|
||||||
var dbWrapperSettings = {
|
|
||||||
"cache": "0", // The cache slows things down when you're mostly writing.
|
|
||||||
};
|
|
||||||
var db = new ueberDB.database(settings.dbType, settings.dbSettings, dbWrapperSettings, log4js.getLogger("ueberDB"));
|
|
||||||
|
|
||||||
db.init(function() {
|
|
||||||
console.log("Waiting for dirtyDB to parse its file.");
|
|
||||||
dirty.on("load", function(length) {
|
|
||||||
console.log("Loaded " + length + " records, processing now.");
|
|
||||||
var remaining = length;
|
|
||||||
dirty.forEach(function(key, value) {
|
|
||||||
db.set(key, value, function(error) {
|
|
||||||
if (typeof error != 'undefined') {
|
|
||||||
console.log("Unexpected result handling: ", key, value, " was: ", error);
|
|
||||||
}
|
|
||||||
remaining -= 1;
|
|
||||||
var oldremaining = remaining;
|
|
||||||
if ((oldremaining % 100) == 0) {
|
|
||||||
console.log("Records not yet flushed to database: ", remaining);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
console.log("Please wait for all records to flush to database, then kill this process.");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,123 +0,0 @@
|
||||||
/*
|
|
||||||
This is a repair tool. It rebuilds an old pad at a new pad location up to a
|
|
||||||
known "good" revision.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if(process.argv.length != 4 && process.argv.length != 5) {
|
|
||||||
console.error("Use: node bin/repairPad.js $PADID $REV [$NEWPADID]");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var npm = require("../src/node_modules/npm");
|
|
||||||
var async = require("../src/node_modules/async");
|
|
||||||
var ueberDB = require("../src/node_modules/ueberDB");
|
|
||||||
|
|
||||||
var padId = process.argv[2];
|
|
||||||
var newRevHead = process.argv[3];
|
|
||||||
var newPadId = process.argv[4] || padId + "-rebuilt";
|
|
||||||
|
|
||||||
var db, oldPad, newPad, settings;
|
|
||||||
var AuthorManager, ChangeSet, Pad, PadManager;
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
function(callback) {
|
|
||||||
npm.load({}, function(err) {
|
|
||||||
if(err) {
|
|
||||||
console.error("Could not load NPM: " + err)
|
|
||||||
process.exit(1);
|
|
||||||
} else {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
function(callback) {
|
|
||||||
// Get a handle into the database
|
|
||||||
db = require('../src/node/db/DB');
|
|
||||||
db.init(callback);
|
|
||||||
}, function(callback) {
|
|
||||||
PadManager = require('../src/node/db/PadManager');
|
|
||||||
Pad = require('../src/node/db/Pad').Pad;
|
|
||||||
// Get references to the original pad and to a newly created pad
|
|
||||||
// HACK: This is a standalone script, so we want to write everything
|
|
||||||
// out to the database immediately. The only problem with this is
|
|
||||||
// that a driver (like the mysql driver) can hardcode these values.
|
|
||||||
db.db.db.settings = {cache: 0, writeInterval: 0, json: true};
|
|
||||||
// Validate the newPadId if specified and that a pad with that ID does
|
|
||||||
// not already exist to avoid overwriting it.
|
|
||||||
if (!PadManager.isValidPadId(newPadId)) {
|
|
||||||
console.error("Cannot create a pad with that id as it is invalid");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
PadManager.doesPadExists(newPadId, function(err, exists) {
|
|
||||||
if (exists) {
|
|
||||||
console.error("Cannot create a pad with that id as it already exists");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
PadManager.getPad(padId, function(err, pad) {
|
|
||||||
oldPad = pad;
|
|
||||||
newPad = new Pad(newPadId);
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}, function(callback) {
|
|
||||||
// Clone all Chat revisions
|
|
||||||
var chatHead = oldPad.chatHead;
|
|
||||||
for(var i = 0, curHeadNum = 0; i <= chatHead; i++) {
|
|
||||||
db.db.get("pad:" + padId + ":chat:" + i, function (err, chat) {
|
|
||||||
db.db.set("pad:" + newPadId + ":chat:" + curHeadNum++, chat);
|
|
||||||
console.log("Created: Chat Revision: pad:" + newPadId + ":chat:" + curHeadNum);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
}, function(callback) {
|
|
||||||
// Rebuild Pad from revisions up to and including the new revision head
|
|
||||||
AuthorManager = require("../src/node/db/AuthorManager");
|
|
||||||
Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
|
||||||
// Author attributes are derived from changesets, but there can also be
|
|
||||||
// non-author attributes with specific mappings that changesets depend on
|
|
||||||
// and, AFAICT, cannot be recreated any other way
|
|
||||||
newPad.pool.numToAttrib = oldPad.pool.numToAttrib;
|
|
||||||
for(var curRevNum = 0; curRevNum <= newRevHead; curRevNum++) {
|
|
||||||
db.db.get("pad:" + padId + ":revs:" + curRevNum, function(err, rev) {
|
|
||||||
if (rev.meta) {
|
|
||||||
throw "The specified revision number could not be found.";
|
|
||||||
}
|
|
||||||
var newRevNum = ++newPad.head;
|
|
||||||
var newRevId = "pad:" + newPad.id + ":revs:" + newRevNum;
|
|
||||||
db.db.set(newRevId, rev);
|
|
||||||
AuthorManager.addPad(rev.meta.author, newPad.id);
|
|
||||||
newPad.atext = Changeset.applyToAText(rev.changeset, newPad.atext, newPad.pool);
|
|
||||||
console.log("Created: Revision: pad:" + newPad.id + ":revs:" + newRevNum);
|
|
||||||
if (newRevNum == newRevHead) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, function(callback) {
|
|
||||||
// Add saved revisions up to the new revision head
|
|
||||||
console.log(newPad.head);
|
|
||||||
var newSavedRevisions = [];
|
|
||||||
for(var i in oldPad.savedRevisions) {
|
|
||||||
savedRev = oldPad.savedRevisions[i]
|
|
||||||
if (savedRev.revNum <= newRevHead) {
|
|
||||||
newSavedRevisions.push(savedRev);
|
|
||||||
console.log("Added: Saved Revision: " + savedRev.revNum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newPad.savedRevisions = newSavedRevisions;
|
|
||||||
callback();
|
|
||||||
}, function(callback) {
|
|
||||||
// Save the source pad
|
|
||||||
db.db.set("pad:"+newPadId, newPad, function(err) {
|
|
||||||
console.log("Created: Source Pad: pad:" + newPadId);
|
|
||||||
newPad.saveToDatabase();
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
], function (err) {
|
|
||||||
if(err) throw err;
|
|
||||||
else {
|
|
||||||
console.info("finished");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,106 +0,0 @@
|
||||||
/*
|
|
||||||
This is a repair tool. It extracts all datas of a pad, removes and inserts them again.
|
|
||||||
*/
|
|
||||||
|
|
||||||
console.warn("WARNING: This script must not be used while etherpad is running!");
|
|
||||||
|
|
||||||
if(process.argv.length != 3)
|
|
||||||
{
|
|
||||||
console.error("Use: node bin/repairPad.js $PADID");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
//get the padID
|
|
||||||
var padId = process.argv[2];
|
|
||||||
|
|
||||||
var db, padManager, pad, settings;
|
|
||||||
var neededDBValues = ["pad:"+padId];
|
|
||||||
|
|
||||||
var npm = require("../src/node_modules/npm");
|
|
||||||
var async = require("../src/node_modules/async");
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
// load npm
|
|
||||||
function(callback) {
|
|
||||||
npm.load({}, function(er) {
|
|
||||||
if(er)
|
|
||||||
{
|
|
||||||
console.error("Could not load NPM: " + er)
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// load modules
|
|
||||||
function(callback) {
|
|
||||||
settings = require('../src/node/utils/Settings');
|
|
||||||
db = require('../src/node/db/DB');
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
//initialize the database
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
db.init(callback);
|
|
||||||
},
|
|
||||||
//get the pad
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
padManager = require('../src/node/db/PadManager');
|
|
||||||
|
|
||||||
padManager.getPad(padId, function(err, _pad)
|
|
||||||
{
|
|
||||||
pad = _pad;
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function (callback)
|
|
||||||
{
|
|
||||||
//add all authors
|
|
||||||
var authors = pad.getAllAuthors();
|
|
||||||
for(var i=0;i<authors.length;i++)
|
|
||||||
{
|
|
||||||
neededDBValues.push("globalAuthor:" + authors[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
//add all revisions
|
|
||||||
var revHead = pad.head;
|
|
||||||
for(var i=0;i<=revHead;i++)
|
|
||||||
{
|
|
||||||
neededDBValues.push("pad:"+padId+":revs:" + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
//get all chat values
|
|
||||||
var chatHead = pad.chatHead;
|
|
||||||
for(var i=0;i<=chatHead;i++)
|
|
||||||
{
|
|
||||||
neededDBValues.push("pad:"+padId+":chat:" + i);
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
function (callback) {
|
|
||||||
db = db.db;
|
|
||||||
neededDBValues.forEach(function(key, value) {
|
|
||||||
console.debug("Key: "+key+", value: "+value);
|
|
||||||
db.remove(key);
|
|
||||||
db.set(key, value);
|
|
||||||
});
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
], function (err)
|
|
||||||
{
|
|
||||||
if(err) throw err;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.info("finished");
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//get the pad object
|
|
||||||
//get all revisions of this pad
|
|
||||||
//get all authors related to this pad
|
|
||||||
//get the readonly link related to this pad
|
|
||||||
//get the chat entries related to this pad
|
|
||||||
//remove all keys from database and insert them again
|
|
|
@ -1,39 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#Move to the folder where ep-lite is installed
|
|
||||||
cd `dirname $0`
|
|
||||||
|
|
||||||
#Was this script started in the bin folder? if yes move out
|
|
||||||
if [ -d "../bin" ]; then
|
|
||||||
cd "../"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ignoreRoot=0
|
|
||||||
for ARG in $*
|
|
||||||
do
|
|
||||||
if [ "$ARG" = "--root" ]; then
|
|
||||||
ignoreRoot=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
#Stop the script if it's started as root
|
|
||||||
if [ "$(id -u)" -eq 0 ] && [ $ignoreRoot -eq 0 ]; then
|
|
||||||
echo "You shouldn't start Etherpad as root!"
|
|
||||||
echo "Please type 'Etherpad rocks my socks' or supply the '--root' argument if you still want to start it as root"
|
|
||||||
read rocks
|
|
||||||
if [ ! "$rocks" == "Etherpad rocks my socks" ]
|
|
||||||
then
|
|
||||||
echo "Your input was incorrect"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Prepare the environment
|
|
||||||
bin/installDeps.sh $* || exit 1
|
|
||||||
|
|
||||||
#Move to the node folder and start
|
|
||||||
echo "Started Etherpad..."
|
|
||||||
|
|
||||||
SCRIPTPATH=`pwd -P`
|
|
||||||
exec node "$SCRIPTPATH/node_modules/ep_etherpad-lite/node/server.js" $*
|
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#This script ensures that ep-lite is automatically restarting after an error happens
|
|
||||||
|
|
||||||
#Handling Errors
|
|
||||||
# 0 silent
|
|
||||||
# 1 email
|
|
||||||
ERROR_HANDLING=0
|
|
||||||
# Your email address which should receive the error messages
|
|
||||||
EMAIL_ADDRESS="no-reply@example.com"
|
|
||||||
# Sets the minimum amount of time between the sending of error emails.
|
|
||||||
# This ensures you do not get spammed during an endless reboot loop
|
|
||||||
# It's the time in seconds
|
|
||||||
TIME_BETWEEN_EMAILS=600 # 10 minutes
|
|
||||||
|
|
||||||
# DON'T EDIT AFTER THIS LINE
|
|
||||||
|
|
||||||
LAST_EMAIL_SEND=0
|
|
||||||
LOG="$1"
|
|
||||||
|
|
||||||
#Move to the folder where ep-lite is installed
|
|
||||||
cd `dirname $0`
|
|
||||||
|
|
||||||
#Was this script started in the bin folder? if yes move out
|
|
||||||
if [ -d "../bin" ]; then
|
|
||||||
cd "../"
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Check if a logfile parameter is set
|
|
||||||
if [ -z "${LOG}" ]; then
|
|
||||||
echo "Set a logfile as the first parameter"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
shift
|
|
||||||
while [ 1 ]
|
|
||||||
do
|
|
||||||
#Try to touch the file if it doesn't exist
|
|
||||||
if [ ! -f ${LOG} ]; then
|
|
||||||
touch ${LOG} || ( echo "Logfile '${LOG}' is not writeable" && exit 1 )
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Check if the file is writeable
|
|
||||||
if [ ! -w ${LOG} ]; then
|
|
||||||
echo "Logfile '${LOG}' is not writeable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
#Start the application
|
|
||||||
bin/run.sh $@ >>${LOG} 2>>${LOG}
|
|
||||||
|
|
||||||
#Send email
|
|
||||||
if [ $ERROR_HANDLING = 1 ]; then
|
|
||||||
TIME_NOW=$(date +%s)
|
|
||||||
TIME_SINCE_LAST_SEND=$(($TIME_NOW - $LAST_EMAIL_SEND))
|
|
||||||
|
|
||||||
if [ $TIME_SINCE_LAST_SEND -gt $TIME_BETWEEN_EMAILS ]; then
|
|
||||||
printf "Server was restarted at: $(date)\nThe last 50 lines of the log before the error happens:\n $(tail -n 50 ${LOG})" | mail -s "Pad Server was restarted" $EMAIL_ADDRESS
|
|
||||||
|
|
||||||
LAST_EMAIL_SEND=$TIME_NOW
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "RESTART!" >>${LOG}
|
|
||||||
|
|
||||||
#Sleep 10 seconds before restart
|
|
||||||
sleep 10
|
|
||||||
done
|
|
|
@ -1,20 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#Move to the folder where ep-lite is installed
|
|
||||||
cd `dirname $0`
|
|
||||||
|
|
||||||
#Was this script started in the bin folder? if yes move out
|
|
||||||
if [ -d "../bin" ]; then
|
|
||||||
cd "../"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# npm outdated --depth=0 | grep -v "^Package" | awk '{print $1}' | xargs npm install $1 --save-dev
|
|
||||||
OUTDATED=`npm outdated --depth=0 | grep -v "^Package" | awk '{print $1}'`
|
|
||||||
# echo $OUTDATED
|
|
||||||
if test -n "$OUTDATED"; then
|
|
||||||
echo "Plugins require update, doing this now..."
|
|
||||||
echo "Updating $OUTDATED"
|
|
||||||
npm install $OUTDATED --save-dev
|
|
||||||
else
|
|
||||||
echo "Plugins are all up to date"
|
|
||||||
fi
|
|
|
@ -1,10 +0,0 @@
|
||||||
@include embed_parameters
|
|
||||||
@include http_api
|
|
||||||
@include hooks_overview
|
|
||||||
@include hooks_client-side
|
|
||||||
@include hooks_server-side
|
|
||||||
@include editorInfo
|
|
||||||
@include changeset_library
|
|
||||||
@include pluginfw
|
|
||||||
@include toolbar
|
|
||||||
@include editbar
|
|
|
@ -1,151 +0,0 @@
|
||||||
# Changeset Library
|
|
||||||
|
|
||||||
```
|
|
||||||
"Z:z>1|2=m=b*0|1+1$\n"
|
|
||||||
```
|
|
||||||
|
|
||||||
This is a Changeset. It's just a string and it's very difficult to read in this form. But the Changeset Library gives us some tools to read it.
|
|
||||||
|
|
||||||
A changeset describes the diff between two revisions of the document. The Browser sends changesets to the server and the server sends them to the clients to update them. These Changesets also get saved into the history of a pad. This allows us to go back to every revision from the past.
|
|
||||||
|
|
||||||
## Changeset.unpack(changeset)
|
|
||||||
|
|
||||||
* `changeset` {String}
|
|
||||||
|
|
||||||
This function returns an object representation of the changeset, similar to this:
|
|
||||||
|
|
||||||
```
|
|
||||||
{ oldLen: 35, newLen: 36, ops: '|2=m=b*0|1+1', charBank: '\n' }
|
|
||||||
```
|
|
||||||
|
|
||||||
* `oldLen` {Number} the original length of the document.
|
|
||||||
* `newLen` {Number} the length of the document after the changeset is applied.
|
|
||||||
* `ops` {String} the actual changes, introduced by this changeset.
|
|
||||||
* `charBank` {String} All characters that are added by this changeset.
|
|
||||||
|
|
||||||
## Changeset.opIterator(ops)
|
|
||||||
|
|
||||||
* `ops` {String} The operators, returned by `Changeset.unpack()`
|
|
||||||
|
|
||||||
Returns an operator iterator. This iterator allows us to iterate over all operators that are in the changeset.
|
|
||||||
|
|
||||||
You can iterate with an opIterator using its `next()` and `hasNext()` methods. Next returns the `next()` operator object and `hasNext()` indicates, whether there are any operators left.
|
|
||||||
|
|
||||||
## The Operator object
|
|
||||||
There are 3 types of operators: `+`,`-` and `=`. These operators describe different changes to the document, beginning with the first character of the document. A `=` operator doesn't change the text, but it may add or remove text attributes. A `-` operator removes text. And a `+` Operator adds text and optionally adds some attributes to it.
|
|
||||||
|
|
||||||
* `opcode` {String} the operator type
|
|
||||||
* `chars` {Number} the length of the text changed by this operator.
|
|
||||||
* `lines` {Number} the number of lines changed by this operator.
|
|
||||||
* `attribs` {attribs} attributes set on this text.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```
|
|
||||||
{ opcode: '+',
|
|
||||||
chars: 1,
|
|
||||||
lines: 1,
|
|
||||||
attribs: '*0' }
|
|
||||||
```
|
|
||||||
|
|
||||||
## APool
|
|
||||||
|
|
||||||
```
|
|
||||||
> var AttributePoolFactory = require("./utils/AttributePoolFactory");
|
|
||||||
> var apool = AttributePoolFactory.createAttributePool();
|
|
||||||
> console.log(apool)
|
|
||||||
{ numToAttrib: {},
|
|
||||||
attribToNum: {},
|
|
||||||
nextNum: 0,
|
|
||||||
putAttrib: [Function],
|
|
||||||
getAttrib: [Function],
|
|
||||||
getAttribKey: [Function],
|
|
||||||
getAttribValue: [Function],
|
|
||||||
eachAttrib: [Function],
|
|
||||||
toJsonable: [Function],
|
|
||||||
fromJsonable: [Function] }
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates an empty apool. An apool saves which attributes were used during the history of a pad. There is one apool for each pad. It only saves the attributes that were really used, it doesn't save unused attributes. Let's fill this apool with some values
|
|
||||||
|
|
||||||
```
|
|
||||||
> apool.fromJsonable({"numToAttrib":{"0":["author","a.kVnWeomPADAT2pn9"],"1":["bold","true"],"2":["italic","true"]},"nextNum":3});
|
|
||||||
> console.log(apool)
|
|
||||||
{ numToAttrib:
|
|
||||||
{ '0': [ 'author', 'a.kVnWeomPADAT2pn9' ],
|
|
||||||
'1': [ 'bold', 'true' ],
|
|
||||||
'2': [ 'italic', 'true' ] },
|
|
||||||
attribToNum:
|
|
||||||
{ 'author,a.kVnWeomPADAT2pn9': 0,
|
|
||||||
'bold,true': 1,
|
|
||||||
'italic,true': 2 },
|
|
||||||
nextNum: 3,
|
|
||||||
putAttrib: [Function],
|
|
||||||
getAttrib: [Function],
|
|
||||||
getAttribKey: [Function],
|
|
||||||
getAttribValue: [Function],
|
|
||||||
eachAttrib: [Function],
|
|
||||||
toJsonable: [Function],
|
|
||||||
fromJsonable: [Function] }
|
|
||||||
```
|
|
||||||
|
|
||||||
We used the fromJsonable function to fill the empty apool with values. the fromJsonable and toJsonable functions are used to serialize and deserialize an apool. You can see that it stores the relation between numbers and attributes. So for example the attribute 1 is the attribute bold and vise versa. An attribute is always a key value pair. For stuff like bold and italic it's just 'italic':'true'. For authors it's author:$AUTHORID. So a character can be bold and italic. But it can't belong to multiple authors
|
|
||||||
|
|
||||||
```
|
|
||||||
> apool.getAttrib(1)
|
|
||||||
[ 'bold', 'true' ]
|
|
||||||
```
|
|
||||||
|
|
||||||
Simple example of how to get the key value pair for the attribute 1
|
|
||||||
|
|
||||||
## AText
|
|
||||||
|
|
||||||
```
|
|
||||||
> var atext = {"text":"bold text\nitalic text\nnormal text\n\n","attribs":"*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2"};
|
|
||||||
> console.log(atext)
|
|
||||||
{ text: 'bold text\nitalic text\nnormal text\n\n',
|
|
||||||
attribs: '*0*1+9*0|1+1*0*1*2+b|1+1*0+b|2+2' }
|
|
||||||
```
|
|
||||||
|
|
||||||
This is an atext. An atext has two parts: text and attribs. The text is just the text of the pad as a string. We will look closer at the attribs at the next steps
|
|
||||||
|
|
||||||
```
|
|
||||||
> var opiterator = Changeset.opIterator(atext.attribs)
|
|
||||||
> console.log(opiterator)
|
|
||||||
{ next: [Function: next],
|
|
||||||
hasNext: [Function: hasNext],
|
|
||||||
lastIndex: [Function: lastIndex] }
|
|
||||||
> opiterator.next()
|
|
||||||
{ opcode: '+',
|
|
||||||
chars: 9,
|
|
||||||
lines: 0,
|
|
||||||
attribs: '*0*1' }
|
|
||||||
> opiterator.next()
|
|
||||||
{ opcode: '+',
|
|
||||||
chars: 1,
|
|
||||||
lines: 1,
|
|
||||||
attribs: '*0' }
|
|
||||||
> opiterator.next()
|
|
||||||
{ opcode: '+',
|
|
||||||
chars: 11,
|
|
||||||
lines: 0,
|
|
||||||
attribs: '*0*1*2' }
|
|
||||||
> opiterator.next()
|
|
||||||
{ opcode: '+',
|
|
||||||
chars: 1,
|
|
||||||
lines: 1,
|
|
||||||
attribs: '' }
|
|
||||||
> opiterator.next()
|
|
||||||
{ opcode: '+',
|
|
||||||
chars: 11,
|
|
||||||
lines: 0,
|
|
||||||
attribs: '*0' }
|
|
||||||
> opiterator.next()
|
|
||||||
{ opcode: '+',
|
|
||||||
chars: 2,
|
|
||||||
lines: 2,
|
|
||||||
attribs: '' }
|
|
||||||
```
|
|
||||||
|
|
||||||
The attribs are again a bunch of operators like .ops in the changeset was. But these operators are only + operators. They describe which part of the text has which attributes
|
|
||||||
|
|
||||||
For more information see /doc/easysync/easysync-notes.txt in the source.
|
|
|
@ -1,28 +0,0 @@
|
||||||
# Editbar
|
|
||||||
src/static/js/pad_editbar.js
|
|
||||||
|
|
||||||
## isEnabled()
|
|
||||||
|
|
||||||
## disable()
|
|
||||||
|
|
||||||
## toggleDropDown(dropdown, callback)
|
|
||||||
Shows the dropdown `div.popup` whose `id` equals `dropdown`.
|
|
||||||
|
|
||||||
## registerCommand(cmd, callback)
|
|
||||||
Register a handler for a specific command. Commands are fired if the corresponding button is clicked or the corresponding select is changed.
|
|
||||||
|
|
||||||
## registerAceCommand(cmd, callback)
|
|
||||||
Creates an ace callstack and calls the callback with an ace instance (and a toolbar item, if applicable): `callback(cmd, ace, item)`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
toolbar.registerAceCommand("insertorderedlist", function (cmd, ace) {
|
|
||||||
ace.ace_doInsertOrderedList();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## registerDropdownCommand(cmd, dropdown)
|
|
||||||
Ties a `div.popup` where `id` equals `dropdown` to a `command` fired by clicking a button.
|
|
||||||
|
|
||||||
## triggerCommand(cmd[, item])
|
|
||||||
Triggers a command (optionally with some internal representation of the toolbar item that triggered it).
|
|
|
@ -1,83 +0,0 @@
|
||||||
# editorInfo
|
|
||||||
|
|
||||||
## editorInfo.ace_replaceRange(start, end, text)
|
|
||||||
This function replaces a range (from `start` to `end`) with `text`.
|
|
||||||
|
|
||||||
## editorInfo.ace_getRep()
|
|
||||||
Returns the `rep` object.
|
|
||||||
|
|
||||||
## editorInfo.ace_getAuthor()
|
|
||||||
## editorInfo.ace_inCallStack()
|
|
||||||
## editorInfo.ace_inCallStackIfNecessary(?)
|
|
||||||
## editorInfo.ace_focus(?)
|
|
||||||
## editorInfo.ace_importText(?)
|
|
||||||
## editorInfo.ace_importAText(?)
|
|
||||||
## editorInfo.ace_exportText(?)
|
|
||||||
## editorInfo.ace_editorChangedSize(?)
|
|
||||||
## editorInfo.ace_setOnKeyPress(?)
|
|
||||||
## editorInfo.ace_setOnKeyDown(?)
|
|
||||||
## editorInfo.ace_setNotifyDirty(?)
|
|
||||||
## editorInfo.ace_dispose(?)
|
|
||||||
## editorInfo.ace_getFormattedCode(?)
|
|
||||||
## editorInfo.ace_setEditable(bool)
|
|
||||||
## editorInfo.ace_execCommand(?)
|
|
||||||
## editorInfo.ace_callWithAce(fn, callStack, normalize)
|
|
||||||
## editorInfo.ace_setProperty(key, value)
|
|
||||||
## editorInfo.ace_setBaseText(txt)
|
|
||||||
## editorInfo.ace_setBaseAttributedText(atxt, apoolJsonObj)
|
|
||||||
## editorInfo.ace_applyChangesToBase(c, optAuthor, apoolJsonObj)
|
|
||||||
## editorInfo.ace_prepareUserChangeset()
|
|
||||||
## editorInfo.ace_applyPreparedChangesetToBase()
|
|
||||||
## editorInfo.ace_setUserChangeNotificationCallback(f)
|
|
||||||
## editorInfo.ace_setAuthorInfo(author, info)
|
|
||||||
## editorInfo.ace_setAuthorSelectionRange(author, start, end)
|
|
||||||
## editorInfo.ace_getUnhandledErrors()
|
|
||||||
## editorInfo.ace_getDebugProperty(prop)
|
|
||||||
## editorInfo.ace_fastIncorp(?)
|
|
||||||
## editorInfo.ace_isCaret(?)
|
|
||||||
## editorInfo.ace_getLineAndCharForPoint(?)
|
|
||||||
## editorInfo.ace_performDocumentApplyAttributesToCharRange(?)
|
|
||||||
## editorInfo.ace_setAttributeOnSelection(attribute, enabled)
|
|
||||||
Sets an attribute on current range.
|
|
||||||
Example: `call.editorInfo.ace_setAttributeOnSelection("turkey::balls", true); // turkey is the attribute here, balls is the value
|
|
||||||
Notes: to remove the attribute pass enabled as false
|
|
||||||
## editorInfo.ace_toggleAttributeOnSelection(?)
|
|
||||||
## editorInfo.ace_getAttributeOnSelection(attribute, prevChar)
|
|
||||||
Returns a boolean if an attribute exists on a selected range.
|
|
||||||
prevChar value should be true if you want to get the previous Character attribute instead of the current selection for example
|
|
||||||
if the caret is at position 0,1 (after first character) it's probable you want the attributes on the character at 0,0
|
|
||||||
The attribute should be the string name of the attribute applied to the selection IE subscript
|
|
||||||
Example usage: Apply the activeButton Class to a button if an attribute is on a highlighted/selected caret position or range.
|
|
||||||
Example `var isItThere = documentAttributeManager.getAttributeOnSelection("turkey::balls", true);`
|
|
||||||
|
|
||||||
See the ep_subscript plugin for an example of this function in action.
|
|
||||||
Notes: Does not work on first or last character of a line. Suffers from a race condition if called with aceEditEvent.
|
|
||||||
## editorInfo.ace_performSelectionChange(?)
|
|
||||||
## editorInfo.ace_doIndentOutdent(?)
|
|
||||||
## editorInfo.ace_doUndoRedo(?)
|
|
||||||
## editorInfo.ace_doInsertUnorderedList(?)
|
|
||||||
## editorInfo.ace_doInsertOrderedList(?)
|
|
||||||
## editorInfo.ace_performDocumentApplyAttributesToRange()
|
|
||||||
|
|
||||||
## editorInfo.ace_getAuthorInfos()
|
|
||||||
Returns an info object about the author. Object key = author_id and info includes author's bg color value.
|
|
||||||
Use to define your own authorship.
|
|
||||||
## editorInfo.ace_performDocumentReplaceRange(start, end, newText)
|
|
||||||
This function replaces a range (from [x1,y1] to [x2,y2]) with `newText`.
|
|
||||||
## editorInfo.ace_performDocumentReplaceCharRange(startChar, endChar, newText)
|
|
||||||
This function replaces a range (from y1 to y2) with `newText`.
|
|
||||||
## editorInfo.ace_renumberList(lineNum)
|
|
||||||
If you delete a line, calling this method will fix the line numbering.
|
|
||||||
## editorInfo.ace_doReturnKey()
|
|
||||||
Forces a return key at the current caret position.
|
|
||||||
## editorInfo.ace_isBlockElement(element)
|
|
||||||
Returns true if your passed element is registered as a block element
|
|
||||||
## editorInfo.ace_getLineListType(lineNum)
|
|
||||||
Returns the line's html list type.
|
|
||||||
## editorInfo.ace_caretLine()
|
|
||||||
Returns X position of the caret.
|
|
||||||
## editorInfo.ace_caretColumn()
|
|
||||||
Returns Y position of the caret.
|
|
||||||
## editorInfo.ace_caretDocChar()
|
|
||||||
Returns the Y offset starting from [x=0,y=0]
|
|
||||||
## editorInfo.ace_isWordChar(?)
|
|
|
@ -1,68 +0,0 @@
|
||||||
# Embed parameters
|
|
||||||
You can easily embed your etherpad-lite into any webpage by using iframes. You can configure the embedded pad using embed parameters.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
Cut and paste the following code into any webpage to embed a pad. The parameters below will hide the chat and the line numbers.
|
|
||||||
|
|
||||||
```
|
|
||||||
<iframe src='http://pad.test.de/p/PAD_NAME?showChat=false&showLineNumbers=false' width=600 height=400></iframe>
|
|
||||||
```
|
|
||||||
|
|
||||||
## showLineNumbers
|
|
||||||
* Boolean
|
|
||||||
|
|
||||||
Default: true
|
|
||||||
|
|
||||||
## showControls
|
|
||||||
* Boolean
|
|
||||||
|
|
||||||
Default: true
|
|
||||||
|
|
||||||
## showChat
|
|
||||||
* Boolean
|
|
||||||
|
|
||||||
Default: true
|
|
||||||
|
|
||||||
## useMonospaceFont
|
|
||||||
* Boolean
|
|
||||||
|
|
||||||
Default: false
|
|
||||||
|
|
||||||
## userName
|
|
||||||
* String
|
|
||||||
|
|
||||||
Default: "unnamed"
|
|
||||||
|
|
||||||
Example: `userName=Etherpad%20User`
|
|
||||||
|
|
||||||
## userColor
|
|
||||||
* String (css hex color value)
|
|
||||||
|
|
||||||
Default: randomly chosen by pad server
|
|
||||||
|
|
||||||
Example: `userColor=%23ff9900`
|
|
||||||
|
|
||||||
## noColors
|
|
||||||
* Boolean
|
|
||||||
|
|
||||||
Default: false
|
|
||||||
|
|
||||||
## alwaysShowChat
|
|
||||||
* Boolean
|
|
||||||
|
|
||||||
Default: false
|
|
||||||
|
|
||||||
## lang
|
|
||||||
* String
|
|
||||||
|
|
||||||
Default: en
|
|
||||||
|
|
||||||
Example: `lang=ar` (translates the interface into Arabic)
|
|
||||||
|
|
||||||
## rtl
|
|
||||||
* Boolean
|
|
||||||
|
|
||||||
Default: true
|
|
||||||
Displays pad text from right to left.
|
|
||||||
|
|
|
@ -1,378 +0,0 @@
|
||||||
# Client-side hooks
|
|
||||||
Most of these hooks are called during or in order to set up the formatting process.
|
|
||||||
|
|
||||||
## documentReady
|
|
||||||
Called from: src/templates/pad.html
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
nothing
|
|
||||||
|
|
||||||
This hook proxies the functionality of jQuery's `$(document).ready` event.
|
|
||||||
|
|
||||||
## aceDomLinePreProcessLineAttributes
|
|
||||||
Called from: src/static/js/domline.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. domline - The current DOM line being processed
|
|
||||||
2. cls - The class of the current block element (useful for styling)
|
|
||||||
|
|
||||||
This hook is called for elements in the DOM that have the "lineMarkerAttribute" set. You can add elements into this category with the aceRegisterBlockElements hook above. This hook is run BEFORE the numbered and ordered lists logic is applied.
|
|
||||||
|
|
||||||
The return value of this hook should have the following structure:
|
|
||||||
|
|
||||||
`{ preHtml: String, postHtml: String, processedMarker: Boolean }`
|
|
||||||
|
|
||||||
The preHtml and postHtml values will be added to the HTML display of the element, and if processedMarker is true, the engine won't try to process it any more.
|
|
||||||
|
|
||||||
## aceDomLineProcessLineAttributes
|
|
||||||
Called from: src/static/js/domline.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. domline - The current DOM line being processed
|
|
||||||
2. cls - The class of the current block element (useful for styling)
|
|
||||||
|
|
||||||
This hook is called for elements in the DOM that have the "lineMarkerAttribute" set. You can add elements into this category with the aceRegisterBlockElements hook above. This hook is run AFTER the ordered and numbered lists logic is applied.
|
|
||||||
|
|
||||||
The return value of this hook should have the following structure:
|
|
||||||
|
|
||||||
`{ preHtml: String, postHtml: String, processedMarker: Boolean }`
|
|
||||||
|
|
||||||
The preHtml and postHtml values will be added to the HTML display of the element, and if processedMarker is true, the engine won't try to process it any more.
|
|
||||||
|
|
||||||
## aceCreateDomLine
|
|
||||||
Called from: src/static/js/domline.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. domline - the current DOM line being processed
|
|
||||||
2. cls - The class of the current element (useful for styling)
|
|
||||||
|
|
||||||
This hook is called for any line being processed by the formatting engine, unless the aceDomLineProcessLineAttributes hook from above returned true, in which case this hook is skipped.
|
|
||||||
|
|
||||||
The return value of this hook should have the following structure:
|
|
||||||
|
|
||||||
`{ extraOpenTags: String, extraCloseTags: String, cls: String }`
|
|
||||||
|
|
||||||
extraOpenTags and extraCloseTags will be added before and after the element in question, and cls will be the new class of the element going forward.
|
|
||||||
|
|
||||||
## acePostWriteDomLineHTML
|
|
||||||
Called from: src/static/js/domline.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. node - the DOM node that just got written to the page
|
|
||||||
|
|
||||||
This hook is for right after a node has been fully formatted and written to the page.
|
|
||||||
|
|
||||||
## aceAttribsToClasses
|
|
||||||
Called from: src/static/js/linestylefilter.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. linestylefilter - the JavaScript object that's currently processing the ace attributes
|
|
||||||
2. key - the current attribute being processed
|
|
||||||
3. value - the value of the attribute being processed
|
|
||||||
|
|
||||||
This hook is called during the attribute processing procedure, and should be used to translate key, value pairs into valid HTML classes that can be inserted into the DOM.
|
|
||||||
|
|
||||||
The return value for this function should be a list of classes, which will then be parsed into a valid class string.
|
|
||||||
|
|
||||||
## aceAttribClasses
|
|
||||||
Called from: src/static/js/linestylefilter.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
1. Attributes - Object of Attributes
|
|
||||||
|
|
||||||
This hook is called when attributes are investigated on a line. It is useful if you want to add another attribute type or property type to a pad.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
exports.aceAttribClasses = function(hook_name, attr, cb){
|
|
||||||
attr.sub = 'tag:sub';
|
|
||||||
cb(attr);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## aceGetFilterStack
|
|
||||||
Called from: src/static/js/linestylefilter.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. linestylefilter - the JavaScript object that's currently processing the ace attributes
|
|
||||||
2. browser - an object indicating which browser is accessing the page
|
|
||||||
|
|
||||||
This hook is called to apply custom regular expression filters to a set of styles. The one example available is the ep_linkify plugin, which adds internal links. They use it to find the telltale `[[ ]]` syntax that signifies internal links, and finding that syntax, they add in the internalHref attribute to be later used by the aceCreateDomLine hook (documented above).
|
|
||||||
|
|
||||||
## aceEditorCSS
|
|
||||||
Called from: src/static/js/ace.js
|
|
||||||
|
|
||||||
Things in context: None
|
|
||||||
|
|
||||||
This hook is provided to allow custom CSS files to be loaded. The return value should be an array of resource urls or paths relative to the plugins directory.
|
|
||||||
|
|
||||||
## aceInitInnerdocbodyHead
|
|
||||||
Called from: src/static/js/ace.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. iframeHTML - the HTML of the editor iframe up to this point, in array format
|
|
||||||
|
|
||||||
This hook is called during the creation of the editor HTML. The array should have lines of HTML added to it, giving the plugin author a chance to add in meta, script, link, and other tags that go into the `<head>` element of the editor HTML document.
|
|
||||||
|
|
||||||
## aceEditEvent
|
|
||||||
Called from: src/static/js/ace2_inner.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. callstack - a bunch of information about the current action
|
|
||||||
2. editorInfo - information about the user who is making the change
|
|
||||||
3. rep - information about where the change is being made
|
|
||||||
4. documentAttributeManager - information about attributes in the document (this is a mystery to me)
|
|
||||||
|
|
||||||
This hook is made available to edit the edit events that might occur when changes are made. Currently you can change the editor information, some of the meanings of the edit, and so on. You can also make internal changes (internal to your plugin) that use the information provided by the edit event.
|
|
||||||
|
|
||||||
## aceRegisterNonScrollableEditEvents
|
|
||||||
Called from: src/static/js/ace2_inner.js
|
|
||||||
|
|
||||||
Things in context: None
|
|
||||||
|
|
||||||
When aceEditEvent (documented above) finishes processing the event, it scrolls the viewport to make caret visible to the user, but if you don't want that behavior to happen you can use this hook to register which edit events should not scroll viewport. The return value of this hook should be a list of event names.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
exports.aceRegisterNonScrollableEditEvents = function(){
|
|
||||||
return [ 'repaginate', 'updatePageCount' ];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## aceRegisterBlockElements
|
|
||||||
Called from: src/static/js/ace2_inner.js
|
|
||||||
|
|
||||||
Things in context: None
|
|
||||||
|
|
||||||
The return value of this hook will add elements into the "lineMarkerAttribute" category, making the aceDomLineProcessLineAttributes hook (documented below) call for those elements.
|
|
||||||
|
|
||||||
## aceInitialized
|
|
||||||
Called from: src/static/js/ace2_inner.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. editorInfo - information about the user who will be making changes through the interface, and a way to insert functions into the main ace object (see ep_headings)
|
|
||||||
2. rep - information about where the user's cursor is
|
|
||||||
3. documentAttributeManager - some kind of magic
|
|
||||||
|
|
||||||
This hook is for inserting further information into the ace engine, for later use in formatting hooks.
|
|
||||||
|
|
||||||
## postAceInit
|
|
||||||
Called from: src/static/js/pad.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. ace - the ace object that is applied to this editor.
|
|
||||||
2. pad - the pad object of the current pad.
|
|
||||||
|
|
||||||
## postToolbarInit
|
|
||||||
Called from: src/static/js/pad_editbar.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. ace - the ace object that is applied to this editor.
|
|
||||||
2. toolbar - Editbar instance. See below for the Editbar documentation.
|
|
||||||
|
|
||||||
Can be used to register custom actions to the toolbar.
|
|
||||||
|
|
||||||
Usage examples:
|
|
||||||
|
|
||||||
* [https://github.com/tiblu/ep_authorship_toggle]()
|
|
||||||
|
|
||||||
## postTimesliderInit
|
|
||||||
Called from: src/static/js/timeslider.js
|
|
||||||
|
|
||||||
There doesn't appear to be any example available of this particular hook being used, but it gets fired after the timeslider is all set up.
|
|
||||||
|
|
||||||
## userJoinOrUpdate
|
|
||||||
Called from: src/static/js/pad_userlist.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. info - the user information
|
|
||||||
|
|
||||||
This hook is called on the client side whenever a user joins or changes. This can be used to create notifications or an alternate user list.
|
|
||||||
|
|
||||||
## chatNewMessage
|
|
||||||
Called from: src/static/js/chat.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. authorName - The user that wrote this message
|
|
||||||
2. author - The authorID of the user that wrote the message
|
|
||||||
2. text - the message text
|
|
||||||
3. sticky (boolean) - if you want the gritter notification bubble to fade out on its own or just sit there
|
|
||||||
3. timestamp - the timestamp of the chat message
|
|
||||||
4. timeStr - the timestamp as a formatted string
|
|
||||||
|
|
||||||
This hook is called on the client side whenever a chat message is received from the server. It can be used to create different notifications for chat messages.
|
|
||||||
|
|
||||||
## collectContentPre
|
|
||||||
Called from: src/static/js/contentcollector.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. cc - the contentcollector object
|
|
||||||
2. state - the current state of the change being made
|
|
||||||
3. tname - the tag name of this node currently being processed
|
|
||||||
4. styl - the style applied to the node (probably CSS) -- Note the typo
|
|
||||||
5. cls - the HTML class string of the node
|
|
||||||
|
|
||||||
This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original.
|
|
||||||
|
|
||||||
E.g. if you need to apply an attribute to newly inserted characters,
|
|
||||||
call cc.doAttrib(state, "attributeName") which results in an attribute attributeName=true.
|
|
||||||
|
|
||||||
If you want to specify also a value, call cc.doAttrib(state, "attributeName::value")
|
|
||||||
which results in an attribute attributeName=value.
|
|
||||||
|
|
||||||
|
|
||||||
## collectContentImage
|
|
||||||
Called from: src/static/js/contentcollector.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. cc - the contentcollector object
|
|
||||||
2. state - the current state of the change being made
|
|
||||||
3. tname - the tag name of this node currently being processed
|
|
||||||
4. style - the style applied to the node (probably CSS)
|
|
||||||
5. cls - the HTML class string of the node
|
|
||||||
6. node - the node being modified
|
|
||||||
|
|
||||||
This hook is called before the content of an image node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
exports.collectContentImage = function(name, context){
|
|
||||||
context.state.lineAttributes.img = context.node.outerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## collectContentPost
|
|
||||||
Called from: src/static/js/contentcollector.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. cc - the contentcollector object
|
|
||||||
2. state - the current state of the change being made
|
|
||||||
3. tname - the tag name of this node currently being processed
|
|
||||||
4. style - the style applied to the node (probably CSS)
|
|
||||||
5. cls - the HTML class string of the node
|
|
||||||
|
|
||||||
This hook is called after the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original.
|
|
||||||
|
|
||||||
## handleClientMessage_`name`
|
|
||||||
Called from: `src/static/js/collab_client.js`
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. payload - the data that got sent with the message (use it for custom message content)
|
|
||||||
|
|
||||||
This hook gets called every time the client receives a message of type `name`. This can most notably be used with the new HTTP API call, "sendClientsMessage", which sends a custom message type to all clients connected to a pad. You can also use this to handle existing types.
|
|
||||||
|
|
||||||
`collab_client.js` has a pretty extensive list of message types, if you want to take a look.
|
|
||||||
|
|
||||||
##aceStartLineAndCharForPoint-aceEndLineAndCharForPoint
|
|
||||||
Called from: src/static/js/ace2_inner.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. callstack - a bunch of information about the current action
|
|
||||||
2. editorInfo - information about the user who is making the change
|
|
||||||
3. rep - information about where the change is being made
|
|
||||||
4. root - the span element of the current line
|
|
||||||
5. point - the starting/ending element where the cursor highlights
|
|
||||||
6. documentAttributeManager - information about attributes in the document
|
|
||||||
|
|
||||||
This hook is provided to allow a plugin to turn DOM node selection into [line,char] selection.
|
|
||||||
The return value should be an array of [line,char]
|
|
||||||
|
|
||||||
##aceKeyEvent
|
|
||||||
Called from: src/static/js/ace2_inner.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. callstack - a bunch of information about the current action
|
|
||||||
2. editorInfo - information about the user who is making the change
|
|
||||||
3. rep - information about where the change is being made
|
|
||||||
4. documentAttributeManager - information about attributes in the document
|
|
||||||
5. evt - the fired event
|
|
||||||
|
|
||||||
This hook is provided to allow a plugin to handle key events.
|
|
||||||
The return value should be true if you have handled the event.
|
|
||||||
|
|
||||||
##collectContentLineText
|
|
||||||
Called from: src/static/js/contentcollector.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. cc - the contentcollector object
|
|
||||||
2. state - the current state of the change being made
|
|
||||||
3. tname - the tag name of this node currently being processed
|
|
||||||
4. text - the text for that line
|
|
||||||
|
|
||||||
This hook allows you to validate/manipulate the text before it's sent to the server side.
|
|
||||||
The return value should be the validated/manipulated text.
|
|
||||||
|
|
||||||
##collectContentLineBreak
|
|
||||||
Called from: src/static/js/contentcollector.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. cc - the contentcollector object
|
|
||||||
2. state - the current state of the change being made
|
|
||||||
3. tname - the tag name of this node currently being processed
|
|
||||||
|
|
||||||
This hook is provided to allow whether the br tag should induce a new magic domline or not.
|
|
||||||
The return value should be either true(break the line) or false.
|
|
||||||
|
|
||||||
##disableAuthorColorsForThisLine
|
|
||||||
Called from: src/static/js/linestylefilter.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. linestylefilter - the JavaScript object that's currently processing the ace attributes
|
|
||||||
2. text - the line text
|
|
||||||
3. class - line class
|
|
||||||
|
|
||||||
This hook is provided to allow whether a given line should be deliniated with multiple authors.
|
|
||||||
Multiple authors in one line cause the creation of magic span lines. This might not suit you and
|
|
||||||
now you can disable it and handle your own deliniation.
|
|
||||||
The return value should be either true(disable) or false.
|
|
||||||
|
|
||||||
## aceSetAuthorStyle
|
|
||||||
Called from: src/static/js/ace2_inner.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. dynamicCSS - css manger for inner ace
|
|
||||||
2. outerDynamicCSS - css manager for outer ace
|
|
||||||
3. parentDynamicCSS - css manager for parent document
|
|
||||||
4. info - author style info
|
|
||||||
5. author - author info
|
|
||||||
6. authorSelector - css selector for author span in inner ace
|
|
||||||
|
|
||||||
This hook is provided to allow author highlight style to be modified.
|
|
||||||
Registered hooks should return 1 if the plugin handles highlighting. If no plugin returns 1, the core will use the default background-based highlighting.
|
|
||||||
|
|
||||||
## aceSelectionChanged
|
|
||||||
Called from: src/static/js/ace2_inner.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. rep - information about where the user's cursor is
|
|
||||||
2. documentAttributeManager - information about attributes in the document
|
|
||||||
|
|
||||||
This hook allows a plugin to react to a cursor or selection change,
|
|
||||||
perhaps to update a UI element based on the style at the cursor location.
|
|
|
@ -1,11 +0,0 @@
|
||||||
# Hooks
|
|
||||||
All hooks are called with two arguments:
|
|
||||||
|
|
||||||
1. name - the name of the hook being called
|
|
||||||
2. context - an object with some relevant information about the context of the call
|
|
||||||
|
|
||||||
## Return values
|
|
||||||
A hook should always return a list or undefined. Returning undefined is equivalent to returning an empty list.
|
|
||||||
All the returned lists are appended to each other, so if the return values where `[1, 2]`, `undefined`, `[3, 4,]`, `undefined` and `[5]`, the value returned by callHook would be `[1, 2, 3, 4, 5]`.
|
|
||||||
|
|
||||||
This is, because it should never matter if you have one plugin or several plugins doing some work - a single plugin should be able to make callHook return the same value a set of plugins are able to return collectively. So, any plugin can return a list of values, of any length, not just one value.
|
|
|
@ -1,455 +0,0 @@
|
||||||
# Server-side hooks
|
|
||||||
These hooks are called on server-side.
|
|
||||||
|
|
||||||
## loadSettings
|
|
||||||
Called from: src/node/server.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. settings - the settings object
|
|
||||||
|
|
||||||
Use this hook to receive the global settings in your plugin.
|
|
||||||
|
|
||||||
## pluginUninstall
|
|
||||||
Called from: src/static/js/pluginfw/installer.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. plugin_name - self-explanatory
|
|
||||||
|
|
||||||
If this hook returns an error, the callback to the uninstall function gets an error as well. This mostly seems useful for handling additional features added in based on the installation of other plugins, which is pretty cool!
|
|
||||||
|
|
||||||
## pluginInstall
|
|
||||||
Called from: src/static/js/pluginfw/installer.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. plugin_name - self-explanatory
|
|
||||||
|
|
||||||
If this hook returns an error, the callback to the install function gets an error, too. This seems useful for adding in features when a particular plugin is installed.
|
|
||||||
|
|
||||||
## init_`<plugin name>`
|
|
||||||
Called from: src/static/js/pluginfw/plugins.js
|
|
||||||
|
|
||||||
Things in context: None
|
|
||||||
|
|
||||||
This function is called after a specific plugin is initialized. This would probably be more useful than the previous two functions if you only wanted to add in features to one specific plugin.
|
|
||||||
|
|
||||||
## expressConfigure
|
|
||||||
Called from: src/node/hooks/express.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. app - the main application object
|
|
||||||
|
|
||||||
This is a helpful hook for changing the behavior and configuration of the application. It's called right after the application gets configured.
|
|
||||||
|
|
||||||
## expressCreateServer
|
|
||||||
Called from: src/node/hooks/express.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. app - the main express application object (helpful for adding new paths and such)
|
|
||||||
2. server - the http server object
|
|
||||||
|
|
||||||
This hook gets called after the application object has been created, but before it starts listening. This is similar to the expressConfigure hook, but it's not guaranteed that the application object will have all relevant configuration variables.
|
|
||||||
|
|
||||||
## eejsBlock_`<name>`
|
|
||||||
Called from: src/node/eejs/index.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. content - the content of the block
|
|
||||||
|
|
||||||
This hook gets called upon the rendering of an ejs template block. For any specific kind of block, you can change how that block gets rendered by modifying the content object passed in.
|
|
||||||
|
|
||||||
Available blocks in `pad.html` are:
|
|
||||||
|
|
||||||
* `htmlHead` - after `<html>` and immediately before the title tag
|
|
||||||
* `styles` - the style `<link>`s
|
|
||||||
* `body` - the contents of the body tag
|
|
||||||
* `editbarMenuLeft` - the left tool bar (consider using the toolbar controller instead of manually adding html here)
|
|
||||||
* `editbarMenuRight` - right tool bar
|
|
||||||
* `afterEditbar` - allows you to add stuff immediately after the toolbar
|
|
||||||
* `userlist` - the contents of the userlist dropdown
|
|
||||||
* `loading` - the initial loading message
|
|
||||||
* `mySettings` - the left column of the settings dropdown ("My view"); intended for adding checkboxes only
|
|
||||||
* `mySettings.dropdowns` - add your dropdown settings here
|
|
||||||
* `globalSettings` - the right column of the settings dropdown ("Global view")
|
|
||||||
* `importColumn` - import form
|
|
||||||
* `exportColumn` - export form
|
|
||||||
* `modals` - Contains all connectivity messages
|
|
||||||
* `embedPopup` - the embed dropdown
|
|
||||||
* `scripts` - Add your script tags here, if you really have to (consider use client-side hooks instead)
|
|
||||||
|
|
||||||
`timeslider.html` blocks:
|
|
||||||
|
|
||||||
* `timesliderStyles`
|
|
||||||
* `timesliderScripts`
|
|
||||||
* `timesliderBody`
|
|
||||||
* `timesliderTop`
|
|
||||||
* `timesliderEditbarRight`
|
|
||||||
* `modals`
|
|
||||||
|
|
||||||
`index.html` blocks:
|
|
||||||
|
|
||||||
* `indexWrapper` - contains the form for creating new pads
|
|
||||||
|
|
||||||
## padInitToolbar
|
|
||||||
Called from: src/node/hooks/express/specialpages.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. toolbar - the toolbar controller that will render the toolbar eventually
|
|
||||||
|
|
||||||
Here you can add custom toolbar items that will be available in the toolbar config in `settings.json`. For more about the toolbar controller see the API section.
|
|
||||||
|
|
||||||
Usage examples:
|
|
||||||
|
|
||||||
* https://github.com/tiblu/ep_authorship_toggle
|
|
||||||
|
|
||||||
## onAccessCheck
|
|
||||||
Called from: src/node/db/SecurityManager.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. padID - the pad the user wants to access
|
|
||||||
2. password - the password the user has given to access the pad
|
|
||||||
3. token - the token of the author
|
|
||||||
4. sessionCookie - the session the use has
|
|
||||||
|
|
||||||
This hook gets called when the access to the concrete pad is being checked. Return `false` to deny access.
|
|
||||||
|
|
||||||
## padCreate
|
|
||||||
Called from: src/node/db/Pad.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. pad - the pad instance
|
|
||||||
2. author - the id of the author who created the pad
|
|
||||||
|
|
||||||
This hook gets called when a new pad was created.
|
|
||||||
|
|
||||||
## padLoad
|
|
||||||
Called from: src/node/db/Pad.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. pad - the pad instance
|
|
||||||
|
|
||||||
This hook gets called when a pad was loaded. If a new pad was created and loaded this event will be emitted too.
|
|
||||||
|
|
||||||
## padUpdate
|
|
||||||
Called from: src/node/db/Pad.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. pad - the pad instance
|
|
||||||
2. author - the id of the author who updated the pad
|
|
||||||
|
|
||||||
This hook gets called when an existing pad was updated.
|
|
||||||
|
|
||||||
## padCopy
|
|
||||||
Called from: src/node/db/Pad.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. originalPad - the source pad instance
|
|
||||||
2. destinationID - the id of the pad copied from originalPad
|
|
||||||
|
|
||||||
This hook gets called when an existing pad was copied.
|
|
||||||
|
|
||||||
Usage examples:
|
|
||||||
|
|
||||||
* https://github.com/ether/ep_comments
|
|
||||||
|
|
||||||
## padRemove
|
|
||||||
Called from: src/node/db/Pad.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. padID
|
|
||||||
|
|
||||||
This hook gets called when an existing pad was removed/deleted.
|
|
||||||
|
|
||||||
Usage examples:
|
|
||||||
|
|
||||||
* https://github.com/ether/ep_comments
|
|
||||||
|
|
||||||
## socketio
|
|
||||||
Called from: src/node/hooks/express/socketio.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. app - the application object
|
|
||||||
2. io - the socketio object
|
|
||||||
3. server - the http server object
|
|
||||||
|
|
||||||
I have no idea what this is useful for, someone else will have to add this description.
|
|
||||||
|
|
||||||
## authorize
|
|
||||||
Called from: src/node/hooks/express/webaccess.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. req - the request object
|
|
||||||
2. res - the response object
|
|
||||||
3. next - ?
|
|
||||||
4. resource - the path being accessed
|
|
||||||
|
|
||||||
This is useful for modifying the way authentication is done, especially for specific paths.
|
|
||||||
|
|
||||||
## authenticate
|
|
||||||
Called from: src/node/hooks/express/webaccess.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. req - the request object
|
|
||||||
2. res - the response object
|
|
||||||
3. next - ?
|
|
||||||
4. username - the username used (optional)
|
|
||||||
5. password - the password used (optional)
|
|
||||||
|
|
||||||
This is useful for modifying the way authentication is done.
|
|
||||||
|
|
||||||
## authFailure
|
|
||||||
Called from: src/node/hooks/express/webaccess.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. req - the request object
|
|
||||||
2. res - the response object
|
|
||||||
3. next - ?
|
|
||||||
|
|
||||||
This is useful for modifying the way authentication is done.
|
|
||||||
|
|
||||||
## handleMessage
|
|
||||||
Called from: src/node/handler/PadMessageHandler.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. message - the message being handled
|
|
||||||
2. client - the client object from socket.io
|
|
||||||
|
|
||||||
This hook will be called once a message arrive. If a plugin calls `callback(null)` the message will be dropped. However, it is not possible to modify the message.
|
|
||||||
|
|
||||||
Plugins may also decide to implement custom behavior once a message arrives.
|
|
||||||
|
|
||||||
**WARNING**: handleMessage will be called, even if the client is not authorized to send this message. It's up to the plugin to check permissions.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
function handleMessage ( hook, context, callback ) {
|
|
||||||
if ( context.message.type == 'USERINFO_UPDATE' ) {
|
|
||||||
// If the message type is USERINFO_UPDATE, drop the message
|
|
||||||
callback(null);
|
|
||||||
}else{
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## handleMessageSecurity
|
|
||||||
Called from: src/node/handler/PadMessageHandler.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. message - the message being handled
|
|
||||||
2. client - the client object from socket.io
|
|
||||||
|
|
||||||
This hook will be called once a message arrives. If a plugin calls `callback(true)` the message will be allowed to be processed. This is especially useful if you want read only pad visitors to update pad contents for whatever reason.
|
|
||||||
|
|
||||||
**WARNING**: handleMessageSecurity will be called, even if the client is not authorized to send this message. It's up to the plugin to check permissions.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
function handleMessageSecurity ( hook, context, callback ) {
|
|
||||||
if ( context.message.boomerang == 'hipster' ) {
|
|
||||||
// If the message boomer is hipster, allow the request
|
|
||||||
callback(true);
|
|
||||||
}else{
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## clientVars
|
|
||||||
Called from: src/node/handler/PadMessageHandler.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. clientVars - the basic `clientVars` built by the core
|
|
||||||
2. pad - the pad this session is about
|
|
||||||
|
|
||||||
This hook will be called once a client connects and the `clientVars` are being sent. Plugins can use this hook to give the client an initial configuration, like the tracking-id of an external analytics-tool that is used on the client-side. You can also overwrite values from the original `clientVars`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
exports.clientVars = function(hook, context, callback)
|
|
||||||
{
|
|
||||||
// tell the client which year we are in
|
|
||||||
return callback({ "currentYear": new Date().getFullYear() });
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
This can be accessed on the client-side using `clientVars.currentYear`.
|
|
||||||
|
|
||||||
## getLineHTMLForExport
|
|
||||||
Called from: src/node/utils/ExportHtml.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. apool - pool object
|
|
||||||
2. attribLine - line attributes
|
|
||||||
3. text - line text
|
|
||||||
|
|
||||||
This hook will allow a plug-in developer to re-write each line when exporting to HTML.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
|
||||||
|
|
||||||
exports.getLineHTMLForExport = function (hook, context) {
|
|
||||||
var header = _analyzeLine(context.attribLine, context.apool);
|
|
||||||
if (header) {
|
|
||||||
return "<" + header + ">" + context.lineContent + "</" + header + ">";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _analyzeLine(alineAttrs, apool) {
|
|
||||||
var header = null;
|
|
||||||
if (alineAttrs) {
|
|
||||||
var opIter = Changeset.opIterator(alineAttrs);
|
|
||||||
if (opIter.hasNext()) {
|
|
||||||
var op = opIter.next();
|
|
||||||
header = Changeset.opAttributeValue(op, 'heading', apool);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## stylesForExport
|
|
||||||
Called from: src/node/utils/ExportHtml.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. padId - The Pad Id
|
|
||||||
|
|
||||||
This hook will allow a plug-in developer to append Styles to the Exported HTML.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
exports.stylesForExport = function(hook, padId, cb){
|
|
||||||
cb("body{font-size:13.37em !important}");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## aceAttribClasses
|
|
||||||
Called from: src/static/js/linestylefilter.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
1. Attributes - Object of Attributes
|
|
||||||
|
|
||||||
This hook is called when attributes are investigated on a line. It is useful if you want to add another attribute type or property type to a pad.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
exports.aceAttribClasses = function(hook_name, attr, cb){
|
|
||||||
attr.sub = 'tag:sub';
|
|
||||||
cb(attr);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## exportFileName
|
|
||||||
Called from src/node/handler/ExportHandler.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. padId
|
|
||||||
|
|
||||||
This hook will allow a plug-in developer to modify the file name of an exported pad. This is useful if you want to export a pad under another name and/or hide the padId under export. Note that the doctype or file extension cannot be modified for security reasons.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
exports.exportFileName = function(hook, padId, callback){
|
|
||||||
callback("newFileName"+padId);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## exportHtmlAdditionalTags
|
|
||||||
Called from src/node/utils/ExportHtml.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. Pad object
|
|
||||||
|
|
||||||
This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. If tags are stored as `['color', 'red']` on the attribute pool, use `exportHtmlAdditionalTagsWithData` instead. An Array should be returned.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
// Add the props to be supported in export
|
|
||||||
exports.exportHtmlAdditionalTags = function(hook, pad, cb){
|
|
||||||
var padId = pad.id;
|
|
||||||
cb(["massive","jugs"]);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## exportHtmlAdditionalTagsWithData
|
|
||||||
Called from src/node/utils/ExportHtml.js
|
|
||||||
|
|
||||||
Things in context:
|
|
||||||
|
|
||||||
1. Pad object
|
|
||||||
|
|
||||||
Identical to `exportHtmlAdditionalTags`, but for tags that are stored with a specific value (not simply `true`) on the attribute pool. For example `['color', 'red']`, instead of `['bold', true]`. This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array of arrays should be returned. The exported HTML will contain tags like `<span data-color="red">` for the content where attributes are `['color', 'red']`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
// Add the props to be supported in export
|
|
||||||
exports.exportHtmlAdditionalTagsWithData = function(hook, pad, cb){
|
|
||||||
var padId = pad.id;
|
|
||||||
cb([["color", "red"], ["color", "blue"]]);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## userLeave
|
|
||||||
Called from src/node/handler/PadMessageHandler.js
|
|
||||||
|
|
||||||
This in context:
|
|
||||||
|
|
||||||
1. session (including the pad id and author id)
|
|
||||||
|
|
||||||
This hook gets called when an author leaves a pad. This is useful if you want to perform certain actions after a pad has been edited
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
exports.userLeave = function(hook, session, callback) {
|
|
||||||
console.log('%s left pad %s', session.author, session.padId);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### clientReady
|
|
||||||
Called from src/node/handler/PadMessageHandler.js
|
|
||||||
|
|
||||||
This in context:
|
|
||||||
|
|
||||||
1. message
|
|
||||||
|
|
||||||
This hook gets called when handling a CLIENT_READY which is the first message from the client to the server.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
exports.clientReady = function(hook, message) {
|
|
||||||
console.log('Client has entered the pad' + message.padId);
|
|
||||||
};
|
|
||||||
```
|
|
|
@ -1,605 +0,0 @@
|
||||||
# HTTP API
|
|
||||||
|
|
||||||
## What can I do with this API?
|
|
||||||
The API gives another web application control of the pads. The basic functions are
|
|
||||||
|
|
||||||
* create/delete pads
|
|
||||||
* grant/forbid access to pads
|
|
||||||
* get/set pad content
|
|
||||||
|
|
||||||
The API is designed in a way, so you can reuse your existing user system with their permissions, and map it to Etherpad. Means: Your web application still has to do authentication, but you can tell Etherpad via the api, which visitors should get which permissions. This allows Etherpad to fit into any web application and extend it with real-time functionality. You can embed the pads via an iframe into your website.
|
|
||||||
|
|
||||||
Take a look at [HTTP API client libraries](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) to check if a library in your favorite programming language is available.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### Example 1
|
|
||||||
|
|
||||||
A portal (such as WordPress) wants to give a user access to a new pad. Let's assume the user have the internal id 7 and his name is michael.
|
|
||||||
|
|
||||||
Portal maps the internal userid to an etherpad author.
|
|
||||||
|
|
||||||
> Request: `http://pad.domain/api/1/createAuthorIfNotExistsFor?apikey=secret&name=Michael&authorMapper=7`
|
|
||||||
>
|
|
||||||
> Response: `{code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}}`
|
|
||||||
|
|
||||||
Portal maps the internal userid to an etherpad group:
|
|
||||||
|
|
||||||
> Request: `http://pad.domain/api/1/createGroupIfNotExistsFor?apikey=secret&groupMapper=7`
|
|
||||||
>
|
|
||||||
> Response: `{code: 0, message:"ok", data: {groupID: "g.s8oes9dhwrvt0zif"}}`
|
|
||||||
|
|
||||||
Portal creates a pad in the userGroup
|
|
||||||
|
|
||||||
> Request: `http://pad.domain/api/1/createGroupPad?apikey=secret&groupID=g.s8oes9dhwrvt0zif&padName=samplePad&text=This is the first sentence in the pad`
|
|
||||||
>
|
|
||||||
> Response: `{code: 0, message:"ok", data: null}`
|
|
||||||
|
|
||||||
Portal starts the session for the user on the group:
|
|
||||||
|
|
||||||
> Request: `http://pad.domain/api/1/createSession?apikey=secret&groupID=g.s8oes9dhwrvt0zif&authorID=a.s8oes9dhwrvt0zif&validUntil=1312201246`
|
|
||||||
>
|
|
||||||
> Response: `{"data":{"sessionID": "s.s8oes9dhwrvt0zif"}}`
|
|
||||||
|
|
||||||
Portal places the cookie "sessionID" with the given value on the client and creates an iframe including the pad.
|
|
||||||
|
|
||||||
### Example 2
|
|
||||||
|
|
||||||
A portal (such as WordPress) wants to transform the contents of a pad that multiple admins edited into a blog post.
|
|
||||||
|
|
||||||
Portal retrieves the contents of the pad for entry into the db as a blog post:
|
|
||||||
|
|
||||||
> Request: `http://pad.domain/api/1/getText?apikey=secret&padID=g.s8oes9dhwrvt0zif$123`
|
|
||||||
>
|
|
||||||
> Response: `{code: 0, message:"ok", data: {text:"Welcome Text"}}`
|
|
||||||
|
|
||||||
Portal submits content into new blog post
|
|
||||||
|
|
||||||
> Portal.AddNewBlog(content)
|
|
||||||
>
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### API version
|
|
||||||
The latest version is `1.2.13`
|
|
||||||
|
|
||||||
The current version can be queried via /api.
|
|
||||||
|
|
||||||
### Request Format
|
|
||||||
|
|
||||||
The API is accessible via HTTP. HTTP Requests are in the format /api/$APIVERSION/$FUNCTIONNAME. Parameters are transmitted via HTTP GET. $APIVERSION depends on the endpoints you want to use.
|
|
||||||
|
|
||||||
### Response Format
|
|
||||||
Responses are valid JSON in the following format:
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"code": number,
|
|
||||||
"message": string,
|
|
||||||
"data": obj
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* **code** a return code
|
|
||||||
* **0** everything ok
|
|
||||||
* **1** wrong parameters
|
|
||||||
* **2** internal error
|
|
||||||
* **3** no such function
|
|
||||||
* **4** no or wrong API Key
|
|
||||||
* **message** a status message. It's ok if everything is fine, else it contains an error message
|
|
||||||
* **data** the payload
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Data Types
|
|
||||||
|
|
||||||
* **groupID** a string, the unique id of a group. Format is g.16RANDOMCHARS, for example g.s8oes9dhwrvt0zif
|
|
||||||
* **sessionID** a string, the unique id of a session. Format is s.16RANDOMCHARS, for example s.s8oes9dhwrvt0zif
|
|
||||||
* **authorID** a string, the unique id of an author. Format is a.16RANDOMCHARS, for example a.s8oes9dhwrvt0zif
|
|
||||||
* **readOnlyID** a string, the unique id of a readonly relation to a pad. Format is r.16RANDOMCHARS, for example r.s8oes9dhwrvt0zif
|
|
||||||
* **padID** a string, format is GROUPID$PADNAME, for example the pad test of group g.s8oes9dhwrvt0zif has padID g.s8oes9dhwrvt0zif$test
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
|
|
||||||
Authentication works via a token that is sent with each request as a post parameter. There is a single token per Etherpad deployment. This token will be random string, generated by Etherpad at the first start. It will be saved in APIKEY.txt in the root folder of Etherpad. Only Etherpad and the requesting application knows this key. Token management will not be exposed through this API.
|
|
||||||
|
|
||||||
### Node Interoperability
|
|
||||||
|
|
||||||
All functions will also be available through a node module accessible from other node.js applications.
|
|
||||||
|
|
||||||
### JSONP
|
|
||||||
|
|
||||||
The API provides _JSONP_ support to allow requests from a server in a different domain.
|
|
||||||
Simply add `&jsonp=?` to the API call.
|
|
||||||
|
|
||||||
Example usage: https://api.jquery.com/jQuery.getJSON/
|
|
||||||
|
|
||||||
## API Methods
|
|
||||||
|
|
||||||
### Groups
|
|
||||||
Pads can belong to a group. The padID of grouppads is starting with a groupID like g.asdfasdfasdfasdf$test
|
|
||||||
|
|
||||||
#### createGroup()
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
creates a new group
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {groupID: g.s8oes9dhwrvt0zif}}`
|
|
||||||
|
|
||||||
#### createGroupIfNotExistsFor(groupMapper)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
this functions helps you to map your application group ids to Etherpad group ids
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {groupID: g.s8oes9dhwrvt0zif}}`
|
|
||||||
|
|
||||||
#### deleteGroup(groupID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
deletes a group
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"groupID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### listPads(groupID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns all pads of this group
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {padIDs : ["g.s8oes9dhwrvt0zif$test", "g.s8oes9dhwrvt0zif$test2"]}`
|
|
||||||
* `{code: 1, message:"groupID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### createGroupPad(groupID, padName [, text])
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
creates a new pad in this group
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"pad does already exist", data: null}`
|
|
||||||
* `{code: 1, message:"groupID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### listAllGroups()
|
|
||||||
* API >= 1.1
|
|
||||||
|
|
||||||
lists all existing groups
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {groupIDs: ["g.mKjkmnAbSMtCt8eL", "g.3ADWx6sbGuAiUmCy"]}}`
|
|
||||||
* `{code: 0, message:"ok", data: {groupIDs: []}}`
|
|
||||||
|
|
||||||
### Author
|
|
||||||
These authors are bound to the attributes the users choose (color and name).
|
|
||||||
|
|
||||||
#### createAuthor([name])
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
creates a new author
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}}`
|
|
||||||
|
|
||||||
#### createAuthorIfNotExistsFor(authorMapper [, name])
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
this functions helps you to map your application author ids to Etherpad author ids
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif"}}`
|
|
||||||
|
|
||||||
#### listPadsOfAuthor(authorID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns an array of all pads this author contributed to
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {padIDs: ["g.s8oes9dhwrvt0zif$test", "g.s8oejklhwrvt0zif$foo"]}}`
|
|
||||||
* `{code: 1, message:"authorID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### getAuthorName(authorID)
|
|
||||||
* API >= 1.1
|
|
||||||
|
|
||||||
Returns the Author Name of the author
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {authorName: "John McLear"}}`
|
|
||||||
|
|
||||||
-> can't be deleted cause this would involve scanning all the pads where this author was
|
|
||||||
|
|
||||||
### Session
|
|
||||||
Sessions can be created between a group and an author. This allows an author to access more than one group. The sessionID will be set as a cookie to the client and is valid until a certain date. The session cookie can also contain multiple comma-separated sessionIDs, allowing a user to edit pads in different groups at the same time. Only users with a valid session for this group, can access group pads. You can create a session after you authenticated the user at your web application, to give them access to the pads. You should save the sessionID of this session and delete it after the user logged out.
|
|
||||||
|
|
||||||
#### createSession(groupID, authorID, validUntil)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
creates a new session. validUntil is an unix timestamp in seconds
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {sessionID: "s.s8oes9dhwrvt0zif"}}`
|
|
||||||
* `{code: 1, message:"groupID doesn't exist", data: null}`
|
|
||||||
* `{code: 1, message:"authorID doesn't exist", data: null}`
|
|
||||||
* `{code: 1, message:"validUntil is in the past", data: null}`
|
|
||||||
|
|
||||||
#### deleteSession(sessionID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
deletes a session
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"sessionID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### getSessionInfo(sessionID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns informations about a session
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {authorID: "a.s8oes9dhwrvt0zif", groupID: g.s8oes9dhwrvt0zif, validUntil: 1312201246}}`
|
|
||||||
* `{code: 1, message:"sessionID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### listSessionsOfGroup(groupID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns all sessions of a group
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}}`
|
|
||||||
* `{code: 1, message:"groupID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### listSessionsOfAuthor(authorID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns all sessions of an author
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{"code":0,"message":"ok","data":{"s.oxf2ras6lvhv2132":{"groupID":"g.s8oes9dhwrvt0zif","authorID":"a.akf8finncvomlqva","validUntil":2312905480}}}`
|
|
||||||
* `{code: 1, message:"authorID does not exist", data: null}`
|
|
||||||
|
|
||||||
### Pad Content
|
|
||||||
|
|
||||||
Pad content can be updated and retrieved through the API
|
|
||||||
|
|
||||||
#### getText(padID, [rev])
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns the text of a pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {text:"Welcome Text"}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### setText(padID, text)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
sets the text of a pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
* `{code: 1, message:"text too long", data: null}`
|
|
||||||
|
|
||||||
#### appendText(padID, text)
|
|
||||||
* API >= 1.2.13
|
|
||||||
|
|
||||||
appends text to a pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
* `{code: 1, message:"text too long", data: null}`
|
|
||||||
|
|
||||||
#### getHTML(padID, [rev])
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns the text of a pad formatted as HTML
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {html:"Welcome Text<br>More Text"}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### setHTML(padID, html)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
sets the text of a pad based on HTML, HTML must be well-formed. Malformed HTML will send a warning to the API log.
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### getAttributePool(padID)
|
|
||||||
* API >= 1.2.8
|
|
||||||
|
|
||||||
returns the attribute pool of a pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{ "code":0,
|
|
||||||
"message":"ok",
|
|
||||||
"data": {
|
|
||||||
"pool":{
|
|
||||||
"numToAttrib":{
|
|
||||||
"0":["author","a.X4m8bBWJBZJnWGSh"],
|
|
||||||
"1":["author","a.TotfBPzov54ihMdH"],
|
|
||||||
"2":["author","a.StiblqrzgeNTbK05"],
|
|
||||||
"3":["bold","true"]
|
|
||||||
},
|
|
||||||
"attribToNum":{
|
|
||||||
"author,a.X4m8bBWJBZJnWGSh":0,
|
|
||||||
"author,a.TotfBPzov54ihMdH":1,
|
|
||||||
"author,a.StiblqrzgeNTbK05":2,
|
|
||||||
"bold,true":3
|
|
||||||
},
|
|
||||||
"nextNum":4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
* `{"code":1,"message":"padID does not exist","data":null}`
|
|
||||||
|
|
||||||
#### getRevisionChangeset(padID, [rev])
|
|
||||||
* API >= 1.2.8
|
|
||||||
|
|
||||||
get the changeset at a given revision, or last revision if 'rev' is not defined.
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{ "code" : 0,
|
|
||||||
"message" : "ok",
|
|
||||||
"data" : "Z:1>6b|5+6b$Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at http://etherpad.org\n"
|
|
||||||
}`
|
|
||||||
* `{"code":1,"message":"padID does not exist","data":null}`
|
|
||||||
* `{"code":1,"message":"rev is higher than the head revision of the pad","data":null}`
|
|
||||||
|
|
||||||
#### createDiffHTML(padID, startRev, endRev)
|
|
||||||
* API >= 1.2.7
|
|
||||||
|
|
||||||
returns an object of diffs from 2 points in a pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!<br><br>Get involved with Etherpad at <a href=\"http://etherpad.org\">http://etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}`
|
|
||||||
* `{"code":4,"message":"no or wrong API Key","data":null}`
|
|
||||||
|
|
||||||
#### restoreRevision(padId, rev)
|
|
||||||
* API >= 1.2.11
|
|
||||||
|
|
||||||
Restores revision from past as new changeset
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* {code:0, message:"ok", data:null}
|
|
||||||
* {code: 1, message:"padID does not exist", data: null}
|
|
||||||
|
|
||||||
### Chat
|
|
||||||
#### getChatHistory(padID, [start, end])
|
|
||||||
* API >= 1.2.7
|
|
||||||
|
|
||||||
returns
|
|
||||||
|
|
||||||
* a part of the chat history, when `start` and `end` are given
|
|
||||||
* the whole chat histroy, when no extra parameters are given
|
|
||||||
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
|
|
||||||
* `{"code":0,"message":"ok","data":{"messages":[{"text":"foo","userId":"a.foo","time":1359199533759,"userName":"test"},{"text":"bar","userId":"a.foo","time":1359199534622,"userName":"test"}]}}`
|
|
||||||
* `{code: 1, message:"start is higher or equal to the current chatHead", data: null}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### getChatHead(padID)
|
|
||||||
* API >= 1.2.7
|
|
||||||
|
|
||||||
returns the chatHead (last number of the last chat-message) of the pad
|
|
||||||
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
|
|
||||||
* `{code: 0, message:"ok", data: {chatHead: 42}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### appendChatMessage(padID, text, authorID [, time])
|
|
||||||
* API >= 1.2.12
|
|
||||||
|
|
||||||
creates a chat message, saves it to the database and sends it to all connected clients of this pad
|
|
||||||
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"text is no string", data: null}`
|
|
||||||
|
|
||||||
### Pad
|
|
||||||
Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security manager controls access of them and it's forbidden for normal pads to include a $ in the name.
|
|
||||||
|
|
||||||
#### createPad(padID [, text])
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
creates a new (non-group) pad. Note that if you need to create a group Pad, you should call **createGroupPad**.
|
|
||||||
You get an error message if you use one of the following characters in the padID: "/", "?", "&" or "#".
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"padID does already exist", data: null}`
|
|
||||||
* `{code: 1, message:"malformed padID: Remove special characters", data: null}`
|
|
||||||
|
|
||||||
#### getRevisionsCount(padID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns the number of revisions of this pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {revisions: 56}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### getSavedRevisionsCount(padID)
|
|
||||||
* API >= 1.2.11
|
|
||||||
|
|
||||||
returns the number of saved revisions of this pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {savedRevisions: 42}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### listSavedRevisions(padID)
|
|
||||||
* API >= 1.2.11
|
|
||||||
|
|
||||||
returns the list of saved revisions of this pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### saveRevision(padID [, rev])
|
|
||||||
* API >= 1.2.11
|
|
||||||
|
|
||||||
saves a revision
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### padUsersCount(padID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns the number of user that are currently editing this pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {padUsersCount: 5}}`
|
|
||||||
|
|
||||||
#### padUsers(padID)
|
|
||||||
* API >= 1.1
|
|
||||||
|
|
||||||
returns the list of users that are currently editing this pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {padUsers: [{colorId:"#c1a9d9","name":"username1","timestamp":1345228793126,"id":"a.n4gEeMLsvg12452n"},{"colorId":"#d9a9cd","name":"Hmmm","timestamp":1345228796042,"id":"a.n4gEeMLsvg12452n"}]}}`
|
|
||||||
* `{code: 0, message:"ok", data: {padUsers: []}}`
|
|
||||||
|
|
||||||
#### deletePad(padID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
deletes a pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### copyPad(sourceID, destinationID[, force=false])
|
|
||||||
* API >= 1.2.8
|
|
||||||
|
|
||||||
copies a pad with full history and chat. If force is true and the destination pad exists, it will be overwritten.
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### movePad(sourceID, destinationID[, force=false])
|
|
||||||
* API >= 1.2.8
|
|
||||||
|
|
||||||
moves a pad. If force is true and the destination pad exists, it will be overwritten.
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### getReadOnlyID(padID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns the read only link of a pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {readOnlyID: "r.s8oes9dhwrvt0zif"}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### getPadID(readOnlyID)
|
|
||||||
* API >= 1.2.10
|
|
||||||
|
|
||||||
returns the id of a pad which is assigned to the readOnlyID
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {padID: "p.s8oes9dhwrvt0zif"}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### setPublicStatus(padID, publicStatus)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
sets a boolean for the public status of a pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### getPublicStatus(padID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
return true of false
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {publicStatus: true}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### setPassword(padID, password)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns ok or an error message
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: null}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### isPasswordProtected(padID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns true or false
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {passwordProtection: true}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### listAuthorsOfPad(padID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns an array of authors who contributed to this pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### getLastEdited(padID)
|
|
||||||
* API >= 1
|
|
||||||
|
|
||||||
returns the timestamp of the last revision of the pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {lastEdited: 1340815946602}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### sendClientsMessage(padID, msg)
|
|
||||||
* API >= 1.1
|
|
||||||
|
|
||||||
sends a custom message of type `msg` to the pad
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {}}`
|
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
|
||||||
|
|
||||||
#### checkToken()
|
|
||||||
* API >= 1.2
|
|
||||||
|
|
||||||
returns ok when the current api token is valid
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{"code":0,"message":"ok","data":null}`
|
|
||||||
* `{"code":4,"message":"no or wrong API Key","data":null}`
|
|
||||||
|
|
||||||
### Pads
|
|
||||||
|
|
||||||
#### listAllPads()
|
|
||||||
* API >= 1.2.1
|
|
||||||
|
|
||||||
lists all pads on this epl instance
|
|
||||||
|
|
||||||
*Example returns:*
|
|
||||||
* `{code: 0, message:"ok", data: {padIDs: ["testPad", "thePadsOfTheOthers"]}}`
|
|
|
@ -1,14 +0,0 @@
|
||||||
# Plugin Framework
|
|
||||||
`require("ep_etherpad-lite/static/js/plugingfw/plugins")`
|
|
||||||
|
|
||||||
## plugins.update
|
|
||||||
`require("ep_etherpad-lite/static/js/plugingfw/plugins").update()` will use npm to list all installed modules and read their ep.json files, registering the contained hooks.
|
|
||||||
A hook registration is a pair of a hook name and a function reference (filename for require() plus function name)
|
|
||||||
|
|
||||||
## hooks.callAll
|
|
||||||
`require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value})` will call all hook functions registered for `hook_name` with `{argname:value}`.
|
|
||||||
|
|
||||||
## hooks.aCallAll
|
|
||||||
?
|
|
||||||
|
|
||||||
## ...
|
|
|
@ -1,46 +0,0 @@
|
||||||
# Toolbar controller
|
|
||||||
src/node/utils/toolbar.js
|
|
||||||
|
|
||||||
## button(opts)
|
|
||||||
* {Object} `opts`
|
|
||||||
* `command` - this command fill be fired on the editbar on click
|
|
||||||
* `localizationId` - will be set as `data-l10-id`
|
|
||||||
* `class` - here you can add additional classes to the button
|
|
||||||
|
|
||||||
Returns: {Button}
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
var orderedlist = toolbar.button({
|
|
||||||
command: "insertorderedlist",
|
|
||||||
localizationId: "pad.toolbar.ol.title",
|
|
||||||
class: "buttonicon buttonicon-insertorderedlist"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also create buttons with text:
|
|
||||||
|
|
||||||
```
|
|
||||||
var myButton = toolbar.button({
|
|
||||||
command: "myButton",
|
|
||||||
localizationId: "myPlugin.toolbar.myButton",
|
|
||||||
class: "buttontext"
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## selectButton(opts)
|
|
||||||
* {Object} `opts`
|
|
||||||
* `id` - id of the menu item
|
|
||||||
* `selectId` - id of the select element
|
|
||||||
* `command` - this command fill be fired on the editbar on change
|
|
||||||
|
|
||||||
Returns: {SelectButton}
|
|
||||||
|
|
||||||
## SelectButton.addOption(value, text, attributes)
|
|
||||||
* {String} value - The value of this option
|
|
||||||
* {String} text - the label text used for this option
|
|
||||||
* {Object} attributes - any additional html attributes go here (e.g. `data-l10n-id`)
|
|
||||||
|
|
||||||
## registerButton(name, item)
|
|
||||||
* {String} name - used to reference the item in the toolbar config in settings.json
|
|
||||||
* {Button|SelectButton} item - the button to add
|
|
|
@ -1,47 +0,0 @@
|
||||||
html {
|
|
||||||
border-top: solid green 5pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.apidoc {
|
|
||||||
width: 60%;
|
|
||||||
min-width: 10cm;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header {
|
|
||||||
padding: 1pc 0;
|
|
||||||
color: #111;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
a:active {
|
|
||||||
color: #272;
|
|
||||||
}
|
|
||||||
a:focus,
|
|
||||||
a:hover {
|
|
||||||
color: #050;
|
|
||||||
}
|
|
||||||
|
|
||||||
#apicontent a.mark,
|
|
||||||
#apicontent a.mark:active {
|
|
||||||
float: right;
|
|
||||||
color: #BBB;
|
|
||||||
font-size: 0.7cm;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
#apicontent a.mark:focus,
|
|
||||||
#apicontent a.mark:hover {
|
|
||||||
color: #AAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
#apicontent code {
|
|
||||||
padding: 1px;
|
|
||||||
background-color: #EEE;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #DDD;
|
|
||||||
}
|
|
||||||
#apicontent pre>code {
|
|
||||||
display: block;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
# Custom static files
|
|
||||||
Etherpad allows you to include your own static files in the browser, by modifying the files in `static/custom`.
|
|
||||||
|
|
||||||
* `index.js` Javascript that'll be run in `/`
|
|
||||||
* `index.css` Stylesheet affecting `/`
|
|
||||||
* `pad.js` Javascript that'll be run in `/p/:padid`
|
|
||||||
* `pad.css` Stylesheet affecting `/p/:padid`
|
|
||||||
* `timeslider.js` Javascript that'll be run in `/p/:padid/timeslider`
|
|
||||||
* `timeslider.css` Stylesheet affecting `/p/:padid/timeslider`
|
|
||||||
* `favicon.ico` Overrides the default favicon.
|
|
||||||
* `robots.txt` Overrides the default `robots.txt`.
|
|
|
@ -1,67 +0,0 @@
|
||||||
# Database structure
|
|
||||||
|
|
||||||
## Keys and their values
|
|
||||||
|
|
||||||
### groups
|
|
||||||
A list of all existing groups (a JSON object with groupIDs as keys and `1` as values).
|
|
||||||
|
|
||||||
### pad:$PADID
|
|
||||||
Contains all information about pads
|
|
||||||
|
|
||||||
* **atext** - the latest attributed text
|
|
||||||
* **pool** - the attribute pool
|
|
||||||
* **head** - the number of the latest revision
|
|
||||||
* **chatHead** - the number of the latest chat entry
|
|
||||||
* **public** - flag that disables security for this pad
|
|
||||||
* **passwordHash** - string that contains a bcrypt hashed password for this pad
|
|
||||||
|
|
||||||
### pad:$PADID:revs:$REVNUM
|
|
||||||
Saves a revision $REVNUM of pad $PADID
|
|
||||||
|
|
||||||
* **meta**
|
|
||||||
* **author** - the autorID of this revision
|
|
||||||
* **timestamp** - the timestamp of when this revision was created
|
|
||||||
* **changeset** - the changeset of this revision
|
|
||||||
|
|
||||||
### pad:$PADID:chat:$CHATNUM
|
|
||||||
Saves a chat entry with num $CHATNUM of pad $PADID
|
|
||||||
|
|
||||||
* **text** - the text of this chat entry
|
|
||||||
* **userId** - the authorID of this chat entry
|
|
||||||
* **time** - the timestamp of this chat entry
|
|
||||||
|
|
||||||
### pad2readonly:$PADID
|
|
||||||
Translates a padID to a readonlyID
|
|
||||||
### readonly2pad:$READONLYID
|
|
||||||
Translates a readonlyID to a padID
|
|
||||||
### token2author:$TOKENID
|
|
||||||
Translates a token to an authorID
|
|
||||||
### globalAuthor:$AUTHORID
|
|
||||||
Information about an author
|
|
||||||
|
|
||||||
* **name** - the name of this author as shown in the pad
|
|
||||||
* **colorID** - the colorID of this author as shown in the pad
|
|
||||||
|
|
||||||
### mapper2group:$MAPPER
|
|
||||||
Maps an external application identifier to an internal group
|
|
||||||
### mapper2author:$MAPPER
|
|
||||||
Maps an external application identifier to an internal author
|
|
||||||
### group:$GROUPID
|
|
||||||
a group of pads
|
|
||||||
|
|
||||||
* **pads** - object with pad names in it, values are 1
|
|
||||||
### session:$SESSIONID
|
|
||||||
a session between an author and a group
|
|
||||||
|
|
||||||
* **groupID** - the groupID the session belongs too
|
|
||||||
* **authorID** - the authorID the session belongs too
|
|
||||||
* **validUntil** - the timestamp until this session is valid
|
|
||||||
|
|
||||||
### author2sessions:$AUTHORID
|
|
||||||
saves the sessions of an author
|
|
||||||
|
|
||||||
* **sessionsIDs** - object with sessionIDs in it, values are 1
|
|
||||||
|
|
||||||
### group2sessions:$GROUPID
|
|
||||||
|
|
||||||
* **sessionsIDs** - object with sessionIDs in it, values are 1
|
|
|
@ -1,15 +0,0 @@
|
||||||
# About this Documentation
|
|
||||||
|
|
||||||
<!-- type=misc -->
|
|
||||||
|
|
||||||
The goal of this documentation is to comprehensively explain Etherpad,
|
|
||||||
both from a reference as well as a conceptual point of view.
|
|
||||||
|
|
||||||
Where appropriate, property types, method arguments, and the arguments
|
|
||||||
provided to event handlers are detailed in a list underneath the topic
|
|
||||||
heading.
|
|
||||||
|
|
||||||
Every `.html` file is generated based on the corresponding
|
|
||||||
`.md` file in the `doc/api/` folder in the source tree. The
|
|
||||||
documentation is generated using the `bin/doc/generate.js` program.
|
|
||||||
The HTML template is located at `doc/template.html`.
|
|
|
@ -1,2 +0,0 @@
|
||||||
# About this folder
|
|
||||||
We put all documentations we found about the original Etherpad together in this folder. Most of this is still valid for the current (node.js based) Etherpad.
|
|
Binary file not shown.
|
@ -1,372 +0,0 @@
|
||||||
\documentclass{article}
|
|
||||||
\usepackage{hyperref}
|
|
||||||
|
|
||||||
\begin{document}
|
|
||||||
|
|
||||||
\title{Etherpad and EasySync Technical Manual}
|
|
||||||
\author{AppJet, Inc., with modifications by the Etherpad Foundation}
|
|
||||||
\date{\today}
|
|
||||||
|
|
||||||
\maketitle
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
\tableofcontents
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
|
|
||||||
\section{Documents}
|
|
||||||
\begin{itemize}
|
|
||||||
\item A document is a list of characters, or a string.
|
|
||||||
\item A document can also be represented as a list of \emph{changesets}.
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
\section{Changesets}
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item A changeset represents a change to a document.
|
|
||||||
\item A changeset can be applied to a document to produce a new document.
|
|
||||||
\item When a document is represented as a list of changesets, it is assumed that the first changeset applies to the empty document, [].
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
|
|
||||||
\section{Changeset representation} \label{representation}
|
|
||||||
|
|
||||||
$$(\ell \rightarrow \ell')[c_1,c_2,c_3,...]$$
|
|
||||||
|
|
||||||
where
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item[] $\ell$ is the length of the document before the change,
|
|
||||||
\item[] $\ell'$ is the length of the document after the change,
|
|
||||||
\item[] $[c_1,c_2,c_3,...]$ is an array of $\ell'$ characters that described the document after the change.
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
Note that $\forall c_i : 0 \leq i \leq \ell'$ is either an integer or a character.
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item Integers represent retained characters in the original document.
|
|
||||||
\item Characters represent insertions.
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
\section{Constraints on Changesets}
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item Changesets are canonical and therefor comparable. When represented in computer memory, we always use the same representation for the same changeset. If the memory representation of two changesets differ, they must be different changesets.
|
|
||||||
\item Changesets are compact. Thus, if there are two ways to represent a changeset in computer memory, then we always use the representation that takes up the fewest bytes.
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
Later we will discuss optimizations to changeset
|
|
||||||
representation (using ``strips'' and other such
|
|
||||||
techniques). The two constraints must apply to any
|
|
||||||
representation of changesets.
|
|
||||||
|
|
||||||
\section{Notation}
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item We use the algebraic multiplication notation to represent changeset application.
|
|
||||||
\item While changesets are defined as operations on documents, documents themselves are represented as a list of changesets, initially applying to the empty document.
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
\paragraph{Example}
|
|
||||||
$A=(0\rightarrow 5)[``hello"]$
|
|
||||||
$B=(5\rightarrow 11)[0-4, ``\ world"]$
|
|
||||||
|
|
||||||
We can write the document ``hello world'' as $A\cdot B$ or
|
|
||||||
just $AB$. Note that the ``initial document'' can be made
|
|
||||||
into the changeset $(0\rightarrow
|
|
||||||
N)[``<\mathit{the\ document\ text}>"]$.
|
|
||||||
|
|
||||||
When $A$ and $B$ are changesets, we can also refer to $(AB)$ as ``the composition'' of $A$ and $B$. Changesets are closed under composition.
|
|
||||||
|
|
||||||
\section{Composition of Changesets}
|
|
||||||
|
|
||||||
For any two changesets $A$, $B$ such that
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item[] $A=(n_1\rightarrow n_2)[\cdots]$
|
|
||||||
\item[] $B=(n_2\rightarrow n_3)[\cdots]$
|
|
||||||
\end{itemize}
|
|
||||||
it is clear that there is a third changeset $C=(n_1\rightarrow n_3)[\cdots]$ such that applying $C$ to a document $X$ yields the same resulting document as does applying $A$ and then $B$. In this case, we write $AB=C$.
|
|
||||||
|
|
||||||
Given the representation from Section \ref{representation}, it is straightforward to compute the composition of two changesets.
|
|
||||||
|
|
||||||
\section{Changeset Merging}
|
|
||||||
|
|
||||||
Now we come to realtime document editing. Suppose two different users make two different changes to the same document at the same time. It is impossible to compose these changes. For example, if we have the document $X$ of length $n$, we may have $A=(n\rightarrow n_a)[\ldots n_a \mathrm{characters}]$, $B=(n\rightarrow n_b)[\ldots n_b \mathrm{characters}]$ where $n\neq n_a\neq n_b$.
|
|
||||||
|
|
||||||
It is impossible to compute $(XA)B$ because $B$ can only be applied to a document of length $n$, and $(XA)$ has length $n_a$. Similarly, $A$ cannot be applied to $(XB)$ because $(XB)$ has length $n_b$.
|
|
||||||
|
|
||||||
This is where \emph{merging} comes in. Merging takes two changesets that apply to the same initial document (and that cannot be composed), and computes a single new changeset that preserves the intent of both changes. The merge of $A$ and $B$ is written as $m(A,B)$. For the Etherpad system to work, we require that $m(A,B)=m(B,A)$.
|
|
||||||
|
|
||||||
Aside from what we have said so far about merging, there are many different implementations that will lead to a workable system. We have created one implementation for text that has the following constraints.
|
|
||||||
|
|
||||||
\section{Follows} \label{follows}
|
|
||||||
|
|
||||||
When users $A$ and $B$ have the same document $X$ on their screen, and they proceed to make respective changesets $A$ and $B$, it is no use to compute $m(A,B)$, because $m(A,B)$ applies to document $X$, but the users are already looking at document $XA$ and $XB$. What we really want is to compute $B'$ and $A'$ such that
|
|
||||||
$$XAB' = XBA' = Xm(A,B)$$
|
|
||||||
|
|
||||||
``Following'' computes these $B'$ and $A'$ changesets. The definition of the ``follow'' function $f$ is such that $Af(A,B)=Bf(B,A)=m(A,B)=m(B,A)$. When we compute $f(A,B)$
|
|
||||||
\begin{itemize}
|
|
||||||
\item Insertions in $A$ become retained characters in $f(A,B)$
|
|
||||||
\item Insertions in $B$ become insertions in $f(A,B)$
|
|
||||||
\item Retain whatever characters are retained in \emph{both} $A$ and $B$
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
\paragraph{Example}
|
|
||||||
|
|
||||||
Suppose we have the initial document $X=(0\rightarrow 8)[``\mathit{baseball}"]$ and user $A$ changes it to ``basil'' with changeset $A$, and user $B$ changes it to ``below'' with changeset $B$.
|
|
||||||
|
|
||||||
We have
|
|
||||||
$X=(0\rightarrow 8)[``\mathit{baseball}"]$ \\
|
|
||||||
$A=(8\rightarrow 5)[0-1, ``\mathit{si}", 7]$ \\
|
|
||||||
$B=(8\rightarrow 5)[0, ``\mathit{e}", 6, ``\mathit{ow}"]$ \\
|
|
||||||
|
|
||||||
First we compute the merge $m(A,B)=m(B,A)$ according to the constraints
|
|
||||||
|
|
||||||
$$m(A,B)=(8\rightarrow 6)[0, "e", "si", "ow"] = (8\rightarrow 6)[0, ``\mathit{esiow}"]$$
|
|
||||||
|
|
||||||
Then we need to compute the follows $B'=f(A,B)$ and $A'=f(B,A)$.
|
|
||||||
|
|
||||||
$$B'=f(A,B)=(5\rightarrow 6)[0,``\mathit{e}",2,3,``\mathit{ow}"]$$
|
|
||||||
|
|
||||||
Note that the numbers $0$, $2$, and $3$ are indices into $A=(8\rightarrow 5)[0,1,``\mathit{si}",7]$
|
|
||||||
|
|
||||||
\begin{tabular}{ccccc}
|
|
||||||
0 & 1 & 2 & 3 & 4 \\
|
|
||||||
0 & 1 & s & i & 7
|
|
||||||
\end{tabular}
|
|
||||||
|
|
||||||
$A'=f(B,A)=(5\rightarrow 6)[0,1,"si",3,4]$
|
|
||||||
|
|
||||||
We can now double check that $AB'=BA'=m(A,B)=(8\rightarrow 6)[0,``\mathit{esiow}"]$.
|
|
||||||
|
|
||||||
Now that we have made the mathematical meaning of the
|
|
||||||
preceding pages complete, we can build a client/server
|
|
||||||
system to support realtime editing by multiple users.
|
|
||||||
|
|
||||||
\section{System Overview}
|
|
||||||
|
|
||||||
There is a server that holds the current state of a
|
|
||||||
document. Clients (users) can connect to the server from
|
|
||||||
their web browsers. The clients and server maintain state
|
|
||||||
and can send messages to one another in real-time, but
|
|
||||||
because we are in a web browser scenario, clients cannot
|
|
||||||
send each other messages directly, and must go through the
|
|
||||||
server always. (This may distinguish from prior art?)
|
|
||||||
|
|
||||||
The other critical design feature of the system is that
|
|
||||||
\emph{A client must always be able to edit their local
|
|
||||||
copy of the document, so the user is never blocked from
|
|
||||||
typing because of waiting to send or receive data.}
|
|
||||||
|
|
||||||
\section{Client State}
|
|
||||||
|
|
||||||
At any moment in time, a client maintains its state in the
|
|
||||||
form of 3 changesets. The client document looks like
|
|
||||||
$A\cdot X \cdot Y$, where
|
|
||||||
|
|
||||||
$A$ is the latest server version, the composition of all
|
|
||||||
changesets committed to the server, from this client or
|
|
||||||
from others, that the server has informed this client
|
|
||||||
about. Initially $A=(0\rightarrow N)[<\mathit{initial\ document\ text}>]$.
|
|
||||||
|
|
||||||
$X$ is the composition of all changesets this client has
|
|
||||||
submitted to the server but has not heard back about yet.
|
|
||||||
Initially $X=(N\rightarrow N)[0,1,2,\ldots, N-1]$, in
|
|
||||||
other words, the identity, henceforth denoted $I_N$.
|
|
||||||
|
|
||||||
$Y$ is the composition of all changesets this client has
|
|
||||||
made but has not yet submitted to the server yet.
|
|
||||||
Initially $Y=(N\rightarrow N)[0,1,2,\ldots, N-1]$.
|
|
||||||
|
|
||||||
\section{Client Operations}
|
|
||||||
|
|
||||||
A client can do 5 things.
|
|
||||||
|
|
||||||
\begin{enumerate}
|
|
||||||
\item Incorporate new typing into local state
|
|
||||||
\item Submit a changeset to the server
|
|
||||||
\item Hear back acknowledgement of a submitted changeset
|
|
||||||
\item Hear from the server about other clients' changesets
|
|
||||||
\item Connect to the server and request the initial document
|
|
||||||
\end{enumerate}
|
|
||||||
|
|
||||||
As these 5 events happen, the client updates its
|
|
||||||
representation $A\cdot X \cdot Y$ according to the
|
|
||||||
relations that follow. Changes ``move left'' as time goes
|
|
||||||
by: into $Y$ when the user types, into $X$ when change
|
|
||||||
sets are submitted to the server, and into $A$ when the
|
|
||||||
server acknowledges changesets.
|
|
||||||
|
|
||||||
\subsection{New local typing}
|
|
||||||
|
|
||||||
When a user makes an edit $E$ to the document, the client
|
|
||||||
computes the composition $(Y\cdot E)$ and updates its local
|
|
||||||
state, i.e. $Y \leftarrow Y\cdot E$. I.e., if $Y$ is the
|
|
||||||
variable holding local unsubmitted changes, it will be
|
|
||||||
assigned the new value $(Y\cdot E)$.
|
|
||||||
|
|
||||||
\subsection{Submitting changesets to server}
|
|
||||||
|
|
||||||
When a client submit its local changes to the server, it
|
|
||||||
transmits a copy of $Y$ and then assigns $Y$ to $X$, and
|
|
||||||
assigns the identity to $Y$. I.e.,
|
|
||||||
|
|
||||||
\begin{enumerate}
|
|
||||||
\item Send $Y$ to server,
|
|
||||||
\item $X \leftarrow Y$
|
|
||||||
\item $Y \leftarrow I_N$
|
|
||||||
(the identity).
|
|
||||||
\end{enumerate}
|
|
||||||
|
|
||||||
This happens every 500ms as long as it receives an
|
|
||||||
acknowledgement. Must receive ACK before submitting
|
|
||||||
again. Note that $X$ is always equal to the identity
|
|
||||||
before the second step occurs, so no information is lost.
|
|
||||||
|
|
||||||
\subsection{Hear ACK from server}
|
|
||||||
|
|
||||||
When the client hears ACK from server,
|
|
||||||
|
|
||||||
$A \leftarrow A\cdot X$ \\
|
|
||||||
$X \leftarrow I_N$
|
|
||||||
|
|
||||||
\subsection{Hear about another client's changeset}
|
|
||||||
|
|
||||||
When a client hears about another client's changeset $B$,
|
|
||||||
it computes a new $A$, $X$, and $Y$, which we will call
|
|
||||||
$A'$, $X'$, and $Y'$ respectively. It also computes a
|
|
||||||
changeset $D$ which is applied to the current text view on
|
|
||||||
the client, $V$. Because $AXY$ must always equal the
|
|
||||||
current view, $AXY=V$ before the client hears about $B$,
|
|
||||||
and $A'X'Y'=VD$ after the computation is performed.
|
|
||||||
|
|
||||||
The steps are:
|
|
||||||
|
|
||||||
\begin{enumerate}
|
|
||||||
\item Compute $A' = AB$
|
|
||||||
\item Compute $X' = f(B,X)$
|
|
||||||
\item Compute $Y' = f(f(X,B), Y)$
|
|
||||||
\item Compute $D=f(Y,f(X,B))$
|
|
||||||
\item Assign $A \leftarrow A'$, $X \leftarrow X'$, $Y \leftarrow Y'$.
|
|
||||||
\item Apply $D$ to the current view of the document
|
|
||||||
displayed on the user's screen.
|
|
||||||
\end{enumerate}
|
|
||||||
|
|
||||||
In steps 2,3, and 4, $f$ is the follow operation described
|
|
||||||
in Section \ref{follows}.
|
|
||||||
|
|
||||||
\paragraph{Proof that $\mathbf{AXY=V \Rightarrow A'X'Y'=VD}$.}
|
|
||||||
Substituting $A'X'Y'=(AB)(f(B,X))(f(f(X,B),Y))$, we
|
|
||||||
recall that merges are commutative. So for any two
|
|
||||||
changesets $P$ and $Q$,
|
|
||||||
$$m(P,Q)=m(Q,P)=Qf(Q,P)=Pf(P,Q)$$
|
|
||||||
|
|
||||||
Applying this to the relation above, we see
|
|
||||||
\begin{eqnarray*}
|
|
||||||
A'X'Y'&=& AB f(B,X) f(f(X,B),Y) \\
|
|
||||||
&=&AX f(X,B) f(f(X,B),Y) \\
|
|
||||||
&=&A X Y f(Y, f(X,B)) \\
|
|
||||||
&=&A X Y D \\
|
|
||||||
&=&V D
|
|
||||||
\end{eqnarray*}
|
|
||||||
As claimed.
|
|
||||||
|
|
||||||
\subsection{Connect to server}
|
|
||||||
|
|
||||||
When a client connects to the server for the first time,
|
|
||||||
it first generates a random unique ID and sends this to
|
|
||||||
the server. The client remembers this ID and sends it
|
|
||||||
with each changeset to the server.
|
|
||||||
|
|
||||||
The client receives the latest version of the document
|
|
||||||
from the server, called HEADTEXT. The client then sets
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item[] $A \leftarrow \mathrm{HEADTEXT}$
|
|
||||||
\item[] $X \leftarrow I_N$
|
|
||||||
\item[] $Y \leftarrow I_N$
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
And finally, the client displays HEADTEXT on the screen.
|
|
||||||
|
|
||||||
\section{Server Overview}
|
|
||||||
|
|
||||||
Like the client(s), the server has state and performs
|
|
||||||
operations. Operations are only performed in response to
|
|
||||||
messages from clients.
|
|
||||||
|
|
||||||
\section{Server State}
|
|
||||||
|
|
||||||
The server maintains a document as an ordered list of
|
|
||||||
\emph{revision records}. A revision record is a data
|
|
||||||
structure that contains a changeset and authorship
|
|
||||||
information.
|
|
||||||
|
|
||||||
\begin{verbatim}
|
|
||||||
RevisionRecord = {
|
|
||||||
ChangeSet,
|
|
||||||
Source (unique ID),
|
|
||||||
Revision Number (consecutive order, starting at 0)
|
|
||||||
}
|
|
||||||
\end{verbatim}
|
|
||||||
|
|
||||||
For efficiency, the server may also store a variable
|
|
||||||
called HEADTEXT, which is the composition of all
|
|
||||||
changesets in the list of revision records. This is an
|
|
||||||
optimization, because clearly this can be computed from
|
|
||||||
the set of revision records.
|
|
||||||
|
|
||||||
\section{Server Operations Overview}
|
|
||||||
|
|
||||||
The server does two things in addition to maintaining
|
|
||||||
state representing the set of connected clients and
|
|
||||||
remembering what revision number each client is up to date
|
|
||||||
with:
|
|
||||||
|
|
||||||
\begin{enumerate}
|
|
||||||
\item Respond to a client's connection requesting the initial document.
|
|
||||||
\item Respond to a client's submission of a new changeset.
|
|
||||||
\end{enumerate}
|
|
||||||
|
|
||||||
\subsection{Respond to client connect}
|
|
||||||
When a server receives a connection request from a client,
|
|
||||||
it receives the client's unique ID and stores that in the
|
|
||||||
server's set of connected clients. It then sends the
|
|
||||||
client the contents of HEADTEXT, and the corresponding
|
|
||||||
revision number. Finally the server notes that this
|
|
||||||
client is up to date with that revision number.
|
|
||||||
|
|
||||||
\subsection{Respond to client changeset}
|
|
||||||
|
|
||||||
When the server receives information from a client about
|
|
||||||
the client's changeset $C$, it does five things:
|
|
||||||
|
|
||||||
\begin{enumerate}
|
|
||||||
\item Notes that this change applies to revision number
|
|
||||||
$r_c$ (the client's latest revision).
|
|
||||||
\item Creates a new changeset $C'$ that is relative to the
|
|
||||||
server's most recent revision number, which we call
|
|
||||||
$r_H$ ($H$ for HEAD). $C'$ can be computed using
|
|
||||||
follows (Section \ref{follows}). Remember that the server has a series of
|
|
||||||
changesets,
|
|
||||||
$$S_0\rightarrow S_1\rightarrow \ldots S_{r_c}\rightarrow S_{r_c+1} \rightarrow \ldots \rightarrow S_{r_H} $$
|
|
||||||
$C$ is relative to $S_{r_c}$, but we need to compute $C'$ relative to $S_{r_H}$.
|
|
||||||
We can compute a new $C$ relative to $S_{r_c+1}$ by computing $f(S_{r_c+1},C)$. Similarly we can repeat for
|
|
||||||
$S_{r_c+2}$ and so forth until we have $C'$ represented relative to $S_{r_H}$.
|
|
||||||
\item Send $C'$ to all other clients
|
|
||||||
\item Send ACK back to original client
|
|
||||||
\item Add $C'$ to the server's list of revision records by creating a new revision record out of this and the client's ID.
|
|
||||||
|
|
||||||
\appendix
|
|
||||||
|
|
||||||
\section*{Additional topics}
|
|
||||||
\begin{enumerate}
|
|
||||||
\item Optimizations (strips, more caching, etc.)
|
|
||||||
\item Pseudocode for composition, merge, and follow
|
|
||||||
\item How authorship information is used to color-code the document based on who typed what
|
|
||||||
\item How persistent connections are maintained between client and server
|
|
||||||
\end{enumerate}
|
|
||||||
\end{enumerate}
|
|
||||||
|
|
||||||
|
|
||||||
\end{document}
|
|
Binary file not shown.
|
@ -1,200 +0,0 @@
|
||||||
\documentclass[12pt]{article}
|
|
||||||
|
|
||||||
\usepackage[T1]{fontenc}
|
|
||||||
\usepackage[USenglish]{babel}
|
|
||||||
|
|
||||||
|
|
||||||
\begin{document}
|
|
||||||
|
|
||||||
\title{Easysync Protocol}
|
|
||||||
\author{AppJet, Inc., with modifications by the Etherpad Foundation}
|
|
||||||
\date{\today}
|
|
||||||
|
|
||||||
\maketitle
|
|
||||||
|
|
||||||
\section{Attributes}
|
|
||||||
|
|
||||||
An ``attribute'' is a (key,value) pair such as
|
|
||||||
\verb|(author,abc123)| or \verb|(bold,true)|.
|
|
||||||
Sometimes an attribute is treated as an instruction to add
|
|
||||||
that attribute, in which case an empty value means to
|
|
||||||
remove it. So \verb|(bold,)| removes the ``bold''
|
|
||||||
attribute. Attributes are interned and given numeric IDs,
|
|
||||||
so the number ``\verb|6|'' could represent
|
|
||||||
``\verb|(bold,true)|'', for example. This mapping is
|
|
||||||
stored in an attribute pool which may be shared by
|
|
||||||
multiple changesets.
|
|
||||||
|
|
||||||
Entries in the pool must be unique, so that attributes can
|
|
||||||
be compared by their IDs. Attribute names cannot contain
|
|
||||||
commas.
|
|
||||||
|
|
||||||
A changeset looks something like the following:
|
|
||||||
|
|
||||||
\begin{verbatim}
|
|
||||||
Z:5g>1|5=2p=v*4*5+1$x
|
|
||||||
\end{verbatim}
|
|
||||||
|
|
||||||
With the corresponding pool containing these entries (among others):
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item[] \verb|4| $\rightarrow$ \verb|(author,1059348573)|
|
|
||||||
\item[] \verb|5| $\rightarrow$ \verb|(bold,true)|
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
This changeset, together with the attribute pool,
|
|
||||||
represents inserting a bold letter ``x'' into the middle
|
|
||||||
of a line.
|
|
||||||
|
|
||||||
The string consists of:
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item a letter \verb|Z| (the ``magic character'' and
|
|
||||||
format version identifier)
|
|
||||||
\item a series punctuation marks (operation codes or
|
|
||||||
``opcodes'' for short), together with alphanumerics
|
|
||||||
(numeric values in base 36).
|
|
||||||
\item a dollar sign (\verb|$|)
|
|
||||||
\item a string of characters used for insertion operations
|
|
||||||
(the ``char bank'')
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
In the example above, if we separate out the operations
|
|
||||||
and convert the numbers to base 10, then we get:
|
|
||||||
\begin{verbatim}
|
|
||||||
Z :196 >1 |5=97 =31 *4 *5 +1 $x
|
|
||||||
\end{verbatim}
|
|
||||||
Here are descriptions of the operations, where capital
|
|
||||||
letters are variables:
|
|
||||||
|
|
||||||
\begin{description}
|
|
||||||
\item{{\bf :N}} \quad \\
|
|
||||||
Source text has length $N$ (must be first op)
|
|
||||||
\item{{\bf >N}} \quad \\
|
|
||||||
Final text is $N$ (positive) characters longer than source
|
|
||||||
text (must be second op)
|
|
||||||
\item{{\bf <N }} \quad \\
|
|
||||||
Final text is $N$ (positive) characters shorter than
|
|
||||||
source text (must be second op)
|
|
||||||
\item{{\bf >0 }} \quad \\
|
|
||||||
Final text is same length as source text
|
|
||||||
\item{{\bf +N }} \quad \\
|
|
||||||
Insert $N$ characters from the bank, none of them newlines
|
|
||||||
\item{{\bf -N}} \quad \\
|
|
||||||
Skip over (delete) $N$ characters from the source text,
|
|
||||||
none of them newlines
|
|
||||||
\item{{\bf =N}} \quad \\
|
|
||||||
Keep $N$ characters from the source text, none of them newlines
|
|
||||||
\item{{\bf |L+N}} \quad \\
|
|
||||||
Insert $N$ characters from the source text, containing $L$
|
|
||||||
newlines. The last character inserted MUST be a newline,
|
|
||||||
but not the (new) document's final newline.
|
|
||||||
\item{{\bf |L-N}} \quad \\
|
|
||||||
Delete $N$ characters from the source text, containing $L$
|
|
||||||
newlines. The last character inserted MUST be a newline,
|
|
||||||
but not the (old) document's final newline.
|
|
||||||
\item{{\bf |L=N}} \quad \\
|
|
||||||
Keep $N$ characters from the source text, containing L
|
|
||||||
newlines. The last character kept MUST be a newline, and
|
|
||||||
the final newline of the document is allowed.
|
|
||||||
\item{{\bf *I}} \quad \\
|
|
||||||
Apply attribute $I$ from the pool to the following
|
|
||||||
\verb|+|, \verb|=|, \verb_|+_, or \verb_|=_ command. In
|
|
||||||
other words, any number of \verb|*| ops can come before a
|
|
||||||
\verb_+_, \verb_=_, or \verb_|_ but not between a \verb_|_
|
|
||||||
and the corresponding \verb_+_ or \verb_=_. If \verb_+_,
|
|
||||||
text is inserted having this attribute. If \verb_=_, text
|
|
||||||
is kept but with the attribute applied as an attribute
|
|
||||||
addition or removal. Consecutive attributes must be sorted
|
|
||||||
lexically by (key,value) with key and value taken as
|
|
||||||
strings. It's illegal to have duplicate keys for
|
|
||||||
(key,value) pairs that apply to the same text. It's
|
|
||||||
illegal to have an empty value for a key in the case of an
|
|
||||||
insertion (\verb_+_), the pair should just be omitted.
|
|
||||||
\end{description}
|
|
||||||
|
|
||||||
Characters from the source text that aren't accounted for
|
|
||||||
are assumed to be kept with the same attributes.
|
|
||||||
|
|
||||||
\paragraph{Additional Constraints}
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item Consecutive \verb_+_, \verb_-_, and \verb_=_ ops of
|
|
||||||
the same type that could be combined are not allowed.
|
|
||||||
Whether combination is possible depends on the
|
|
||||||
attributes of the ops and whether each is multiline or
|
|
||||||
not. For example, two multiline deletions can never be
|
|
||||||
consecutive, nor can any insertion come after a
|
|
||||||
non-multiline insertion with the same attributes.
|
|
||||||
\item ``No-op'' ops are not allowed, such as deleting 0
|
|
||||||
characters. However, attribute applications that don't
|
|
||||||
have any effect are allowed.
|
|
||||||
\item Characters at the end of the source text cannot be
|
|
||||||
explicitly kept with no changes; if the change doesn't
|
|
||||||
affect the last $N$ characters, those ``keep'' ops must
|
|
||||||
be left off.
|
|
||||||
\item In any consecutive sequence of insertions (\verb_+_)
|
|
||||||
and deletions (\verb_-_) with no keeps (\verb_=_), the
|
|
||||||
deletions must come before the insertions.
|
|
||||||
\item The document text before and after will always end
|
|
||||||
with a newline. This policy avoids a lot of
|
|
||||||
special-casing of the end of the document. If a final
|
|
||||||
newline is always added when importing text and removed
|
|
||||||
when exporting text, then the changeset representation
|
|
||||||
can be used to process text files that may or may not
|
|
||||||
have a final newline.
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
\paragraph{Attribution string}
|
|
||||||
|
|
||||||
An \emph{attribution string} is a series of inserts with
|
|
||||||
no deletions or keeps. For example, ``\verb_*3+8|1+5_''
|
|
||||||
describes the attributes of a string of length 13, where
|
|
||||||
the first 8 chars have attribute 3 and the next 5 chars
|
|
||||||
have no attributes, with the last of these 5 chars being a
|
|
||||||
newline. Constraints apply similar to those affecting
|
|
||||||
changesets, but the restriction about the final newline of
|
|
||||||
the new document being added doesn't apply.
|
|
||||||
|
|
||||||
Attributes in an attribution string cannot be empty, like
|
|
||||||
``\verb|(bold,)|'', they should instead be absent.
|
|
||||||
|
|
||||||
|
|
||||||
\section{Further Considerations}
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item composing changesets/attributions with different
|
|
||||||
pools.
|
|
||||||
\item generalizing ``applyToAttribution'' to make
|
|
||||||
``mutateAttributionLines'' and ``compose''
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
\section{Using Unicode?}
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item no unicode (for efficient escaping, sightliness)
|
|
||||||
\item efficient operations for ACE and collab (attributed text, etc.)
|
|
||||||
\item good for time-slider
|
|
||||||
\item good for API
|
|
||||||
\item line-ending aware
|
|
||||||
X more coherent (deleting or styling text merging with insertion)
|
|
||||||
\item server-side syntax highlighting?
|
|
||||||
\item unify author map with attribute pool
|
|
||||||
\item unify attributed text with changeset rep
|
|
||||||
\item not: reversible
|
|
||||||
\item force final newline of document to be preserved
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
\paragraph{Unicode bad!}
|
|
||||||
|
|
||||||
\begin{itemize}
|
|
||||||
\item ugly (hard to read)
|
|
||||||
\item more complex to parse
|
|
||||||
\item harder to store and transmit correctly
|
|
||||||
\item doesn't save all that much space anyway
|
|
||||||
\item blows up in size when string-escaped
|
|
||||||
\item embarrassing for API
|
|
||||||
\end{itemize}
|
|
||||||
|
|
||||||
|
|
||||||
\end{document}
|
|
|
@ -1,133 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
Copied from the old Etherpad. Found in /infrastructure/ace/
|
|
||||||
|
|
||||||
Goals:
|
|
||||||
|
|
||||||
- no unicode (for efficient escaping, sightliness)
|
|
||||||
- efficient operations for ACE and collab (attributed text, etc.)
|
|
||||||
- good for time-slider
|
|
||||||
- good for API
|
|
||||||
- line-ending aware
|
|
||||||
X more coherent (deleting or styling text merging with insertion)
|
|
||||||
- server-side syntax highlighting?
|
|
||||||
- unify author map with attribute pool
|
|
||||||
- unify attributed text with changeset rep
|
|
||||||
- not: reversible
|
|
||||||
- force final newline of document to be preserved
|
|
||||||
|
|
||||||
- Unicode bad:
|
|
||||||
- ugly (hard to read)
|
|
||||||
- more complex to parse
|
|
||||||
- harder to store and transmit correctly
|
|
||||||
- doesn't save all that much space anyway
|
|
||||||
- blows up in size when string-escaped
|
|
||||||
- embarrassing for API
|
|
||||||
|
|
||||||
|
|
||||||
# Attributes:
|
|
||||||
|
|
||||||
An "attribute" is a (key,value) pair such as (author,abc123456) or
|
|
||||||
(bold,true). Sometimes an attribute is treated as an instruction to
|
|
||||||
add that attribute, in which case an empty value means to remove it.
|
|
||||||
So (bold,) removes the "bold" attribute. Attributes are interned and
|
|
||||||
given numeric IDs, so the number "6" could represent "(bold,true)",
|
|
||||||
for example. This mapping is stored in an attribute "pool" which may
|
|
||||||
be shared by multiple changesets.
|
|
||||||
|
|
||||||
Entries in the pool must be unique, so that attributes can be compared
|
|
||||||
by their IDs. Attribute names cannot contain commas.
|
|
||||||
|
|
||||||
A changeset looks something like the following:
|
|
||||||
|
|
||||||
Z:5g>1|5=2p=v*4*5+1$x
|
|
||||||
|
|
||||||
With the corresponding pool containing these entries:
|
|
||||||
|
|
||||||
...
|
|
||||||
4 -> (author,1059348573)
|
|
||||||
5 -> (bold,true)
|
|
||||||
...
|
|
||||||
|
|
||||||
This changeset, together with the pool, represents inserting
|
|
||||||
a bold letter "x" into the middle of a line. The string consists of:
|
|
||||||
|
|
||||||
- a letter Z (the "magic character" and format version identifier)
|
|
||||||
- a series of opcodes (punctuation) and numeric values in base 36 (the
|
|
||||||
alphanumerics)
|
|
||||||
- a dollar sign ($)
|
|
||||||
- a string of characters used by insertion operations (the "char bank")
|
|
||||||
|
|
||||||
If we separate out the operations and convert the numbers to base 10, we get:
|
|
||||||
|
|
||||||
Z :196 >1 |5=97 =31 *4 *5 +1 $"x"
|
|
||||||
|
|
||||||
Here are descriptions of the operations, where capital letters are variables:
|
|
||||||
|
|
||||||
":N" : Source text has length N (must be first op)
|
|
||||||
">N" : Final text is N (positive) characters longer than source text (must be second op)
|
|
||||||
"<N" : Final text is N (positive) characters shorter than source text (must be second op)
|
|
||||||
">0" : Final text is same length as source text
|
|
||||||
"+N" : Insert N characters from the bank, none of them newlines
|
|
||||||
"-N" : Skip over (delete) N characters from the source text, none of them newlines
|
|
||||||
"=N" : Keep N characters from the source text, none of them newlines
|
|
||||||
"|L+N" : Insert N characters from the source text, containing L newlines. The last
|
|
||||||
character inserted MUST be a newline, but not the (new) document's final newline.
|
|
||||||
"|L-N" : Delete N characters from the source text, containing L newlines. The last
|
|
||||||
character inserted MUST be a newline, but not the (old) document's final newline.
|
|
||||||
"|L=N" : Keep N characters from the source text, containing L newlines. The last character
|
|
||||||
kept MUST be a newline, and the final newline of the document is allowed.
|
|
||||||
"*I" : Apply attribute I from the pool to the following +, =, |+, or |= command.
|
|
||||||
In other words, any number of * ops can come before a +, =, or | but not
|
|
||||||
between a | and the corresponding + or =.
|
|
||||||
If +, text is inserted having this attribute. If =, text is kept but with
|
|
||||||
the attribute applied as an attribute addition or removal.
|
|
||||||
Consecutive attributes must be sorted lexically by (key,value) with key
|
|
||||||
and value taken as strings. It's illegal to have duplicate keys
|
|
||||||
for (key,value) pairs that apply to the same text. It's illegal to
|
|
||||||
have an empty value for a key in the case of an insertion (+), the
|
|
||||||
pair should just be omitted.
|
|
||||||
|
|
||||||
Characters from the source text that aren't accounted for are assumed to be kept
|
|
||||||
with the same attributes.
|
|
||||||
|
|
||||||
Additional Constraints:
|
|
||||||
|
|
||||||
- Consecutive +, -, and = ops of the same type that could be combined are not allowed.
|
|
||||||
Whether combination is possible depends on the attributes of the ops and whether
|
|
||||||
each is multiline or not. For example, two multiline deletions can never be
|
|
||||||
consecutive, nor can any insertion come after a non-multiline insertion with the
|
|
||||||
same attributes.
|
|
||||||
- "No-op" ops are not allowed, such as deleting 0 characters. However, attribute
|
|
||||||
applications that don't have any effect are allowed.
|
|
||||||
- Characters at the end of the source text cannot be explicitly kept with no changes;
|
|
||||||
if the change doesn't affect the last N characters, those "keep" ops must be left off.
|
|
||||||
- In any consecutive sequence of insertions (+) and deletions (-) with no keeps (=),
|
|
||||||
the deletions must come before the insertions.
|
|
||||||
- The document text before and after will always end with a newline. This policy avoids
|
|
||||||
a lot of special-casing of the end of the document. If a final newline is
|
|
||||||
always added when importing text and removed when exporting text, then the
|
|
||||||
changeset representation can be used to process text files that may or may not
|
|
||||||
have a final newline.
|
|
||||||
|
|
||||||
Attribution string:
|
|
||||||
|
|
||||||
An "attribution string" is a series of inserts with no deletions or keeps.
|
|
||||||
For example, "*3+8|1+5" describes the attributes of a string of length 13,
|
|
||||||
where the first 8 chars have attribute 3 and the next 5 chars have no
|
|
||||||
attributes, with the last of these 5 chars being a newline. Constraints
|
|
||||||
apply similar to those affecting changesets, but the restriction about
|
|
||||||
the final newline of the new document being added doesn't apply.
|
|
||||||
|
|
||||||
Attributes in an attribution string cannot be empty, like "(bold,)", they should
|
|
||||||
instead be absent.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-------
|
|
||||||
Considerations:
|
|
||||||
|
|
||||||
- composing changesets/attributions with different pools
|
|
||||||
- generalizing "applyToAttribution" to make "mutateAttributionLines" and "compose"
|
|
|
@ -1,7 +0,0 @@
|
||||||
@include documentation
|
|
||||||
@include stats
|
|
||||||
@include localization
|
|
||||||
@include custom_static
|
|
||||||
@include api/api
|
|
||||||
@include plugins
|
|
||||||
@include database
|
|
|
@ -1,94 +0,0 @@
|
||||||
# Localization
|
|
||||||
Etherpad provides a multi-language user interface, that's apart from your users' content, so users from different countries can collaborate on a single document, while still having the user interface displayed in their mother tongue.
|
|
||||||
|
|
||||||
|
|
||||||
## Translating
|
|
||||||
We rely on https://translatewiki.net to handle the translation process for us, so if you'd like to help...
|
|
||||||
|
|
||||||
1. Sign up at https://translatewiki.net
|
|
||||||
2. Visit our [TWN project page](https://translatewiki.net/wiki/Translating:Etherpad_lite)
|
|
||||||
3. Click on `Translate Etherpad lite interface`
|
|
||||||
4. Choose a target language, you'd like to translate our interface to, and hit `Fetch`
|
|
||||||
5. Start translating!
|
|
||||||
|
|
||||||
Translations will be send back to us regularly and will eventually appear in the next release.
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
|
|
||||||
### Server-side
|
|
||||||
`/src/locales` contains files for all supported languages which contain the translated strings. Translation files are simple `*.json` files and look like this:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{ "pad.modals.connected": "Connecté."
|
|
||||||
, "pad.modals.uderdup": "Ouvrir dans une nouvelle fenêtre."
|
|
||||||
, "pad.toolbar.unindent.title": "Dèsindenter"
|
|
||||||
, "pad.toolbar.undo.title": "Annuler (Ctrl-Z)"
|
|
||||||
, "timeslider.pageTitle": "{{appTitle}} Curseur temporel"
|
|
||||||
, ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Each translation consists of a key (the id of the string that is to be translated) and the translated string. Terms in curly braces must not be touched but left as they are, since they represent a dynamically changing part of the string like a variable. Imagine a message welcoming a user: `Welcome, {{userName}}!` would be translated as `Ahoy, {{userName}}!` in pirate.
|
|
||||||
|
|
||||||
### Client-side
|
|
||||||
We use a `language` cookie to save your language settings if you change them. If you don't, we autodetect your locale using information from your browser. Then, the preferred language is fed into a library called [html10n.js](https://github.com/marcelklehr/html10n.js), which loads the appropriate translations and applies them to our templates. Its features include translation params, pluralization, include rules and a nice javascript API.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Localizing plugins
|
|
||||||
|
|
||||||
### 1. Mark the strings to translate
|
|
||||||
|
|
||||||
In the template files of your plugin, change all hardcoded messages/strings...
|
|
||||||
|
|
||||||
from:
|
|
||||||
```html
|
|
||||||
<option value="0">Heading 1</option>
|
|
||||||
```
|
|
||||||
to:
|
|
||||||
```html
|
|
||||||
<option data-l10n-id="ep_heading.h1" value="0"></option>
|
|
||||||
```
|
|
||||||
|
|
||||||
In the javascript files of your plugin, change all hardcoded messages/strings...
|
|
||||||
|
|
||||||
from:
|
|
||||||
```js
|
|
||||||
alert ('Chat');
|
|
||||||
```
|
|
||||||
to:
|
|
||||||
```js
|
|
||||||
alert(window._('pad.chat'));
|
|
||||||
```
|
|
||||||
### 2. Create translate files in the locales directory of your plugin
|
|
||||||
|
|
||||||
* The name of the file must be the language code of the language it contains translations for (see [supported lang codes](https://joker-x.github.com/languages4translatewiki/test/); e.g. en ? English, es ? Spanish...)
|
|
||||||
* The extension of the file must be `.json`
|
|
||||||
* The default language is English, so your plugin should always provide `en.json`
|
|
||||||
* In order to avoid naming conflicts, your message keys should start with the name of your plugin followed by a dot (see below)
|
|
||||||
|
|
||||||
*ep_your-plugin/locales/en.json*
|
|
||||||
```
|
|
||||||
{ "ep_your-plugin.h1": "Heading 1"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*ep_your-plugin/locales/es.json*
|
|
||||||
```
|
|
||||||
{ "ep_your-plugin.h1": "Título 1"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Every time the http server is started, it will auto-detect your messages and merge them automatically with the core messages.
|
|
||||||
|
|
||||||
### Overwrite core messages
|
|
||||||
|
|
||||||
You can overwrite Etherpad's core messages in your plugin's locale files.
|
|
||||||
For example, if you want to replace `Chat` with `Notes`, simply add...
|
|
||||||
|
|
||||||
*ep_your-plugin/locales/en.json*
|
|
||||||
```
|
|
||||||
{ "ep_your-plugin.h1": "Heading 1"
|
|
||||||
, "pad.chat": "Notes"
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,119 +0,0 @@
|
||||||
# Plugins
|
|
||||||
Etherpad allows you to extend its functionality with plugins. A plugin registers hooks (functions) for certain events (thus certain features) in Etherpad-lite to execute its own functionality based on these events.
|
|
||||||
|
|
||||||
Publicly available plugins can be found in the npm registry (see <https://npmjs.org>). Etherpad-lite's naming convention for plugins is to prefix your plugins with `ep_`. So, e.g. it's `ep_flubberworms`. Thus you can install plugins from npm, using `npm install ep_flubberworm` in etherpad-lite's root directory.
|
|
||||||
|
|
||||||
You can also browse to `http://yourEtherpadInstan.ce/admin/plugins`, which will list all installed plugins and those available on npm. It even provides functionality to search through all available plugins.
|
|
||||||
|
|
||||||
## Folder structure
|
|
||||||
A basic plugin usually has the following folder structure:
|
|
||||||
```
|
|
||||||
ep_<plugin>/
|
|
||||||
| static/
|
|
||||||
| templates/
|
|
||||||
| locales/
|
|
||||||
+ ep.json
|
|
||||||
+ package.json
|
|
||||||
```
|
|
||||||
If your plugin includes client-side hooks, put them in `static/js/`. If you're adding in CSS or image files, you should put those files in `static/css/ `and `static/image/`, respectively, and templates go into `templates/`. Translations go into `locales/`
|
|
||||||
|
|
||||||
A Standard directory structure like this makes it easier to navigate through your code. That said, do note, that this is not actually *required* to make your plugin run. If you want to make use of our i18n system, you need to put your translations into `locales/`, though, in order to have them integrated. (See "Localization" for more info on how to localize your plugin)
|
|
||||||
|
|
||||||
## Plugin definition
|
|
||||||
Your plugin definition goes into `ep.json`. In this file you register your hooks, indicate the parts of your plugin and the order of execution. (A documentation of all available events to hook into can be found in chapter [hooks](#all_hooks).)
|
|
||||||
|
|
||||||
A hook registration is a pairs of a hook name and a function reference (filename to require() + exported function name)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"parts": [
|
|
||||||
{
|
|
||||||
"name": "nameThisPartHoweverYouWant",
|
|
||||||
"hooks": {
|
|
||||||
"authenticate" : "ep_<plugin>/<file>:FUNCTIONNAME1",
|
|
||||||
"expressCreateServer": "ep_<plugin>/<file>:FUNCTIONNAME2"
|
|
||||||
},
|
|
||||||
"client_hooks": {
|
|
||||||
"acePopulateDOMLine": "ep_plugin/<file>:FUNCTIONNAME3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Etherpad-lite will expect the part of the hook definition before the colon to be a javascript file and will try to require it. The part after the colon is expected to be a valid function identifier of that module. So, you have to export your hooks, using [`module.exports`](https://nodejs.org/docs/latest/api/modules.html#modules_modules) and register it in `ep.json` as `ep_<plugin>/path/to/<file>:FUNCTIONNAME`.
|
|
||||||
You can omit the `FUNCTIONNAME` part, if the exported function has got the same name as the hook. So `"authorize" : "ep_flubberworm/foo"` will call the function `exports.authorize` in `ep_flubberworm/foo.js`
|
|
||||||
|
|
||||||
### Client hooks and server hooks
|
|
||||||
There are server hooks, which will be executed on the server (e.g. `expressCreateServer`), and there are client hooks, which are executed on the client (e.g. `acePopulateDomLine`). Be sure to not make assumptions about the environment your code is running in, e.g. don't try to access `process`, if you know your code will be run on the client, and likewise, don't try to access `window` on the server...
|
|
||||||
|
|
||||||
### Parts
|
|
||||||
As your plugins become more and more complex, you will find yourself in the need to manage dependencies between plugins. E.g. you want the hooks of a certain plugin to be executed before (or after) yours. You can also manage these dependencies in your plugin definition file `ep.json`:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
"parts": [
|
|
||||||
{
|
|
||||||
"name": "onepart",
|
|
||||||
"pre": [],
|
|
||||||
"post": ["ep_onemoreplugin/partone"]
|
|
||||||
"hooks": {
|
|
||||||
"storeBar": "ep_monospace/plugin:storeBar",
|
|
||||||
"getFoo": "ep_monospace/plugin:getFoo",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "otherpart",
|
|
||||||
"pre": ["ep_my_example/somepart", "ep_otherplugin/main"],
|
|
||||||
"post": [],
|
|
||||||
"hooks": {
|
|
||||||
"someEvent": "ep_my_example/otherpart:someEvent",
|
|
||||||
"another": "ep_my_example/otherpart:another"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Usually a plugin will add only one functionality at a time, so it will probably only use one `part` definition to register its hooks. However, sometimes you have to put different (unrelated) functionalities into one plugin. For this you will want use parts, so other plugins can depend on them.
|
|
||||||
|
|
||||||
#### pre/post
|
|
||||||
The `"pre"` and `"post"` definitions, affect the order in which parts of a plugin are executed. This ensures that plugins and their hooks are executed in the correct order.
|
|
||||||
|
|
||||||
`"pre"` lists parts that must be executed *before* the defining part. `"post"` lists parts that must be executed *after* the defining part.
|
|
||||||
|
|
||||||
You can, on a basic level, think of this as double-ended dependency listing. If you have a dependency on another plugin, you can make sure it loads before yours by putting it in `"pre"`. If you are setting up things that might need to be used by a plugin later, you can ensure proper order by putting it in `"post"`.
|
|
||||||
|
|
||||||
Note that it would be far more sane to use `"pre"` in almost any case, but if you want to change config variables for another plugin, or maybe modify its environment, `"post"` could definitely be useful.
|
|
||||||
|
|
||||||
Also, note that dependencies should *also* be listed in your package.json, so they can be `npm install`'d automagically when your plugin gets installed.
|
|
||||||
|
|
||||||
## Package definition
|
|
||||||
Your plugin must also contain a [package definition file](https://docs.npmjs.com/files/package.json), called package.json, in the project root - this file contains various metadata relevant to your plugin, such as the name and version number, author, project hompage, contributors, a short description, etc. If you publish your plugin on npm, these metadata are used for package search etc., but it's necessary for Etherpad-lite plugins, even if you don't publish your plugin.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "ep_PLUGINNAME",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"description": "DESCRIPTION",
|
|
||||||
"author": "USERNAME (REAL NAME) <MAIL@EXAMPLE.COM>",
|
|
||||||
"contributors": [],
|
|
||||||
"dependencies": {"MODULE": "0.3.20"},
|
|
||||||
"engines": { "node": ">= 0.6.0"}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Templates
|
|
||||||
If your plugin adds or modifies the front end HTML (e.g. adding buttons or changing their functions), you should put the necessary HTML code for such operations in `templates/`, in files of type ".ejs", since Etherpad uses EJS for HTML templating. See the following link for more information about EJS: <https://github.com/visionmedia/ejs>.
|
|
||||||
|
|
||||||
## Writing and running front-end tests for your plugin
|
|
||||||
|
|
||||||
Etherpad allows you to easily create front-end tests for plugins.
|
|
||||||
|
|
||||||
1. Create a new folder
|
|
||||||
```
|
|
||||||
%your_plugin%/static/tests/frontend/specs
|
|
||||||
```
|
|
||||||
2. Put your spec file in here (Example spec files are visible in %etherpad_root_folder%/frontend/tests/specs)
|
|
||||||
|
|
||||||
3. Visit http://yourserver.com/frontend/tests your front-end tests will run.
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Statistics
|
|
||||||
Etherpad keeps track of the goings-on inside the edit machinery. If you'd like to have a look at this, just point your browser to `/stats`.
|
|
||||||
|
|
||||||
We currently measure:
|
|
||||||
|
|
||||||
- totalUsers (counter)
|
|
||||||
- connects (meter)
|
|
||||||
- disconnects (meter)
|
|
||||||
- pendingEdits (counter)
|
|
||||||
- edits (timer)
|
|
||||||
- failedChangesets (meter)
|
|
||||||
- httpRequests (timer)
|
|
||||||
- http500 (meter)
|
|
||||||
- memoryUsage (gauge)
|
|
||||||
|
|
||||||
Under the hood, we are happy to rely on [measured](https://github.com/felixge/node-measured) for all our metrics needs.
|
|
||||||
|
|
||||||
To modify or simply access our stats in your plugin, simply `require('ep_etherpad-lite/stats')` which is a `measured.Collection`.
|
|
|
@ -1,23 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>__SECTION__ - Etherpad v__VERSION__ Manual & Documentation</title>
|
|
||||||
<link rel="stylesheet" href="assets/style.css">
|
|
||||||
</head>
|
|
||||||
<body class="apidoc" id="api-section-__FILENAME__">
|
|
||||||
<header id="header">
|
|
||||||
<h1>Etherpad v__VERSION__ Manual & Documentation</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div id="toc">
|
|
||||||
<h2>Table of Contents</h2>
|
|
||||||
__TOC__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="apicontent">
|
|
||||||
__CONTENT__
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,271 +0,0 @@
|
||||||
/*
|
|
||||||
This file must be valid JSON. But comments are allowed
|
|
||||||
|
|
||||||
Please edit settings.json, not settings.json.template
|
|
||||||
|
|
||||||
To still commit settings without credentials you can
|
|
||||||
store any credential settings in credentials.json
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
// Name your instance!
|
|
||||||
"title": "Etherpad",
|
|
||||||
|
|
||||||
// favicon default name
|
|
||||||
// alternatively, set up a fully specified Url to your own favicon
|
|
||||||
"favicon": "favicon.ico",
|
|
||||||
|
|
||||||
//IP and port which etherpad should bind at
|
|
||||||
"ip": "0.0.0.0",
|
|
||||||
"port" : 9001,
|
|
||||||
|
|
||||||
// Option to hide/show the settings.json in admin page, default option is set to true
|
|
||||||
"showSettingsInAdminPage" : true,
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Node native SSL support
|
|
||||||
// this is disabled by default
|
|
||||||
//
|
|
||||||
// make sure to have the minimum and correct file access permissions set
|
|
||||||
// so that the Etherpad server can access them
|
|
||||||
|
|
||||||
"ssl" : {
|
|
||||||
"key" : "/path-to-your/epl-server.key",
|
|
||||||
"cert" : "/path-to-your/epl-server.crt",
|
|
||||||
"ca": ["/path-to-your/epl-intermediate-cert1.crt", "/path-to-your/epl-intermediate-cert2.crt"]
|
|
||||||
},
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
//The Type of the database. You can choose between dirty, postgres, sqlite and mysql
|
|
||||||
//You shouldn't use "dirty" for for anything else than testing or development
|
|
||||||
"dbType" : "dirty",
|
|
||||||
//the database specific settings
|
|
||||||
"dbSettings" : {
|
|
||||||
"filename" : "var/dirty.db"
|
|
||||||
},
|
|
||||||
|
|
||||||
/* An Example of MySQL Configuration
|
|
||||||
"dbType" : "mysql",
|
|
||||||
"dbSettings" : {
|
|
||||||
"user" : "root",
|
|
||||||
"host" : "localhost",
|
|
||||||
"password": "",
|
|
||||||
"database": "store",
|
|
||||||
"charset" : "utf8mb4"
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
//the default text of a pad
|
|
||||||
"defaultPadText" : "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at http:\/\/etherpad.org\n",
|
|
||||||
|
|
||||||
/* Default Pad behavior, users can override by changing */
|
|
||||||
"padOptions": {
|
|
||||||
"noColors": false,
|
|
||||||
"showControls": true,
|
|
||||||
"showChat": true,
|
|
||||||
"showLineNumbers": true,
|
|
||||||
"useMonospaceFont": false,
|
|
||||||
"userName": false,
|
|
||||||
"userColor": false,
|
|
||||||
"rtl": false,
|
|
||||||
"alwaysShowChat": false,
|
|
||||||
"chatAndUsers": false,
|
|
||||||
"lang": "en-gb"
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Pad Shortcut Keys */
|
|
||||||
"padShortcutEnabled" : {
|
|
||||||
"altF9" : true, /* focus on the File Menu and/or editbar */
|
|
||||||
"altC" : true, /* focus on the Chat window */
|
|
||||||
"cmdShift2" : true, /* shows a gritter popup showing a line author */
|
|
||||||
"delete" : true,
|
|
||||||
"return" : true,
|
|
||||||
"esc" : true, /* in mozilla versions 14-19 avoid reconnecting pad */
|
|
||||||
"cmdS" : true, /* save a revision */
|
|
||||||
"tab" : true, /* indent */
|
|
||||||
"cmdZ" : true, /* undo/redo */
|
|
||||||
"cmdY" : true, /* redo */
|
|
||||||
"cmdI" : true, /* italic */
|
|
||||||
"cmdB" : true, /* bold */
|
|
||||||
"cmdU" : true, /* underline */
|
|
||||||
"cmd5" : true, /* strike through */
|
|
||||||
"cmdShiftL" : true, /* unordered list */
|
|
||||||
"cmdShiftN" : true, /* ordered list */
|
|
||||||
"cmdShift1" : true, /* ordered list */
|
|
||||||
"cmdShiftC" : true, /* clear authorship */
|
|
||||||
"cmdH" : true, /* backspace */
|
|
||||||
"ctrlHome" : true, /* scroll to top of pad */
|
|
||||||
"pageUp" : true,
|
|
||||||
"pageDown" : true
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Should we suppress errors from being visible in the default Pad Text? */
|
|
||||||
"suppressErrorsInPadText" : false,
|
|
||||||
|
|
||||||
/* Users must have a session to access pads. This effectively allows only group pads to be accessed. */
|
|
||||||
"requireSession" : false,
|
|
||||||
|
|
||||||
/* Users may edit pads but not create new ones. Pad creation is only via the API. This applies both to group pads and regular pads. */
|
|
||||||
"editOnly" : false,
|
|
||||||
|
|
||||||
/* Users, who have a valid session, automatically get granted access to password protected pads */
|
|
||||||
"sessionNoPassword" : false,
|
|
||||||
|
|
||||||
/* if true, all css & js will be minified before sending to the client. This will improve the loading performance massivly,
|
|
||||||
but makes it impossible to debug the javascript/css */
|
|
||||||
"minify" : true,
|
|
||||||
|
|
||||||
/* How long may clients use served javascript code (in seconds)? Without versioning this
|
|
||||||
may cause problems during deployment. Set to 0 to disable caching */
|
|
||||||
"maxAge" : 21600, // 60 * 60 * 6 = 6 hours
|
|
||||||
|
|
||||||
/* This is the absolute path to the Abiword executable. Setting it to null, disables abiword.
|
|
||||||
Abiword is needed to advanced import/export features of pads*/
|
|
||||||
"abiword" : null,
|
|
||||||
|
|
||||||
/* This is the absolute path to the soffice executable. Setting it to null, disables LibreOffice exporting.
|
|
||||||
LibreOffice can be used in lieu of Abiword to export pads */
|
|
||||||
"soffice" : null,
|
|
||||||
|
|
||||||
/* This is the path to the Tidy executable. Setting it to null, disables Tidy.
|
|
||||||
Tidy is used to improve the quality of exported pads*/
|
|
||||||
"tidyHtml" : null,
|
|
||||||
|
|
||||||
/* Allow import of file types other than the supported types: txt, doc, docx, rtf, odt, html & htm */
|
|
||||||
"allowUnknownFileEnds" : true,
|
|
||||||
|
|
||||||
/* This setting is used if you require authentication of all users.
|
|
||||||
Note: /admin always requires authentication. */
|
|
||||||
"requireAuthentication" : false,
|
|
||||||
|
|
||||||
/* Require authorization by a module, or a user with is_admin set, see below. */
|
|
||||||
"requireAuthorization" : false,
|
|
||||||
|
|
||||||
/*when you use NginX or another proxy/ load-balancer set this to true*/
|
|
||||||
"trustProxy" : false,
|
|
||||||
|
|
||||||
/* Privacy: disable IP logging */
|
|
||||||
"disableIPlogging" : false,
|
|
||||||
|
|
||||||
/* Time (in seconds) to automatically reconnect pad when a "Force reconnect"
|
|
||||||
message is shown to user. Set to 0 to disable automatic reconnection */
|
|
||||||
"automaticReconnectionTimeout" : 0,
|
|
||||||
/*
|
|
||||||
* By default, when caret is moved out of viewport, it scrolls the minimum height needed to make this
|
|
||||||
* line visible.
|
|
||||||
*/
|
|
||||||
"scrollWhenFocusLineIsOutOfViewport": {
|
|
||||||
/*
|
|
||||||
* Percentage of viewport height to be additionally scrolled.
|
|
||||||
* E.g use "percentage.editionAboveViewport": 0.5, to place caret line in the
|
|
||||||
* middle of viewport, when user edits a line above of the viewport
|
|
||||||
* Set to 0 to disable extra scrolling
|
|
||||||
*/
|
|
||||||
"percentage": {
|
|
||||||
"editionAboveViewport": 0,
|
|
||||||
"editionBelowViewport": 0
|
|
||||||
},
|
|
||||||
/* Time (in milliseconds) used to animate the scroll transition. Set to 0 to disable animation */
|
|
||||||
"duration": 0,
|
|
||||||
/*
|
|
||||||
* Flag to control if it should scroll when user places the caret in the last line of the viewport
|
|
||||||
*/
|
|
||||||
"scrollWhenCaretIsInTheLastLineOfViewport": false,
|
|
||||||
/*
|
|
||||||
* Percentage of viewport height to be additionally scrolled when user presses arrow up
|
|
||||||
* in the line of the top of the viewport.
|
|
||||||
* Set to 0 to let the scroll to be handled as default by the Etherpad
|
|
||||||
*/
|
|
||||||
"percentageToScrollWhenUserPressesArrowUp": 0
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Users for basic authentication. is_admin = true gives access to /admin.
|
|
||||||
If you do not uncomment this, /admin will not be available! */
|
|
||||||
/*
|
|
||||||
"users": {
|
|
||||||
"admin": {
|
|
||||||
"password": "changeme1",
|
|
||||||
"is_admin": true
|
|
||||||
},
|
|
||||||
"user": {
|
|
||||||
"password": "changeme1",
|
|
||||||
"is_admin": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
// restrict socket.io transport methods
|
|
||||||
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
|
|
||||||
|
|
||||||
// Allow Load Testing tools to hit the Etherpad Instance. Warning this will disable security on the instance.
|
|
||||||
"loadTest": false,
|
|
||||||
|
|
||||||
// Disable indentation on new line when previous line ends with some special chars (':', '[', '(', '{')
|
|
||||||
/*
|
|
||||||
"indentationOnNewLine": false,
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* The toolbar buttons configuration.
|
|
||||||
"toolbar": {
|
|
||||||
"left": [
|
|
||||||
["bold", "italic", "underline", "strikethrough"],
|
|
||||||
["orderedlist", "unorderedlist", "indent", "outdent"],
|
|
||||||
["undo", "redo"],
|
|
||||||
["clearauthorship"]
|
|
||||||
],
|
|
||||||
"right": [
|
|
||||||
["importexport", "timeslider", "savedrevision"],
|
|
||||||
["settings", "embed"],
|
|
||||||
["showusers"]
|
|
||||||
],
|
|
||||||
"timeslider": [
|
|
||||||
["timeslider_export", "timeslider_returnToPad"]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
|
|
||||||
"loglevel": "INFO",
|
|
||||||
|
|
||||||
//Logging configuration. See log4js documentation for further information
|
|
||||||
// https://github.com/nomiddlename/log4js-node
|
|
||||||
// You can add as many appenders as you want here:
|
|
||||||
"logconfig" :
|
|
||||||
{ "appenders": [
|
|
||||||
{ "type": "console"
|
|
||||||
//, "category": "access"// only logs pad access
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
, { "type": "file"
|
|
||||||
, "filename": "your-log-file-here.log"
|
|
||||||
, "maxLogSize": 1024
|
|
||||||
, "backups": 3 // how many log files there're gonna be at max
|
|
||||||
//, "category": "test" // only log a specific category
|
|
||||||
}*/
|
|
||||||
/*
|
|
||||||
, { "type": "logLevelFilter"
|
|
||||||
, "level": "warn" // filters out all log messages that have a lower level than "error"
|
|
||||||
, "appender":
|
|
||||||
{ Use whatever appender you want here }
|
|
||||||
}*/
|
|
||||||
/*
|
|
||||||
, { "type": "logLevelFilter"
|
|
||||||
, "level": "error" // filters out all log messages that have a lower level than "error"
|
|
||||||
, "appender":
|
|
||||||
{ "type": "smtp"
|
|
||||||
, "subject": "An error occurred in your EPL instance!"
|
|
||||||
, "recipients": "bar@blurdybloop.com, baz@blurdybloop.com"
|
|
||||||
, "sendInterval": 300 // 60 * 5 = 5 minutes -- will buffer log messages; set to 0 to send a mail for every message
|
|
||||||
, "transport": "SMTP", "SMTP": { // see https://github.com/andris9/Nodemailer#possible-transport-methods
|
|
||||||
"host": "smtp.example.com", "port": 465,
|
|
||||||
"secureConnection": true,
|
|
||||||
"auth": {
|
|
||||||
"user": "foo@example.com",
|
|
||||||
"pass": "bar_foo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Ignore this file and see the file in the base installation folder
|
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"parts": [
|
|
||||||
{ "name": "express", "hooks": {
|
|
||||||
"createServer": "ep_etherpad-lite/node/hooks/express:createServer",
|
|
||||||
"restartServer": "ep_etherpad-lite/node/hooks/express:restartServer"
|
|
||||||
} },
|
|
||||||
{ "name": "static", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/static:expressCreateServer" } },
|
|
||||||
{ "name": "i18n", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/i18n:expressCreateServer" } },
|
|
||||||
{ "name": "specialpages", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/specialpages:expressCreateServer" } },
|
|
||||||
{ "name": "padurlsanitize", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padurlsanitize:expressCreateServer" } },
|
|
||||||
{ "name": "padreadonly", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padreadonly:expressCreateServer" } },
|
|
||||||
{ "name": "webaccess", "hooks": { "expressConfigure": "ep_etherpad-lite/node/hooks/express/webaccess:expressConfigure" } },
|
|
||||||
{ "name": "apicalls", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/apicalls:expressCreateServer" } },
|
|
||||||
{ "name": "importexport", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/importexport:expressCreateServer" } },
|
|
||||||
{ "name": "errorhandling", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling:expressCreateServer" } },
|
|
||||||
{ "name": "socketio", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio:expressCreateServer" } },
|
|
||||||
{ "name": "tests", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/tests:expressCreateServer" } },
|
|
||||||
{ "name": "admin", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/admin:expressCreateServer" } },
|
|
||||||
{ "name": "adminplugins", "hooks": {
|
|
||||||
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins:expressCreateServer",
|
|
||||||
"socketio": "ep_etherpad-lite/node/hooks/express/adminplugins:socketio" }
|
|
||||||
},
|
|
||||||
{ "name": "adminsettings", "hooks": {
|
|
||||||
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminsettings:expressCreateServer",
|
|
||||||
"socketio": "ep_etherpad-lite/node/hooks/express/adminsettings:socketio" }
|
|
||||||
},
|
|
||||||
{ "name": "swagger", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/swagger:expressCreateServer" } }
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
version="1.1"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
id="svg2987">
|
|
||||||
<defs
|
|
||||||
id="defs2989" />
|
|
||||||
<metadata
|
|
||||||
id="metadata2992">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
transform="translate(0,-1036.3618)"
|
|
||||||
id="layer1">
|
|
||||||
<path
|
|
||||||
d="m 5.0621446,1039.5621 c 5.6960494,0 6.2053964,0 6.2053964,0 l -0.0238,-0.8981 -3.131226,-0.018 -3.1312269,-0.018 z"
|
|
||||||
id="path3806"
|
|
||||||
style="fill:#464646;fill-opacity:1;stroke:#464646;stroke-width:0.68021488;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" />
|
|
||||||
<path
|
|
||||||
d="m 12.4892,1052.3561 0,-2.1733 2.173211,0 0,2.1733 z"
|
|
||||||
id="path3770-7-0-9"
|
|
||||||
style="fill:#b1b1b1;fill-opacity:1;stroke:none" />
|
|
||||||
<path
|
|
||||||
d="m 1.6231475,1052.3561 0,-2.1733 2.1732107,0 0,2.1733 z"
|
|
||||||
id="path3770-7-0"
|
|
||||||
style="fill:#b1b1b1;fill-opacity:1;stroke:none" />
|
|
||||||
<path
|
|
||||||
d="m 2.7097528,1041.49 2.1732106,-1.0866 0,-2.1251"
|
|
||||||
id="path3775"
|
|
||||||
style="fill:none;stroke:#aeaeae;stroke-width:0.54330266;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" />
|
|
||||||
<path
|
|
||||||
d="m 13.575806,1041.49 -2.17321,-1.0866 0.0065,-2.0833"
|
|
||||||
id="path3777"
|
|
||||||
style="fill:none;stroke:#aeaeae;stroke-width:0.54330266;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" />
|
|
||||||
<path
|
|
||||||
d="m 1.6231475,1041.49 c 0.014561,3.8432 -0.019342,6.178 0,7.6063 0.030207,2.23 0,2.1732 0,2.1732 1.1853886,0 1.1853886,0 1.1853886,1.0866 l 10.6684869,0 c 0,-1.0866 0,-1.0866 1.185388,-1.0866 0,-3.26 0,-6.5198 0,-9.7795 z"
|
|
||||||
id="path3019"
|
|
||||||
style="fill:#464646;fill-opacity:1;stroke:none" />
|
|
||||||
<path
|
|
||||||
d="m 3.7963582,1050.1828 c 6.3865768,0 8.6928418,0 8.6928418,0"
|
|
||||||
id="path3023"
|
|
||||||
style="fill:none;stroke:#09c900;stroke-width:1.07546031;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" />
|
|
||||||
<path
|
|
||||||
d="m 3.7963582,1048.0095 8.6928418,0"
|
|
||||||
id="path3025"
|
|
||||||
style="fill:none;stroke:#fcc200;stroke-width:1.1616298;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" />
|
|
||||||
<path
|
|
||||||
d="m 3.7963582,1045.8365 8.6928418,0"
|
|
||||||
id="path3027"
|
|
||||||
style="fill:none;stroke:#00a3fb;stroke-width:1.1616298;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" />
|
|
||||||
<path
|
|
||||||
d="m 3.7963582,1043.6632 8.6928418,0"
|
|
||||||
id="path3029"
|
|
||||||
style="fill:none;stroke:#ff4091;stroke-width:1.1616298;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" />
|
|
||||||
<path
|
|
||||||
d="m 1.6231475,1042.5766 0,-4.3464 1.0866053,0 0,4.3464 z"
|
|
||||||
id="path3770"
|
|
||||||
style="fill:#464646;fill-opacity:1;stroke:none" />
|
|
||||||
<path
|
|
||||||
d="m 13.575806,1042.5766 0,-4.3464 1.086605,0 0,4.3464 z"
|
|
||||||
id="path3770-1"
|
|
||||||
style="fill:#464646;fill-opacity:1;stroke:none" />
|
|
||||||
<path
|
|
||||||
d="m 1.6231475,1038.2302 0,-1.0867 1.0866053,0 0,1.0867 z"
|
|
||||||
id="path3770-7"
|
|
||||||
style="fill:#b1b1b1;fill-opacity:1;stroke:none" />
|
|
||||||
<path
|
|
||||||
d="m 13.575806,1038.2302 0,-1.0867 1.086605,0 0,1.0867 z"
|
|
||||||
id="path3770-4"
|
|
||||||
style="fill:#b1b1b1;fill-opacity:1;stroke:none" />
|
|
||||||
<path
|
|
||||||
d="m 5.3413804,1038.5358 c 2.6160566,-2.5761 3.1649988,-2.3796 5.6028086,0.018"
|
|
||||||
id="path3767"
|
|
||||||
style="fill:none;stroke:#464646;stroke-width:0.54330266;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 4.1 KiB |
|
@ -1,75 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Naudefj",
|
|
||||||
"Fwolff"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "Nuwe pad",
|
|
||||||
"index.createOpenPad": "of skep/open 'n pad met die naam:",
|
|
||||||
"pad.toolbar.bold.title": "Vet (Ctrl-B)",
|
|
||||||
"pad.toolbar.italic.title": "Kursief (Ctrl-I)",
|
|
||||||
"pad.toolbar.underline.title": "Onderstreep (Ctrl-U)",
|
|
||||||
"pad.toolbar.strikethrough.title": "Deurgehaal (Ctrl+5)",
|
|
||||||
"pad.toolbar.ol.title": "Geordende lys (Ctrl+Shift+N)",
|
|
||||||
"pad.toolbar.ul.title": "Ongeordende lys (Ctrl+Shift+L)",
|
|
||||||
"pad.toolbar.indent.title": "Indenteer (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "Verklein indentering (Shift+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "Ongedaan maak (Ctrl-Z)",
|
|
||||||
"pad.toolbar.redo.title": "Herdoen (Ctrl-Y)",
|
|
||||||
"pad.toolbar.clearAuthorship.title": "Verwyder skrywers se kleure (Ctrl+Shift+C)",
|
|
||||||
"pad.toolbar.import_export.title": "Voer in/uit van/na verskillende lêerformate",
|
|
||||||
"pad.toolbar.settings.title": "Voorkeure",
|
|
||||||
"pad.colorpicker.save": "Stoor",
|
|
||||||
"pad.colorpicker.cancel": "Kanselleer",
|
|
||||||
"pad.loading": "Laai...",
|
|
||||||
"pad.settings.myView": "My oorsig",
|
|
||||||
"pad.settings.fontType.normal": "Normaal",
|
|
||||||
"pad.settings.fontType.monospaced": "Monospasie",
|
|
||||||
"pad.settings.language": "Taal:",
|
|
||||||
"pad.importExport.import_export": "Voer in/uit",
|
|
||||||
"pad.importExport.import": "Laai enige tekslêer of dokument op",
|
|
||||||
"pad.importExport.importSuccessful": "Sukses!",
|
|
||||||
"pad.importExport.exporthtml": "HTML",
|
|
||||||
"pad.importExport.exportplain": "Skoon teks",
|
|
||||||
"pad.importExport.exportword": "Microsoft Word",
|
|
||||||
"pad.importExport.exportpdf": "PDF",
|
|
||||||
"pad.importExport.exportopen": "ODF (Open Document-formaat)",
|
|
||||||
"pad.modals.cancel": "Kanselleer",
|
|
||||||
"pad.modals.userdup.advice": "Maak weer 'n verbinding as u die venster wil gebruik.",
|
|
||||||
"pad.modals.unauth": "Nie toegestaan",
|
|
||||||
"pad.modals.deleted": "Geskrap.",
|
|
||||||
"pad.share": "Deel die pad",
|
|
||||||
"pad.share.readonly": "Lees-alleen",
|
|
||||||
"pad.share.link": "Skakel",
|
|
||||||
"pad.share.emebdcode": "Inbed URL",
|
|
||||||
"pad.chat": "Klets",
|
|
||||||
"pad.chat.title": "Maak kletsblad vir die pad oop",
|
|
||||||
"timeslider.toolbar.returnbutton": "Terug na pad",
|
|
||||||
"timeslider.toolbar.authors": "Outeurs:",
|
|
||||||
"timeslider.toolbar.authorsList": "Geen outeurs",
|
|
||||||
"timeslider.exportCurrent": "Huidige weergawe eksporteer as:",
|
|
||||||
"timeslider.version": "Weergawe {{version}}",
|
|
||||||
"timeslider.saved": "Gestoor op {{day}} {{month}} {{year}}",
|
|
||||||
"timeslider.dateformat": "{{year}}-{{month}}-{{day}} {{hours}}:{{minutes}}:{{seconds}}",
|
|
||||||
"timeslider.month.january": "Januarie",
|
|
||||||
"timeslider.month.february": "Februarie",
|
|
||||||
"timeslider.month.march": "Maart",
|
|
||||||
"timeslider.month.april": "April",
|
|
||||||
"timeslider.month.may": "Mei",
|
|
||||||
"timeslider.month.june": "Junie",
|
|
||||||
"timeslider.month.july": "Julie",
|
|
||||||
"timeslider.month.august": "Augustus",
|
|
||||||
"timeslider.month.september": "September",
|
|
||||||
"timeslider.month.october": "Oktober",
|
|
||||||
"timeslider.month.november": "November",
|
|
||||||
"timeslider.month.december": "Desember",
|
|
||||||
"pad.userlist.entername": "Verskaf u naam",
|
|
||||||
"pad.userlist.unnamed": "sonder naam",
|
|
||||||
"pad.userlist.guest": "Gas",
|
|
||||||
"pad.userlist.deny": "Keur af",
|
|
||||||
"pad.userlist.approve": "Keur goed",
|
|
||||||
"pad.impexp.importbutton": "Voer nou in",
|
|
||||||
"pad.impexp.importing": "Besig met invoer...",
|
|
||||||
"pad.impexp.importfailed": "Invoer het gefaal"
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Ali1",
|
|
||||||
"Tux-tn",
|
|
||||||
"Alami",
|
|
||||||
"Meno25",
|
|
||||||
"Test Create account",
|
|
||||||
"محمد أحمد عبد الفتاح",
|
|
||||||
"Haytham morsy",
|
|
||||||
"ديفيد",
|
|
||||||
"Mido",
|
|
||||||
"Shbib Al-Subaie"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "باد جديد",
|
|
||||||
"index.createOpenPad": "أو صنع/فتح باد بوضع اسمه:",
|
|
||||||
"pad.toolbar.bold.title": "سميك (Ctrl-B)",
|
|
||||||
"pad.toolbar.italic.title": "مائل (Ctrl-I)",
|
|
||||||
"pad.toolbar.underline.title": "تسطير (Ctrl-U)",
|
|
||||||
"pad.toolbar.strikethrough.title": "شطب (Ctrl+5)",
|
|
||||||
"pad.toolbar.ol.title": "قائمة مرتبة (Ctrl+Shift+N)",
|
|
||||||
"pad.toolbar.ul.title": "قائمة غير مرتبة (Ctrl+Shift+L)",
|
|
||||||
"pad.toolbar.indent.title": "إزاحة",
|
|
||||||
"pad.toolbar.unindent.title": "حذف الإزاحة",
|
|
||||||
"pad.toolbar.undo.title": "فك (Ctrl-Z)",
|
|
||||||
"pad.toolbar.redo.title": "تكرار (Ctrl-Y)",
|
|
||||||
"pad.toolbar.clearAuthorship.title": "مسح ألوان التأليف (Ctrl+Shift+C)",
|
|
||||||
"pad.toolbar.import_export.title": "استيراد/تصدير من/إلى تنسيقات ملفات مختلفة",
|
|
||||||
"pad.toolbar.timeslider.title": "متصفح التاريخ",
|
|
||||||
"pad.toolbar.savedRevision.title": "حفظ المراجعة",
|
|
||||||
"pad.toolbar.settings.title": "الإعدادات",
|
|
||||||
"pad.toolbar.embed.title": "تبادل و تضمين هذا الباد",
|
|
||||||
"pad.toolbar.showusers.title": "عرض المستخدمين على هذا الباد",
|
|
||||||
"pad.colorpicker.save": "تسجيل",
|
|
||||||
"pad.colorpicker.cancel": "إلغاء",
|
|
||||||
"pad.loading": "جارٍ التحميل...",
|
|
||||||
"pad.noCookie": "الكوكيز غير متاحة. الرجاء السماح بتحميل الكوكيز على متصفحك!",
|
|
||||||
"pad.passwordRequired": "تحتاج إلى كلمة مرور للوصول إلى هذا الباد",
|
|
||||||
"pad.permissionDenied": "ليس لديك إذن لدخول هذا الباد",
|
|
||||||
"pad.wrongPassword": "كانت كلمة المرور خاطئة",
|
|
||||||
"pad.settings.padSettings": "إعدادات الباد",
|
|
||||||
"pad.settings.myView": "رؤيتي",
|
|
||||||
"pad.settings.stickychat": "الدردشة دائما على الشاشة",
|
|
||||||
"pad.settings.chatandusers": "أظهر الدردشة والمستخدمين",
|
|
||||||
"pad.settings.colorcheck": "ألوان التأليف",
|
|
||||||
"pad.settings.linenocheck": "أرقام الأسطر",
|
|
||||||
"pad.settings.rtlcheck": "قراءة المحتويات من اليمين إلى اليسار؟",
|
|
||||||
"pad.settings.fontType": "نوع الخط:",
|
|
||||||
"pad.settings.fontType.normal": "عادي",
|
|
||||||
"pad.settings.fontType.monospaced": "ثابت العرض",
|
|
||||||
"pad.settings.globalView": "الرؤية الشاملة",
|
|
||||||
"pad.settings.language": "اللغة:",
|
|
||||||
"pad.importExport.import_export": "استيراد/تصدير",
|
|
||||||
"pad.importExport.import": "تحميل أي ملف نصي أو وثيقة",
|
|
||||||
"pad.importExport.importSuccessful": "ناجح!",
|
|
||||||
"pad.importExport.export": "تصدير الباد الحالي بصفة:",
|
|
||||||
"pad.importExport.exportetherpad": "إيثرباد",
|
|
||||||
"pad.importExport.exporthtml": "إتش تي إم إل",
|
|
||||||
"pad.importExport.exportplain": "نص عادي",
|
|
||||||
"pad.importExport.exportword": "مايكروسوفت وورد",
|
|
||||||
"pad.importExport.exportpdf": "صيغة المستندات المحمولة",
|
|
||||||
"pad.importExport.exportopen": "ODF (نسق المستند المفتوح)",
|
|
||||||
"pad.importExport.abiword.innerHTML": "لايمكنك الاستيراد إلا من نص عادي أو من تنسيقات إتش تي إم إل. للحصول على المزيد من ميزات الاستيراد المتقدمة، يرجى تثبيت أبيورد <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\"></a>.",
|
|
||||||
"pad.modals.connected": "متصل.",
|
|
||||||
"pad.modals.reconnecting": "إعادة الاتصال ببادك",
|
|
||||||
"pad.modals.forcereconnect": "فرض إعادة الاتصال",
|
|
||||||
"pad.modals.reconnecttimer": "حاول إعادة الاتصال",
|
|
||||||
"pad.modals.cancel": "إلغاء",
|
|
||||||
"pad.modals.userdup": "مفتوح في نافذة أخرى",
|
|
||||||
"pad.modals.userdup.explanation": "يبدو أن هذا الباد تم فتحه في أكثر من نافذة متصفح في هذا الحاسوب.",
|
|
||||||
"pad.modals.userdup.advice": "إعادة الاتصال لاستعمال هذه النافذة بدلاً من الأخرى.",
|
|
||||||
"pad.modals.unauth": "غير مخول",
|
|
||||||
"pad.modals.unauth.explanation": "لقد تغيرت الأذونات الخاصة بك أثناء عرض هذه الصفحة. أعد محاولة الاتصال.",
|
|
||||||
"pad.modals.looping.explanation": "هناك مشاكل في الاتصال مع ملقم التزامن.",
|
|
||||||
"pad.modals.looping.cause": "ربما كنت متصلاً من خلال وكيل أو جدار حماية غير متوافق.",
|
|
||||||
"pad.modals.initsocketfail": "لا يمكن الوصول إلى الخادم",
|
|
||||||
"pad.modals.initsocketfail.explanation": "تعذر الاتصال بخادم المزامنة.",
|
|
||||||
"pad.modals.initsocketfail.cause": "هذا على الأرجح بسبب مشكلة في المستعرض الخاص بك أو الاتصال بإنترنت.",
|
|
||||||
"pad.modals.slowcommit.explanation": "الخادم لا يستجيب.",
|
|
||||||
"pad.modals.slowcommit.cause": "يمكن أن يكون هذا بسبب مشاكل في الاتصال بالشبكة.",
|
|
||||||
"pad.modals.badChangeset.explanation": "لقد صُنفَت إحدى عمليات التحرير التي قمت بها كعملية غير مسموح بها من قبل ملقم التزامن.",
|
|
||||||
"pad.modals.badChangeset.cause": "يمكن أن يكون هذا بسبب تكوين ملقم خاطئ أو بسبب سلوك آخر غير متوقع. يرجى الاتصال بمسؤول الخدمة إذا كنت تعتقد بأن هناك خطأ ما. حاول إعادة الاتصال لمتابعة التحرير.",
|
|
||||||
"pad.modals.corruptPad.explanation": "الباد الذي تحاول الوصول إليه تالف.",
|
|
||||||
"pad.modals.corruptPad.cause": "قد يكون هذا بسبب تكوين ملقم خاطئ أو بسبب سلوك آخر غير متوقع. يرجى الاتصال بمسؤول الخدمة.",
|
|
||||||
"pad.modals.deleted": "محذوف.",
|
|
||||||
"pad.modals.deleted.explanation": "تمت إزالة هذا الباد",
|
|
||||||
"pad.modals.disconnected": "لم تعد متصلا.",
|
|
||||||
"pad.modals.disconnected.explanation": "تم فقدان الاتصال بالخادم",
|
|
||||||
"pad.modals.disconnected.cause": "قد يكون الخادم غير متوفر. يرجى إعلام مسؤول الخدمة إذا كان هذا لا يزال يحدث.",
|
|
||||||
"pad.share": "شارك هذه الباد",
|
|
||||||
"pad.share.readonly": "للقراءة فقط",
|
|
||||||
"pad.share.link": "رابط",
|
|
||||||
"pad.share.emebdcode": "URL للتضمين",
|
|
||||||
"pad.chat": "دردشة",
|
|
||||||
"pad.chat.title": "فتح الدردشة لهذا الباد",
|
|
||||||
"pad.chat.loadmessages": "تحميل المزيد من الرسائل",
|
|
||||||
"timeslider.pageTitle": "{{appTitle}} متصفح التاريخ",
|
|
||||||
"timeslider.toolbar.returnbutton": "العودة إلى الباد",
|
|
||||||
"timeslider.toolbar.authors": "المؤلفون:",
|
|
||||||
"timeslider.toolbar.authorsList": "بدون مؤلفين",
|
|
||||||
"timeslider.toolbar.exportlink.title": "تصدير",
|
|
||||||
"timeslider.exportCurrent": "تصدير النسخة الحالية ك:",
|
|
||||||
"timeslider.version": "إصدار {{version}}",
|
|
||||||
"timeslider.saved": "محفوظ {{month}} {{day}}, {{year}}",
|
|
||||||
"timeslider.playPause": "تشغيل / إيقاف مؤقت لمحتويات الباد",
|
|
||||||
"timeslider.backRevision": "عد إلى مراجعة في هذه الباد",
|
|
||||||
"timeslider.forwardRevision": "انطلق إلى مراجعة في هذه الباد",
|
|
||||||
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
|
||||||
"timeslider.month.january": "يناير",
|
|
||||||
"timeslider.month.february": "فبراير",
|
|
||||||
"timeslider.month.march": "مارس",
|
|
||||||
"timeslider.month.april": "أبريل",
|
|
||||||
"timeslider.month.may": "مايو",
|
|
||||||
"timeslider.month.june": "يونيو",
|
|
||||||
"timeslider.month.july": "يوليو",
|
|
||||||
"timeslider.month.august": "أغسطس",
|
|
||||||
"timeslider.month.september": "سبتمبر",
|
|
||||||
"timeslider.month.october": "أكتوبر",
|
|
||||||
"timeslider.month.november": "نوفمبر",
|
|
||||||
"timeslider.month.december": "ديسمبر",
|
|
||||||
"timeslider.unnamedauthors": "بدون اسم {{num}} {[plural(num) واحد: كاتب، آخر: مؤلف]}",
|
|
||||||
"pad.savedrevs.marked": "هذا التنقيح محدد الآن كمراجعة محفوظة",
|
|
||||||
"pad.savedrevs.timeslider": "يمكنك عرض المراجعات المحفوظة بزيارة متصفح التاريخ",
|
|
||||||
"pad.userlist.entername": "أدخل اسمك",
|
|
||||||
"pad.userlist.unnamed": "غير مسمى",
|
|
||||||
"pad.userlist.guest": "ضيف",
|
|
||||||
"pad.userlist.deny": "رفض",
|
|
||||||
"pad.userlist.approve": "موافقة",
|
|
||||||
"pad.editbar.clearcolors": "مسح ألوان التأليف أو المستند بأكمله؟",
|
|
||||||
"pad.impexp.importbutton": "الاستيراد الآن",
|
|
||||||
"pad.impexp.importing": "الاستيراد...",
|
|
||||||
"pad.impexp.confirmimport": "استيراد ملف سيؤدي للكتابة فوق النص الحالي بالباد. هل أنت متأكد من أنك تريد المتابعة؟",
|
|
||||||
"pad.impexp.convertFailed": "لم نتمكن من استيراد هذا الملف. يرجى استخدام تنسيق مستند مختلف، أو النسخ واللصق يدوياً",
|
|
||||||
"pad.impexp.padHasData": "لا يمكننا استيراد هذا الملف لأن هذا الباد تم بالفعل تغييره; الرجاء استيراد باد جديد",
|
|
||||||
"pad.impexp.uploadFailed": "فشل التحميل، الرجاء المحاولة مرة أخرى",
|
|
||||||
"pad.impexp.importfailed": "فشل الاستيراد",
|
|
||||||
"pad.impexp.copypaste": "الرجاء نسخ/لصق",
|
|
||||||
"pad.impexp.exportdisabled": "تصدير التنسيق {{type}} معطل. يرجى الاتصال بمسؤول النظام الخاص بك للحصول على التفاصيل."
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Xuacu"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "Nuevu bloc",
|
|
||||||
"index.createOpenPad": "o crear/abrir un bloc col nome:",
|
|
||||||
"pad.toolbar.bold.title": "Negrina (Ctrl-B)",
|
|
||||||
"pad.toolbar.italic.title": "Cursiva (Ctrl-I)",
|
|
||||||
"pad.toolbar.underline.title": "Sorrayáu (Ctrl-U)",
|
|
||||||
"pad.toolbar.strikethrough.title": "Tacháu (Ctrl+5)",
|
|
||||||
"pad.toolbar.ol.title": "Llista ordenada (Ctrl+Mayús+N)",
|
|
||||||
"pad.toolbar.ul.title": "Llista desordenada (Ctrl+Mayús+L)",
|
|
||||||
"pad.toolbar.indent.title": "Sangría (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "Sangría inversa (Mayúsc+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "Desfacer (Ctrl-Z)",
|
|
||||||
"pad.toolbar.redo.title": "Refacer (Ctrl-Y)",
|
|
||||||
"pad.toolbar.clearAuthorship.title": "Llimpiar los colores d'autoría (Ctrl+Mayús+C)",
|
|
||||||
"pad.toolbar.import_export.title": "Importar/Esportar ente distintos formatos de ficheru",
|
|
||||||
"pad.toolbar.timeslider.title": "Eslizador de tiempu",
|
|
||||||
"pad.toolbar.savedRevision.title": "Guardar revisión",
|
|
||||||
"pad.toolbar.settings.title": "Configuración",
|
|
||||||
"pad.toolbar.embed.title": "Compartir ya incrustar esti bloc",
|
|
||||||
"pad.toolbar.showusers.title": "Amosar los usuarios d'esti bloc",
|
|
||||||
"pad.colorpicker.save": "Guardar",
|
|
||||||
"pad.colorpicker.cancel": "Encaboxar",
|
|
||||||
"pad.loading": "Cargando...",
|
|
||||||
"pad.noCookie": "Nun pudo alcontrase la cookie. ¡Por favor, permite les cookies nel navegador!",
|
|
||||||
"pad.passwordRequired": "Necesites una contraseña pa entrar a esti bloc",
|
|
||||||
"pad.permissionDenied": "Nun tienes permisu pa entrar a esti bloc",
|
|
||||||
"pad.wrongPassword": "La contraseña era incorreuta",
|
|
||||||
"pad.settings.padSettings": "Configuración del bloc",
|
|
||||||
"pad.settings.myView": "la mio vista",
|
|
||||||
"pad.settings.stickychat": "Alderique en pantalla siempres",
|
|
||||||
"pad.settings.chatandusers": "Amosar la charra y los usuarios",
|
|
||||||
"pad.settings.colorcheck": "Colores d'autoría",
|
|
||||||
"pad.settings.linenocheck": "Númberos de llinia",
|
|
||||||
"pad.settings.rtlcheck": "¿Lleer el conteníu de drecha a izquierda?",
|
|
||||||
"pad.settings.fontType": "Tipografía:",
|
|
||||||
"pad.settings.fontType.normal": "Normal",
|
|
||||||
"pad.settings.fontType.monospaced": "Monoespaciada",
|
|
||||||
"pad.settings.globalView": "Vista global",
|
|
||||||
"pad.settings.language": "Llingua:",
|
|
||||||
"pad.importExport.import_export": "Importar/Esportar",
|
|
||||||
"pad.importExport.import": "Xubir cualquier ficheru o documentu de testu",
|
|
||||||
"pad.importExport.importSuccessful": "¡Correuto!",
|
|
||||||
"pad.importExport.export": "Esportar el bloc actual como:",
|
|
||||||
"pad.importExport.exportetherpad": "Etherpad",
|
|
||||||
"pad.importExport.exporthtml": "HTML",
|
|
||||||
"pad.importExport.exportplain": "Testu simple",
|
|
||||||
"pad.importExport.exportword": "Microsoft Word",
|
|
||||||
"pad.importExport.exportpdf": "PDF",
|
|
||||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
|
||||||
"pad.importExport.abiword.innerHTML": "Sólo se pue importar dende los formatos de testu planu o html. Pa carauterístiques d'importación más avanzaes <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">instala abiword</a>.",
|
|
||||||
"pad.modals.connected": "Coneutáu.",
|
|
||||||
"pad.modals.reconnecting": "Reconeutando col to bloc...",
|
|
||||||
"pad.modals.forcereconnect": "Forzar la reconexón",
|
|
||||||
"pad.modals.reconnecttimer": "Tentando reconeutar en",
|
|
||||||
"pad.modals.cancel": "Encaboxar",
|
|
||||||
"pad.modals.userdup": "Abiertu n'otra ventana",
|
|
||||||
"pad.modals.userdup.explanation": "Esti bloc paez que ta abiertu en más d'una ventana del navegador d'esti ordenador.",
|
|
||||||
"pad.modals.userdup.advice": "Reconeutar pa usar esta ventana.",
|
|
||||||
"pad.modals.unauth": "Non autorizáu",
|
|
||||||
"pad.modals.unauth.explanation": "Los tos permisos camudaron mientres vies esta páxina. Intenta volver a coneutar.",
|
|
||||||
"pad.modals.looping.explanation": "Hai problemes de comunicación col sirvidor de sincronización.",
|
|
||||||
"pad.modals.looping.cause": "Pues tar coneutáu per un torgafueos o un proxy incompatibles.",
|
|
||||||
"pad.modals.initsocketfail": "Sirvidor incalcanzable.",
|
|
||||||
"pad.modals.initsocketfail.explanation": "Nun se pudo coneutar col sirvidor de sincronización.",
|
|
||||||
"pad.modals.initsocketfail.cause": "Probablemente ye por aciu d'un problema col navegador o cola to conexón a internet.",
|
|
||||||
"pad.modals.slowcommit.explanation": "El sirvidor nun respuende.",
|
|
||||||
"pad.modals.slowcommit.cause": "Pue ser por problemes de coneutividá de la rede.",
|
|
||||||
"pad.modals.badChangeset.explanation": "El sirvidor de sincronización clasificó como illegal una edición que fizo.",
|
|
||||||
"pad.modals.badChangeset.cause": "Esto podría dase por una mala configuración del sirvidor o por algún otru comportamientu inesperáu. Comuníquese col alministrador del serviciu si cree qu'esto ye un error. Intente volver a coneutar pa siguir editando.",
|
|
||||||
"pad.modals.corruptPad.explanation": "El bloc al qu'intenta entrar ta corrompíu.",
|
|
||||||
"pad.modals.corruptPad.cause": "Esto pue ser por una mala configuración del sirvidor o por algún otru comportamientu inesperáu. Comuníquese col alministrador del serviciu.",
|
|
||||||
"pad.modals.deleted": "Desaniciáu",
|
|
||||||
"pad.modals.deleted.explanation": "Esti bloc se desanició.",
|
|
||||||
"pad.modals.disconnected": "Te desconeutasti.",
|
|
||||||
"pad.modals.disconnected.explanation": "Perdióse la conexón col sirvidor",
|
|
||||||
"pad.modals.disconnected.cause": "El sirvidor podría nun tar disponible. Por favor, avise al alministrador del serviciu si sigue pasando esto.",
|
|
||||||
"pad.share": "Compartir esti bloc",
|
|
||||||
"pad.share.readonly": "Sólo llectura",
|
|
||||||
"pad.share.link": "Enllaz",
|
|
||||||
"pad.share.emebdcode": "Incrustar URL",
|
|
||||||
"pad.chat": "Chat",
|
|
||||||
"pad.chat.title": "Abrir el chat d'esti bloc.",
|
|
||||||
"pad.chat.loadmessages": "Cargar más mensaxes",
|
|
||||||
"timeslider.pageTitle": "Eslizador de tiempu de {{appTitle}}",
|
|
||||||
"timeslider.toolbar.returnbutton": "Tornar al bloc",
|
|
||||||
"timeslider.toolbar.authors": "Autores:",
|
|
||||||
"timeslider.toolbar.authorsList": "Nun hai autores",
|
|
||||||
"timeslider.toolbar.exportlink.title": "Esportar",
|
|
||||||
"timeslider.exportCurrent": "Esportar la versión actual como:",
|
|
||||||
"timeslider.version": "Versión {{version}}",
|
|
||||||
"timeslider.saved": "Guardáu el {{day}} de {{month}} de {{year}}",
|
|
||||||
"timeslider.playPause": "Reproducir/posar el conteníu del bloc",
|
|
||||||
"timeslider.backRevision": "Dir a la revisión anterior d'esti bloc",
|
|
||||||
"timeslider.forwardRevision": "Dir a la revisión siguiente d'esti bloc",
|
|
||||||
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
|
||||||
"timeslider.month.january": "de xineru",
|
|
||||||
"timeslider.month.february": "de febreru",
|
|
||||||
"timeslider.month.march": "de marzu",
|
|
||||||
"timeslider.month.april": "d'abril",
|
|
||||||
"timeslider.month.may": "de mayu",
|
|
||||||
"timeslider.month.june": "de xunu",
|
|
||||||
"timeslider.month.july": "de xunetu",
|
|
||||||
"timeslider.month.august": "d'agostu",
|
|
||||||
"timeslider.month.september": "de setiembre",
|
|
||||||
"timeslider.month.october": "d'ochobre",
|
|
||||||
"timeslider.month.november": "de payares",
|
|
||||||
"timeslider.month.december": "d'avientu",
|
|
||||||
"timeslider.unnamedauthors": "{{num}} {[plural(num) one: autor anónimu, other: autores anónimos]}",
|
|
||||||
"pad.savedrevs.marked": "Esta revisión marcose como revisión guardada",
|
|
||||||
"pad.savedrevs.timeslider": "Pues ver les revisiones guardaes visitando la llinia temporal",
|
|
||||||
"pad.userlist.entername": "Escribi'l to nome",
|
|
||||||
"pad.userlist.unnamed": "ensin nome",
|
|
||||||
"pad.userlist.guest": "Invitáu",
|
|
||||||
"pad.userlist.deny": "Refugar",
|
|
||||||
"pad.userlist.approve": "Aprobar",
|
|
||||||
"pad.editbar.clearcolors": "¿Llimpiar los colores d'autoría nel documentu ensembre?",
|
|
||||||
"pad.impexp.importbutton": "Importar agora",
|
|
||||||
"pad.impexp.importing": "Importando...",
|
|
||||||
"pad.impexp.confirmimport": "La importación d'un ficheru sustituirá'l testu actual del bloc. ¿Seguro que quies siguir?",
|
|
||||||
"pad.impexp.convertFailed": "Nun pudimos importar esti ficheru. Por favor,usa otru formatu de ficheru diferente o copia y pega manualmente.",
|
|
||||||
"pad.impexp.padHasData": "Nun pudimos importar esti ficheru porque esti bloc yá tuvo cambios; impórtalu a un bloc nuevu",
|
|
||||||
"pad.impexp.uploadFailed": "Falló la carga del ficheru, intentalo otra vuelta",
|
|
||||||
"pad.impexp.importfailed": "Falló la importación",
|
|
||||||
"pad.impexp.copypaste": "Por favor, copia y apega",
|
|
||||||
"pad.impexp.exportdisabled": "La esportación en formatu {{type}} ta desactivada. Por favor, comunica col alministrador del sistema pa más detalles."
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"1AnuraagPandey"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "नयाँ प्याड",
|
|
||||||
"pad.toolbar.bold.title": "मोट (Ctrl-B)",
|
|
||||||
"pad.toolbar.italic.title": "तिरछा (Ctrl+I)",
|
|
||||||
"pad.toolbar.underline.title": "निम्न रेखाङ्कन (Ctrl-U)",
|
|
||||||
"pad.toolbar.indent.title": "इन्डेन्ट (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "आउटडेन्ट (Shift+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "रद्द (Ctrl-Z)",
|
|
||||||
"pad.toolbar.redo.title": "पुन:लागु (Ctrl-Y)",
|
|
||||||
"pad.toolbar.timeslider.title": "टाइमस्लाइडर",
|
|
||||||
"pad.toolbar.savedRevision.title": "पुनरावलोकन संग्रह किहा जाय",
|
|
||||||
"pad.toolbar.settings.title": "सेटिङ्ग",
|
|
||||||
"pad.colorpicker.save": "सहेजा जाय",
|
|
||||||
"pad.colorpicker.cancel": "रद्द करा जाय",
|
|
||||||
"pad.loading": "लोड होत है...",
|
|
||||||
"pad.wrongPassword": "आप कय पासवर्ड गलत रहा",
|
|
||||||
"pad.settings.padSettings": "प्याड सेटिङ्ग",
|
|
||||||
"pad.settings.myView": "हमार दृष्य",
|
|
||||||
"pad.settings.colorcheck": "लेखकीय रङ्ग",
|
|
||||||
"pad.settings.linenocheck": "हरफ संख्या",
|
|
||||||
"pad.settings.fontType": "फन्ट प्रकार:",
|
|
||||||
"pad.settings.fontType.normal": "साधारण",
|
|
||||||
"pad.settings.fontType.monospaced": "मोनोस्पेस",
|
|
||||||
"pad.settings.globalView": "विश्वव्यापी दृष्य",
|
|
||||||
"pad.settings.language": "भाषा",
|
|
||||||
"pad.importExport.import_export": "आयात/निर्यात",
|
|
||||||
"pad.importExport.importSuccessful": "सफल!",
|
|
||||||
"pad.importExport.exporthtml": "HTML",
|
|
||||||
"pad.importExport.exportplain": "सामान्य पाठ",
|
|
||||||
"pad.importExport.exportword": "माइक्रोसफ्ट वर्ड",
|
|
||||||
"pad.importExport.exportpdf": "पिडिएफ",
|
|
||||||
"pad.importExport.exportopen": "ओडिएफ(खुल्ला कागजात ढाँचा)",
|
|
||||||
"pad.modals.unauth": "अनाधिकृत",
|
|
||||||
"pad.modals.initsocketfail": "सर्भरमा पहुँच से बहरे है ।",
|
|
||||||
"pad.share.readonly": "पढय वाला खाली",
|
|
||||||
"pad.share.link": "लिङ्क",
|
|
||||||
"pad.share.emebdcode": "URL जोडा जाय",
|
|
||||||
"pad.chat": "बातचीत",
|
|
||||||
"timeslider.pageTitle": "{{appTitle}} समय रेखा",
|
|
||||||
"timeslider.toolbar.authors": "लेखक:",
|
|
||||||
"timeslider.toolbar.exportlink.title": "निर्यात",
|
|
||||||
"timeslider.version": "संस्करण {{version}}",
|
|
||||||
"timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
|
||||||
"timeslider.month.january": "जनवरी",
|
|
||||||
"timeslider.month.february": "फेब्रुअरी",
|
|
||||||
"timeslider.month.march": "मार्च",
|
|
||||||
"timeslider.month.april": "अप्रैल",
|
|
||||||
"timeslider.month.may": "मई",
|
|
||||||
"timeslider.month.june": "जून",
|
|
||||||
"timeslider.month.july": "जुलाई",
|
|
||||||
"timeslider.month.august": "अगस्त",
|
|
||||||
"timeslider.month.september": "सेप्टेम्बर",
|
|
||||||
"timeslider.month.october": "अक्टूबर",
|
|
||||||
"timeslider.month.november": "नोभेम्बर",
|
|
||||||
"timeslider.month.december": "डिसेम्बर",
|
|
||||||
"timeslider.unnamedauthors": "{{num}} unnamed {[plural(num) one: author, other: authors ]}",
|
|
||||||
"pad.userlist.unnamed": "बेनामी",
|
|
||||||
"pad.userlist.guest": "पहुना",
|
|
||||||
"pad.userlist.deny": "अस्वीकार",
|
|
||||||
"pad.userlist.approve": "स्वीकृत",
|
|
||||||
"pad.impexp.importing": "आयात होत है...",
|
|
||||||
"pad.impexp.importfailed": "आयात असफल रहा",
|
|
||||||
"pad.impexp.copypaste": "कृपया कपी पेस्ट कीन जाय"
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"AZISS",
|
|
||||||
"Khan27",
|
|
||||||
"Mushviq Abdulla",
|
|
||||||
"Wertuose",
|
|
||||||
"Mastizada",
|
|
||||||
"Archaeodontosaurus",
|
|
||||||
"Neriman2003"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "Yeni lövhə",
|
|
||||||
"index.createOpenPad": "və ya lövhəni bu adla yarat/aç:",
|
|
||||||
"pad.toolbar.bold.title": "Qalın (Ctrl-B)",
|
|
||||||
"pad.toolbar.italic.title": "Kursiv (Ctrl-I)",
|
|
||||||
"pad.toolbar.underline.title": "Altından xətt çəkmə (Ctrl-U)",
|
|
||||||
"pad.toolbar.strikethrough.title": "Üstdən xətləmək (Ctrl+5)",
|
|
||||||
"pad.toolbar.ol.title": "Sıralanmış siyahı (Ctrl+Shift+N)",
|
|
||||||
"pad.toolbar.ul.title": "Sırasız siyahı (Ctrl+Shift+L)",
|
|
||||||
"pad.toolbar.indent.title": "Abzas (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "Çıxıntı (Shift+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "Geri qaytar (Ctrl+Z)",
|
|
||||||
"pad.toolbar.redo.title": "Qaytar (Ctrl+Y)",
|
|
||||||
"pad.toolbar.clearAuthorship.title": "Müəlliflik Rənglərini Təmizlə (Ctrl+Shift+C)",
|
|
||||||
"pad.toolbar.import_export.title": "Müxtəlif fayl formatların(a/dan) idxal/ixrac",
|
|
||||||
"pad.toolbar.timeslider.title": "Vaxt cədvəli",
|
|
||||||
"pad.toolbar.savedRevision.title": "Düzəlişləri Saxla",
|
|
||||||
"pad.toolbar.settings.title": "Tənzimləmələr",
|
|
||||||
"pad.toolbar.embed.title": "Bu lövhəni paylaş və qur",
|
|
||||||
"pad.toolbar.showusers.title": "Lövhədəki istifadəçiləri göstər",
|
|
||||||
"pad.colorpicker.save": "Saxla",
|
|
||||||
"pad.colorpicker.cancel": "İmtina",
|
|
||||||
"pad.loading": "Yüklənir...",
|
|
||||||
"pad.noCookie": "Çərəz tapıla bilmədi. Lütfən səyyahınızda çərəzlərə icazə verinǃ",
|
|
||||||
"pad.passwordRequired": "Bu lövhəyə daxil olmaq üçün parol lazımdır",
|
|
||||||
"pad.permissionDenied": "Bu lövhəyə daxil olmaq üçün icazəniz yoxdur",
|
|
||||||
"pad.wrongPassword": "Sizin parolunuz səhvdir",
|
|
||||||
"pad.settings.padSettings": "Lövhə nizamlamaları",
|
|
||||||
"pad.settings.myView": "Mənim Görüntüm",
|
|
||||||
"pad.settings.stickychat": "Söhbət həmişə ekranda",
|
|
||||||
"pad.settings.chatandusers": "Gap və İstifadəçiləri Göstər",
|
|
||||||
"pad.settings.colorcheck": "Müəlliflik rəngləri",
|
|
||||||
"pad.settings.linenocheck": "Sətir nömrələri",
|
|
||||||
"pad.settings.rtlcheck": "Mühtəviyyat sağdan sola doğru oxunsunmu?",
|
|
||||||
"pad.settings.fontType": "Şriftin tipi:",
|
|
||||||
"pad.settings.fontType.normal": "Normal",
|
|
||||||
"pad.settings.fontType.monospaced": "Monoboşluq",
|
|
||||||
"pad.settings.globalView": "Ümumi görünüş",
|
|
||||||
"pad.settings.language": "Dil:",
|
|
||||||
"pad.importExport.import_export": "İdxal/İxrac",
|
|
||||||
"pad.importExport.import": "Hər hansı bir mətn faylı və ya sənəd yüklə",
|
|
||||||
"pad.importExport.importSuccessful": "Uğurlu!",
|
|
||||||
"pad.importExport.export": "Hazırkı lövhəni bu şəkildə ixrac et:",
|
|
||||||
"pad.importExport.exportetherpad": "Etherpad",
|
|
||||||
"pad.importExport.exporthtml": "HTML",
|
|
||||||
"pad.importExport.exportplain": "Adi mətn",
|
|
||||||
"pad.importExport.exportword": "Microsoft Word",
|
|
||||||
"pad.importExport.exportpdf": "PDF",
|
|
||||||
"pad.importExport.exportopen": "ODF (açıq sənəd formatı)",
|
|
||||||
"pad.importExport.abiword.innerHTML": "Siz yalnız adi mətndən və ya HTML-dən idxal edə bilərsiniz. İdxalın daha mürəkkəb funksiyaları üçün, zəhmət olmasa, <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\"> AbiWord-i quraşdırın</a>.",
|
|
||||||
"pad.modals.connected": "Bağlandı.",
|
|
||||||
"pad.modals.reconnecting": "Sizin lövhə yenidən qoşulur..",
|
|
||||||
"pad.modals.forcereconnect": "Məcbur təkrarən bağlan",
|
|
||||||
"pad.modals.reconnecttimer": "Yenidən qoşulur",
|
|
||||||
"pad.modals.cancel": "Ləğv et",
|
|
||||||
"pad.modals.userdup": "Başqa pəncərədə artıq açıqdır",
|
|
||||||
"pad.modals.userdup.explanation": "Bu lövhə, ola bilsin ki, bu kompüterdəki brauzerin bir neçə pəncərəsində açılmışdır.",
|
|
||||||
"pad.modals.userdup.advice": "Bu pəncərəni istifadə etmək üçün yenidən qoşul.",
|
|
||||||
"pad.modals.unauth": "İcazəli deyil",
|
|
||||||
"pad.modals.unauth.explanation": "Bu səhifəyə baxdığınız vaxt sizin icazəniz dəyişilib. Bərpa etmək üşün yenidən cəhd edin.",
|
|
||||||
"pad.modals.looping.explanation": "Sinxronlaşdırma serveri ilə kommunikasiya xətası var.",
|
|
||||||
"pad.modals.looping.cause": "Ola bilsin ki, siz uyğun olmayan fayrvol və ya proksi vasitəsi ilə qoşulmağa cəhd göstərirsiniz.",
|
|
||||||
"pad.modals.initsocketfail": "Server əlçatmazdır.",
|
|
||||||
"pad.modals.initsocketfail.explanation": "Sinxronlaşdırma serverinə qoşulma mümkünsüzdür.",
|
|
||||||
"pad.modals.initsocketfail.cause": "Ehtimal ki, bu problem sizin brauzerinizlə və ya internet-birləşmənizlə əlaqədərdir.",
|
|
||||||
"pad.modals.slowcommit.explanation": "Server cavab vermir.",
|
|
||||||
"pad.modals.slowcommit.cause": "Bu şəbəkə bağlantısında problemlər yarana bilər.",
|
|
||||||
"pad.modals.badChangeset.explanation": "Etdiyiniz bir redaktə sinxronizasiya serveri tərəfindən qeyri-leqal/qanundan kənar olaraq təsbit edildi.",
|
|
||||||
"pad.modals.badChangeset.cause": "Bu, yanlış server tərtibatı ya da başqa bir gözlənilməyən davranışlar nəticəsində ola bilər. Bu sizə bir xəta imiş kimi görünürsə lütfən servis nəzarətçisi ilə əlaqə yaradın. Redaktəyə davam etmək üçün yenidən qoşulmanı yoxlayın.",
|
|
||||||
"pad.modals.corruptPad.explanation": "Daxil olmağa çalışdığınız lövhə zədəlidir.",
|
|
||||||
"pad.modals.corruptPad.cause": "Bu, yanlış server tərtibatı ya da başqa bir gözlənilməyən davranışlardan əmələ gələ bilər. Lütfən servis nəzarətçisi ilə əlaqə yaradın.",
|
|
||||||
"pad.modals.deleted": "Silindi.",
|
|
||||||
"pad.modals.deleted.explanation": "Bu lövhə silindi.",
|
|
||||||
"pad.modals.disconnected": "Əlaqə kəsilib.",
|
|
||||||
"pad.modals.disconnected.explanation": "Serverə qoşulma itirilib",
|
|
||||||
"pad.modals.disconnected.cause": "Server ola bilsin, əlçatmazdır. Əgər belə davam edərsə xidmət administratorunu xəbərdar edin.",
|
|
||||||
"pad.share": "Bu lövhəni paylaş",
|
|
||||||
"pad.share.readonly": "Yalnız oxuyun",
|
|
||||||
"pad.share.link": "Keçid",
|
|
||||||
"pad.share.emebdcode": "URL-ni yayımla",
|
|
||||||
"pad.chat": "Söhbət",
|
|
||||||
"pad.chat.title": "Bu lövhə üçün çat açın.",
|
|
||||||
"pad.chat.loadmessages": "Daha çox mesaj yüklə",
|
|
||||||
"timeslider.pageTitle": "{{appTitle}} Vaxt cədvəli",
|
|
||||||
"timeslider.toolbar.returnbutton": "Lövhəyə qayıt",
|
|
||||||
"timeslider.toolbar.authors": "Müəlliflər:",
|
|
||||||
"timeslider.toolbar.authorsList": "Müəllif yoxdur",
|
|
||||||
"timeslider.toolbar.exportlink.title": "İxrac",
|
|
||||||
"timeslider.exportCurrent": "Cari versiyanı ixrac etmək kimi:",
|
|
||||||
"timeslider.version": "Versiya {{version}}",
|
|
||||||
"timeslider.saved": "Saxlanıldı {{day}} {{month}}, {{year}}",
|
|
||||||
"timeslider.playPause": "Geri oxutma / Lövhə Məzmunlarını Dayandır",
|
|
||||||
"timeslider.backRevision": "Sənədin bundan əvvəlki bir versiyasına qayıtmaq",
|
|
||||||
"timeslider.forwardRevision": "Sənədin bundan sonrakı bir versiyasına qayıtmaq",
|
|
||||||
"timeslider.dateformat": "{{day}} {{month}}, {{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
|
||||||
"timeslider.month.january": "Yanvar",
|
|
||||||
"timeslider.month.february": "Fevral",
|
|
||||||
"timeslider.month.march": "Mart",
|
|
||||||
"timeslider.month.april": "Aprel",
|
|
||||||
"timeslider.month.may": "May",
|
|
||||||
"timeslider.month.june": "İyun",
|
|
||||||
"timeslider.month.july": "İyul",
|
|
||||||
"timeslider.month.august": "Avqust",
|
|
||||||
"timeslider.month.september": "Sentyabr",
|
|
||||||
"timeslider.month.october": "Oktyabr",
|
|
||||||
"timeslider.month.november": "Noyabr",
|
|
||||||
"timeslider.month.december": "Dekabr",
|
|
||||||
"timeslider.unnamedauthors": "{{num}} adsız {[plural(num) one: müəllif, other: müəllif]}",
|
|
||||||
"pad.savedrevs.marked": "Bu versiya indi yaddaşa saxlanmış kimi nişanlandı",
|
|
||||||
"pad.savedrevs.timeslider": "Siz görə bilərsiniz saxlanılan versiyası miqyasında vaxt",
|
|
||||||
"pad.userlist.entername": "Adınızı daxil edin",
|
|
||||||
"pad.userlist.unnamed": "adsız",
|
|
||||||
"pad.userlist.guest": "Qonaq",
|
|
||||||
"pad.userlist.deny": "İnkar etmək",
|
|
||||||
"pad.userlist.approve": "Təsdiqləmək",
|
|
||||||
"pad.editbar.clearcolors": "Bütün sənədlərdə müəllif rəngləri təmizlənsin?",
|
|
||||||
"pad.impexp.importbutton": "İndi idxal et",
|
|
||||||
"pad.impexp.importing": "İdxal...",
|
|
||||||
"pad.impexp.confirmimport": "Faylın idxalı lövhədəki cari mətni yeniləyəcək. Davam etmək istədiyinizə əminsinizmi?",
|
|
||||||
"pad.impexp.convertFailed": "Biz bu fayl idxal etmək mümkün deyil idi. Xahiş olunur müxtəlif sənəddən istifadə edin və ya kopyalayıb yapışdırmaq yolundan istifadə edin",
|
|
||||||
"pad.impexp.padHasData": "Biz bu faylı idxal edə bilmədik, çünki bu lövhədə düzəlişlər edilib, lütfən yeni lövhə idxal edin",
|
|
||||||
"pad.impexp.uploadFailed": "Yükləmədə səhv, xahiş olunur yenə cəhd edin",
|
|
||||||
"pad.impexp.importfailed": "İdxal zamanı səhv",
|
|
||||||
"pad.impexp.copypaste": "Xahiş edirik kopyalayıb yapışdırın",
|
|
||||||
"pad.impexp.exportdisabled": "{{ type}} formatında ixrac söndürülmüşdür. Ətraflı informasiya üçün sistem administratoruna müraciət ediniz."
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Amir a57",
|
|
||||||
"Mousa",
|
|
||||||
"Koroğlu",
|
|
||||||
"Alp Er Tunqa",
|
|
||||||
"Ilğım"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "یئنی یادداشت دفترچه سی",
|
|
||||||
"index.createOpenPad": "یا دا ایجاد /بیر پد آدلا برابر آچماق:",
|
|
||||||
"pad.toolbar.bold.title": "بویوت",
|
|
||||||
"pad.toolbar.italic.title": "مورب",
|
|
||||||
"pad.toolbar.underline.title": "خطدین آلتی",
|
|
||||||
"pad.toolbar.strikethrough.title": "خط یئمیش (Ctrl+5)",
|
|
||||||
"pad.toolbar.ol.title": "جوتدنمیش فهرست (Ctrl+Shift+N)",
|
|
||||||
"pad.toolbar.ul.title": "جوتدنمهمیش لیست (Ctrl+Shift+L)",
|
|
||||||
"pad.toolbar.indent.title": "ایچری باتما (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "ائشیگه چیخدیغی (Shift+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "باطل ائتمک",
|
|
||||||
"pad.toolbar.redo.title": "یئنی دن",
|
|
||||||
"pad.toolbar.clearAuthorship.title": "یازیچی بوْیالارینی سیلمک (Ctrl+Shift+C)",
|
|
||||||
"pad.toolbar.import_export.title": "آیری قالیب لردن /ایچری توکمه / ائشیگه توکمه",
|
|
||||||
"pad.toolbar.timeslider.title": "زمان اسلایدی",
|
|
||||||
"pad.toolbar.savedRevision.title": "نۆسخهنی ذخیره ائت",
|
|
||||||
"pad.toolbar.settings.title": "تنظیملر",
|
|
||||||
"pad.toolbar.embed.title": "بو یادداشت دفترچه سین یئرلشدیر و پایلاش",
|
|
||||||
"pad.toolbar.showusers.title": "بو دفترچه یادداشت دا اولان کاربرلری گوستر",
|
|
||||||
"pad.colorpicker.save": "ذخیره ائت",
|
|
||||||
"pad.colorpicker.cancel": "وازگئچ",
|
|
||||||
"pad.loading": "یوکلنیر...",
|
|
||||||
"pad.noCookie": "کوکی تاپیلمادی. لوطفن براوزرینیزده کوکیلره ایجازه وئرین!",
|
|
||||||
"pad.passwordRequired": "بو نوت دفترچه سینه ال تاپماق اوچون بیر رمزه احتیاجینیز واردیر.",
|
|
||||||
"pad.permissionDenied": "بو نوت دفترچه سینه ال تاپماق اوچون ایجازه نیز یوخدور.",
|
|
||||||
"pad.wrongPassword": "سیزین رمزینیز دوز دئییل",
|
|
||||||
"pad.settings.padSettings": "یادداشت دفترچه سینین تنظیملر",
|
|
||||||
"pad.settings.myView": "منیم گورنتوم",
|
|
||||||
"pad.settings.stickychat": "نمایش صفحه سینده همیشه چت اولسون",
|
|
||||||
"pad.settings.chatandusers": "چت ایله ایشلدنلری گؤستر",
|
|
||||||
"pad.settings.colorcheck": "یازیچی رنگ لری",
|
|
||||||
"pad.settings.linenocheck": "خطوط شماره سی",
|
|
||||||
"pad.settings.rtlcheck": "ایچینده کیلری ساغدان یوخسا سولدان اوخوسون؟",
|
|
||||||
"pad.settings.fontType": "قلم نوعی",
|
|
||||||
"pad.settings.fontType.normal": "نورمال",
|
|
||||||
"pad.settings.fontType.monospaced": "مونو اسپئیس",
|
|
||||||
"pad.settings.globalView": "سراسر گورونتو",
|
|
||||||
"pad.settings.language": "دیل:",
|
|
||||||
"pad.importExport.import_export": "ایچری توکمه /ائشیگه توکمه",
|
|
||||||
"pad.importExport.import": "سند یا دا متنی پرونده یوکله",
|
|
||||||
"pad.importExport.importSuccessful": "باشاریلی اولدو!",
|
|
||||||
"pad.importExport.export": "بو یادداشت دفترچه سی عنوانا ایچری توکمه",
|
|
||||||
"pad.importExport.exportetherpad": "اترپد",
|
|
||||||
"pad.importExport.exporthtml": "اچ تی ام ال",
|
|
||||||
"pad.importExport.exportplain": "ساده متن",
|
|
||||||
"pad.importExport.exportword": "مایکروسافت وورد",
|
|
||||||
"pad.importExport.exportpdf": "پی دی اف",
|
|
||||||
"pad.importExport.exportopen": "او دی اف",
|
|
||||||
"pad.modals.connected": "باغلاندی.",
|
|
||||||
"pad.modals.reconnecting": "یادداشت دفترچهنیزه یئنیدن باغلانمایا چالیشیلیر...",
|
|
||||||
"pad.modals.forcereconnect": "تکرار باغلانماق اوچون زوْرلاما",
|
|
||||||
"pad.modals.reconnecttimer": "یئنیدن باغلانمایا چالیشیلیر",
|
|
||||||
"pad.modals.cancel": "وازگئچ",
|
|
||||||
"pad.modals.userdup": "آیری پنجره ده آچیلدی",
|
|
||||||
"pad.modals.userdup.advice": "بو پئنجره دن ایستفاده ائتمک اوچون یئنی دن متصیل اول",
|
|
||||||
"pad.modals.unauth": "اوْلماز",
|
|
||||||
"pad.modals.unauth.explanation": "سیزین ال چتما مسئله سی بو صفحه نین گورونوش زمانیندا دییشیلیب دیر .\nسعی ائدین یئنی دن متصیل اولاسینیز",
|
|
||||||
"pad.modals.looping.explanation": "ارتیباطی موشکیل بیر ائتمه سرور ده وار دیر",
|
|
||||||
"pad.modals.looping.cause": "بلکه سیز دوز دئمیین بیر فایروال یادا پروکسی طریقی ایله متصیل اولوب سینیز",
|
|
||||||
"pad.modals.initsocketfail": "سرور الده دئییلدیر.",
|
|
||||||
"pad.modals.initsocketfail.explanation": "بیرلشدیریلمه سرور لرینه متصیل اولا بیلمه دی",
|
|
||||||
"pad.modals.slowcommit.explanation": "سرور جواب وئرمه ییر.",
|
|
||||||
"pad.modals.slowcommit.cause": "بو، شبکه باغلانتیسیندا خطالار اوچون اولا بیلر.",
|
|
||||||
"pad.modals.corruptPad.explanation": "ال تاپماغا چالیشدیغینیز پد کورلانیبدیر.",
|
|
||||||
"pad.modals.corruptPad.cause": "بو، غلط سرور تنظیملری یوخسا آیری بیر گوزلنیلمز بیر داورانیشدان عمله گله بیلر. لوطفا سرویس ایداره چیسی ایله تماس توتون.",
|
|
||||||
"pad.modals.deleted": "سیلیندی.",
|
|
||||||
"pad.modals.deleted.explanation": "بۇ یادداشت دفترچهسی سیلینیبدیر.",
|
|
||||||
"pad.modals.disconnected": "سیزین باغلانتینیز کسیلیبدیر.",
|
|
||||||
"pad.modals.disconnected.explanation": "سروره باغلانتی کسیلیبدیر.",
|
|
||||||
"pad.modals.disconnected.cause": "سرور ال چاتماز اولا بیلر. بئله قالیرسا سرویس ایداره چیسینی آییق سالین.",
|
|
||||||
"pad.share": "بو نوت دفترچه سینی پایلاش",
|
|
||||||
"pad.share.readonly": "سادهجه اوْخومالی",
|
|
||||||
"pad.share.link": "باغلانتی",
|
|
||||||
"pad.share.emebdcode": "یۇآرالی یئرلشدیرمک",
|
|
||||||
"pad.chat": "چت",
|
|
||||||
"pad.chat.title": "بو یادداشت دفترچهسینه چتی آچ.",
|
|
||||||
"pad.chat.loadmessages": "داها آرتیق پیام یوکله",
|
|
||||||
"timeslider.pageTitle": "{{appTitle}}زمان اسلایدری",
|
|
||||||
"timeslider.toolbar.returnbutton": "یادداشت دفترچهسینه قاییت.",
|
|
||||||
"timeslider.toolbar.authors": "یازیچیلار",
|
|
||||||
"timeslider.toolbar.authorsList": "یازیچیسیز",
|
|
||||||
"timeslider.toolbar.exportlink.title": "ائشیگه آپارماق",
|
|
||||||
"timeslider.exportCurrent": "موجود نوسخه نی بو عونوانلا ائشیگه چیخارت:",
|
|
||||||
"timeslider.version": "{{version}} ورژنی",
|
|
||||||
"timeslider.saved": "ساخلانیلدی {{day}} {{month}}, {{year}}",
|
|
||||||
"timeslider.playPause": "پد ایچیندهکیلری یئنه اوْخوت/دۇردور",
|
|
||||||
"timeslider.month.january": "ژانویه",
|
|
||||||
"timeslider.month.february": "فوریه",
|
|
||||||
"timeslider.month.march": "مارس",
|
|
||||||
"timeslider.month.april": "آوریل",
|
|
||||||
"timeslider.month.may": "مئی",
|
|
||||||
"timeslider.month.june": "ژوئن",
|
|
||||||
"timeslider.month.july": "جولای",
|
|
||||||
"timeslider.month.august": "آقوست",
|
|
||||||
"timeslider.month.september": "سپتامبر",
|
|
||||||
"timeslider.month.october": "اوْکتوبر",
|
|
||||||
"timeslider.month.november": "نوْوامبر",
|
|
||||||
"timeslider.month.december": "دسامبر",
|
|
||||||
"pad.savedrevs.marked": "بۇ نوسخه ایندی ذخیره اوْلونموش کیمی علامتلندی.",
|
|
||||||
"pad.userlist.entername": "آدینیزی یازین",
|
|
||||||
"pad.userlist.unnamed": "آدسیز",
|
|
||||||
"pad.userlist.guest": "قوْناق",
|
|
||||||
"pad.userlist.deny": "دانماق",
|
|
||||||
"pad.userlist.approve": "اوْنایلا",
|
|
||||||
"pad.editbar.clearcolors": "بوتون سندلرده یازار بوْیالاری سیلینسین می؟",
|
|
||||||
"pad.impexp.importbutton": "ایندی ایچری گتیر",
|
|
||||||
"pad.impexp.importing": "ایچری گتیریلیر...",
|
|
||||||
"pad.impexp.uploadFailed": "آپلود اولونمادی، یئنه چالیشین",
|
|
||||||
"pad.impexp.importfailed": "ایچری گتیرمه اولونمادی",
|
|
||||||
"pad.impexp.copypaste": "لوطفن کوپی ائدیب، یاپیشدیرین"
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Baloch Afghanistan"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "دفترچه یادداشت تازه",
|
|
||||||
"index.createOpenPad": "یا ایجاد/بازکردن یک دفترچه یادداشت با نام:",
|
|
||||||
"pad.toolbar.bold.title": "پررنگ (Ctrl-B)",
|
|
||||||
"pad.toolbar.italic.title": "کج (Ctrl-I)",
|
|
||||||
"pad.toolbar.underline.title": "زیرخط (Ctrl-U)",
|
|
||||||
"pad.toolbar.strikethrough.title": "خط خورده",
|
|
||||||
"pad.toolbar.ol.title": "فهرست مرتب شده",
|
|
||||||
"pad.toolbar.ul.title": "فهرست مرتب نشده",
|
|
||||||
"pad.toolbar.indent.title": "تورفتگی (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "بیرون رفتگی (Shift+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "باطلکردن (Ctrl-Z)",
|
|
||||||
"pad.toolbar.redo.title": "از نو (Ctrl-Y)",
|
|
||||||
"pad.toolbar.clearAuthorship.title": "پاککردن رنگهای نویسندگی",
|
|
||||||
"pad.toolbar.import_export.title": "درونریزی/برونریزی از/به قالبهای مختلف",
|
|
||||||
"pad.toolbar.timeslider.title": "لغزندهٔ زمان",
|
|
||||||
"pad.toolbar.savedRevision.title": "ذخیرهسازی نسخه",
|
|
||||||
"pad.toolbar.settings.title": "تنظیمات",
|
|
||||||
"pad.toolbar.embed.title": "اشتراک و جاسازی این دفترچه یادداشت",
|
|
||||||
"pad.toolbar.showusers.title": "نمایش کاربران در این دفترچه یادداشت",
|
|
||||||
"pad.colorpicker.save": "زاپاس کورتین",
|
|
||||||
"pad.colorpicker.cancel": "کنسیل",
|
|
||||||
"pad.loading": "...بار بیت",
|
|
||||||
"pad.passwordRequired": "برای دسترسی به این دفترچه یادداشت نیاز به یک گذرواژه دارید",
|
|
||||||
"pad.permissionDenied": "شرمنده، شما را اجازت په دسترسی ای صفحه نیست.",
|
|
||||||
"pad.wrongPassword": "گذرواژهی شما درست نیست",
|
|
||||||
"pad.settings.padSettings": "تنظیمات دفترچه یادداشت",
|
|
||||||
"pad.settings.myView": "نمای من",
|
|
||||||
"pad.settings.stickychat": "گفتگو همیشه روی صفحه نمایش باشد",
|
|
||||||
"pad.settings.colorcheck": "رنگهای نویسندگی",
|
|
||||||
"pad.settings.linenocheck": "شمارهی خطوط",
|
|
||||||
"pad.settings.rtlcheck": "خواندن محتوا از راست به چپ؟",
|
|
||||||
"pad.settings.fontType": "نوع قلم:",
|
|
||||||
"pad.settings.fontType.normal": "نرمال",
|
|
||||||
"pad.settings.fontType.monospaced": "Monospace",
|
|
||||||
"pad.settings.globalView": "نمای سراسری",
|
|
||||||
"pad.settings.language": "زبان:",
|
|
||||||
"pad.importExport.import_export": "درونریزی/برونریزی",
|
|
||||||
"pad.importExport.import": "بارگذاری پروندهی متنی یا سند",
|
|
||||||
"pad.importExport.importSuccessful": "موفقیت آمیز بود!",
|
|
||||||
"pad.importExport.export": "برونریزی این دفترچه یادداشت با قالب:",
|
|
||||||
"pad.importExport.exporthtml": "HTML",
|
|
||||||
"pad.importExport.exportplain": "سادگین متن",
|
|
||||||
"pad.importExport.exportword": "Microsoft Word",
|
|
||||||
"pad.importExport.exportpdf": "PDF",
|
|
||||||
"pad.importExport.exportopen": "ODF (قالب سند باز)",
|
|
||||||
"pad.importExport.abiword.innerHTML": "شما تنها میتوانید از قالب متن ساده یا اچتیامال درونریزی کنید. برای بیشتر شدن ویژگیهای درونریزی پیشرفته <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">AbiWord</a> را نصب کنید.",
|
|
||||||
"pad.modals.connected": "متصل شد.",
|
|
||||||
"pad.modals.reconnecting": "در حال اتصال دوباره به دفترچه یادداشت شما..",
|
|
||||||
"pad.modals.forcereconnect": "واداشتن به اتصال دوباره",
|
|
||||||
"pad.modals.userdup": "در پنجرهای دیگر باز شد",
|
|
||||||
"pad.modals.userdup.explanation": "گمان میرود این دفترچه یادداشت در بیش از یک پنجرهی مرورگر باز شدهاست.",
|
|
||||||
"pad.modals.userdup.advice": "برای استفاده از این پنجره دوباره وصل شوید.",
|
|
||||||
"pad.modals.unauth": "مجاز نیست",
|
|
||||||
"pad.modals.unauth.explanation": "دسترسی شما در حین مشاهدهی این برگه تغییر یافتهاست. دوباره متصل شوید.",
|
|
||||||
"pad.modals.looping.explanation": "مشکلاتی ارتباطی با سرور همگامسازی وجود دارد.",
|
|
||||||
"pad.modals.looping.cause": "شاید شما از طریق یک فایروال یا پروکسی ناسازگار متصل شدهاید.",
|
|
||||||
"pad.modals.initsocketfail": "سرور در دسترس نیست.",
|
|
||||||
"pad.modals.initsocketfail.explanation": "نمیتوان به سرور همگام سازی وصل شد.",
|
|
||||||
"pad.modals.initsocketfail.cause": "شاید این به خاطر مشکلی در مرورگر یا اتصال اینترنتی شما باشد.",
|
|
||||||
"pad.modals.slowcommit.explanation": "سرور پاسخ نمیدهد.",
|
|
||||||
"pad.modals.slowcommit.cause": "این میتواند به خاطر مشکلاتی در اتصال به شبکه باشد.",
|
|
||||||
"pad.modals.badChangeset.explanation": "ویرایشی که شما انجام دادهاید توسط سرور همگامسازی نادرست طیقهبندی شدهاست.",
|
|
||||||
"pad.modals.badChangeset.cause": "این میتواند به دلیل پیکربندی اشتباه یا سایر رفتارهای غیرمنتظره باشد. اگر فکر میکنید این یک خطا است لطفاً با مدیر خدمت تماس بگیرید. برای ادامهٔ ویرایش سعی کنید که دوباره متصل شوید.",
|
|
||||||
"pad.modals.corruptPad.explanation": "پدی که شما سعی دارید دسترسی پیدا کنید خراب است.",
|
|
||||||
"pad.modals.corruptPad.cause": "این احتمالاً به دلیل تنظیمات اشتباه کارساز یا سایر رفتارهای غیرمنتظره است. لطفاً با مدیر خدمت تماس حاصل کنید.",
|
|
||||||
"pad.modals.deleted": "پاک کورتین",
|
|
||||||
"pad.modals.deleted.explanation": "این دفترچه یادداشت پاک شدهاست.",
|
|
||||||
"pad.modals.disconnected": "اتصال شما قطع شدهاست.",
|
|
||||||
"pad.modals.disconnected.explanation": "اتصال به سرور قطع شدهاست.",
|
|
||||||
"pad.modals.disconnected.cause": "ممکن است سرور در دسترس نباشد. اگر این مشکل باز هم رخ داد مدیر حدمت را آگاه کنید.",
|
|
||||||
"pad.share": "به اشتراکگذاری این دفترچه یادداشت",
|
|
||||||
"pad.share.readonly": "فقط خواندنی",
|
|
||||||
"pad.share.link": "پیوند",
|
|
||||||
"pad.share.emebdcode": "جاسازی نشانی",
|
|
||||||
"pad.chat": "گفتگو",
|
|
||||||
"pad.chat.title": "بازکردن گفتگو برای این دفترچه یادداشت",
|
|
||||||
"pad.chat.loadmessages": "بارگیری پیامهای بیشتر",
|
|
||||||
"timeslider.pageTitle": "لغزندهٔ زمان {{appTitle}}",
|
|
||||||
"timeslider.toolbar.returnbutton": "بازگشت به دفترچه یادداشت",
|
|
||||||
"timeslider.toolbar.authors": "نویسوک:",
|
|
||||||
"timeslider.toolbar.authorsList": "بدون نویسنده",
|
|
||||||
"timeslider.toolbar.exportlink.title": "درگیزگ",
|
|
||||||
"timeslider.exportCurrent": "برونریزی نگارش کنونی به عنوان:",
|
|
||||||
"timeslider.version": "نگارش {{version}}",
|
|
||||||
"timeslider.saved": "{{month}} {{day}}، {{year}} ذخیره شد",
|
|
||||||
"timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
|
||||||
"timeslider.month.january": "جنوری",
|
|
||||||
"timeslider.month.february": "پیبروری",
|
|
||||||
"timeslider.month.march": "مارچ",
|
|
||||||
"timeslider.month.april": "آپریل",
|
|
||||||
"timeslider.month.may": "می",
|
|
||||||
"timeslider.month.june": "جون",
|
|
||||||
"timeslider.month.july": "جولای",
|
|
||||||
"timeslider.month.august": "آگوست",
|
|
||||||
"timeslider.month.september": "سپٹامبر",
|
|
||||||
"timeslider.month.october": "اکتوبر",
|
|
||||||
"timeslider.month.november": "نوامبر",
|
|
||||||
"timeslider.month.december": "دسمبر",
|
|
||||||
"timeslider.unnamedauthors": "{{num}} نویسندهٔ بینام",
|
|
||||||
"pad.savedrevs.marked": "این بازنویسی هم اکنون به عنوان ذخیره شده علامتگذاری شد",
|
|
||||||
"pad.userlist.entername": "وتی یوزرنامء بلک ات",
|
|
||||||
"pad.userlist.unnamed": "بدون نام",
|
|
||||||
"pad.userlist.guest": "مهمان",
|
|
||||||
"pad.userlist.deny": "رد کردن",
|
|
||||||
"pad.userlist.approve": "تایید",
|
|
||||||
"pad.editbar.clearcolors": "رنگ نویسندگی از همهی سند پاک شود؟",
|
|
||||||
"pad.impexp.importbutton": "هم اکنون درونریزی کن",
|
|
||||||
"pad.impexp.importing": "در حال درونریزی...",
|
|
||||||
"pad.impexp.confirmimport": "با درونریزی یک پرونده نوشتهٔ کنونی دفترچه پاک میشود. آیا میخواهید ادامه دهید؟",
|
|
||||||
"pad.impexp.convertFailed": "ما نمیتوانیم این پرونده را درونریزی کنیم. خواهشمندیم قالب دیگری برای سندتان انتخاب کرده یا بصورت دستی آنرا کپی کنید",
|
|
||||||
"pad.impexp.uploadFailed": "آپلود انجام نشد، دوباره تلاش کنید",
|
|
||||||
"pad.impexp.importfailed": "درونریزی انجام نشد",
|
|
||||||
"pad.impexp.copypaste": "کپی پیست کنید",
|
|
||||||
"pad.impexp.exportdisabled": "برونریزی با قالب {{type}} از کار افتاده است. برای جزئیات بیشتر با مدیر سیستمتان تماس بگیرید."
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Jim-by",
|
|
||||||
"Wizardist",
|
|
||||||
"Red Winged Duck"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "Стварыць",
|
|
||||||
"index.createOpenPad": "ці тварыць/адкрыць дакумэнт з назвай:",
|
|
||||||
"pad.toolbar.bold.title": "Тоўсты (Ctrl-B)",
|
|
||||||
"pad.toolbar.italic.title": "Курсіў (Ctrl-I)",
|
|
||||||
"pad.toolbar.underline.title": "Падкрэсьліваньне (Ctrl-U)",
|
|
||||||
"pad.toolbar.strikethrough.title": "Закрэсьліваньне (Ctrl+5)",
|
|
||||||
"pad.toolbar.ol.title": "Упарадкаваны сьпіс (Ctrl+Shift+N)",
|
|
||||||
"pad.toolbar.ul.title": "Неўпарадкаваны сьпіс (Ctrl+Shift+L)",
|
|
||||||
"pad.toolbar.indent.title": "Водступ (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "Выступ (Shift+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "Скасаваць(Ctrl-Z)",
|
|
||||||
"pad.toolbar.redo.title": "Вярнуць (Ctrl-Y)",
|
|
||||||
"pad.toolbar.clearAuthorship.title": "Прыбраць колер дакумэнту (Ctrl+Shift+C)",
|
|
||||||
"pad.toolbar.import_export.title": "Імпарт/Экспарт з выкарыстаньне розных фарматаў файлаў",
|
|
||||||
"pad.toolbar.timeslider.title": "Шкала часу",
|
|
||||||
"pad.toolbar.savedRevision.title": "Захаваць вэрсію",
|
|
||||||
"pad.toolbar.settings.title": "Налады",
|
|
||||||
"pad.toolbar.embed.title": "Падзяліцца і ўбудаваць гэты дакумэнт",
|
|
||||||
"pad.toolbar.showusers.title": "Паказаць карыстальнікаў у гэтым дакумэнце",
|
|
||||||
"pad.colorpicker.save": "Захаваць",
|
|
||||||
"pad.colorpicker.cancel": "Скасаваць",
|
|
||||||
"pad.loading": "Загрузка...",
|
|
||||||
"pad.noCookie": "Кукі ня знойдзеныя. Калі ласка, дазвольце кукі ў вашым браўзэры!",
|
|
||||||
"pad.passwordRequired": "Для доступу да гэтага дакумэнта патрэбны пароль",
|
|
||||||
"pad.permissionDenied": "Вы ня маеце дазволу на доступ да гэтага дакумэнта",
|
|
||||||
"pad.wrongPassword": "Вы ўвялі няслушны пароль",
|
|
||||||
"pad.settings.padSettings": "Налады дакумэнта",
|
|
||||||
"pad.settings.myView": "Мой выгляд",
|
|
||||||
"pad.settings.stickychat": "Заўсёды паказваць чат",
|
|
||||||
"pad.settings.chatandusers": "Паказаць чат і ўдзельнікаў",
|
|
||||||
"pad.settings.colorcheck": "Колеры аўтарства",
|
|
||||||
"pad.settings.linenocheck": "Нумары радкоў",
|
|
||||||
"pad.settings.rtlcheck": "Тэкст справа-налева",
|
|
||||||
"pad.settings.fontType": "Тып шрыфту:",
|
|
||||||
"pad.settings.fontType.normal": "Звычайны",
|
|
||||||
"pad.settings.fontType.monospaced": "Монашырынны",
|
|
||||||
"pad.settings.globalView": "Агульны выгляд",
|
|
||||||
"pad.settings.language": "Мова:",
|
|
||||||
"pad.importExport.import_export": "Імпарт/Экспарт",
|
|
||||||
"pad.importExport.import": "Загрузіжайце любыя тэкставыя файлы або дакумэнты",
|
|
||||||
"pad.importExport.importSuccessful": "Пасьпяхова!",
|
|
||||||
"pad.importExport.export": "Экспартаваць бягучы дакумэнт як:",
|
|
||||||
"pad.importExport.exportetherpad": "Etherpad",
|
|
||||||
"pad.importExport.exporthtml": "HTML",
|
|
||||||
"pad.importExport.exportplain": "Просты тэкст",
|
|
||||||
"pad.importExport.exportword": "Microsoft Word",
|
|
||||||
"pad.importExport.exportpdf": "PDF",
|
|
||||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
|
||||||
"pad.importExport.abiword.innerHTML": "Вы можаце імпартаваць толькі з звычайнага тэксту або HTML. Дзеля больш пашыраных магчымасьцяў імпарту, калі ласка, <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">усталюйце AbiWord</a>.",
|
|
||||||
"pad.modals.connected": "Падлучыліся.",
|
|
||||||
"pad.modals.reconnecting": "Перападлучэньне да вашага дакумэнта...",
|
|
||||||
"pad.modals.forcereconnect": "Прымусовае перападлучэньне",
|
|
||||||
"pad.modals.reconnecttimer": "Спрабуем перападключыцца праз",
|
|
||||||
"pad.modals.cancel": "Адмяніць",
|
|
||||||
"pad.modals.userdup": "Адкрыта ў іншым акне",
|
|
||||||
"pad.modals.userdup.explanation": "Падобна, дакумэнт адкрыты больш чым у адным акне браўзэра на гэтым кампутары.",
|
|
||||||
"pad.modals.userdup.advice": "Паўторна падключыць з выкарыстаньнем гэтага акна.",
|
|
||||||
"pad.modals.unauth": "Не аўтарызаваны",
|
|
||||||
"pad.modals.unauth.explanation": "Вашыя правы былі зьмененыя ў часе прагляду гэтай старонкі. Паспрабуйце перападключыцца.",
|
|
||||||
"pad.modals.looping.explanation": "Праблемы далучэньня да сэрвэра сынхранізацыі.",
|
|
||||||
"pad.modals.looping.cause": "Магчыма, вы падключыліся празь несумяшчальны брандмаўэр або проксі.",
|
|
||||||
"pad.modals.initsocketfail": "Сэрвэр недаступны.",
|
|
||||||
"pad.modals.initsocketfail.explanation": "Не атрымалася падлучыцца да сэрвэра сынхранізацыі.",
|
|
||||||
"pad.modals.initsocketfail.cause": "Імаверна, гэта зьвязана з праблемамі з вашым браўзэрам або інтэрнэт-злучэньнем.",
|
|
||||||
"pad.modals.slowcommit.explanation": "Сэрвэр не адказвае.",
|
|
||||||
"pad.modals.slowcommit.cause": "Гэта можа быць выклікана праблемамі зь сеткавым падлучэньнем.",
|
|
||||||
"pad.modals.badChangeset.explanation": "Сэрвэр сынхранізацыі вызначыў зробленае вамі рэдагаваньне як недапушчальнае.",
|
|
||||||
"pad.modals.badChangeset.cause": "Гэта можа адбывацца празь няслушную канфігурацыю сэрвэра або празь іншыя нечаканыя дзеяньні. Калі ласка, скантактуйцеся з адміністратарам, калі вы лічыце, што гэта памылка. Паспрабуйце перападлучыцца, каб працягнуць рэдагаваньне.",
|
|
||||||
"pad.modals.corruptPad.explanation": "Дакумэнт, да якога вы спрабуеце атрымаць доступ, пашкоджаны.",
|
|
||||||
"pad.modals.corruptPad.cause": "Гэта можа быць выклікана няправільнай канфігурацыяй сэрвэру або іншымі нечаканымі дзеяньнямі. Калі ласка, скантактуйцеся з адміністратарам службы.",
|
|
||||||
"pad.modals.deleted": "Выдалены.",
|
|
||||||
"pad.modals.deleted.explanation": "Гэты дакумэнт быў выдалены.",
|
|
||||||
"pad.modals.disconnected": "Вы былі адключаныя.",
|
|
||||||
"pad.modals.disconnected.explanation": "Злучэньне з сэрвэрам было страчанае",
|
|
||||||
"pad.modals.disconnected.cause": "Магчыма, сэрвэр недаступны. Калі ласка, паведаміце адміністратару службы, калі праблема будзе паўтарацца.",
|
|
||||||
"pad.share": "Падзяліцца дакумэнтам",
|
|
||||||
"pad.share.readonly": "Толькі для чытаньня",
|
|
||||||
"pad.share.link": "Спасылка",
|
|
||||||
"pad.share.emebdcode": "Укласьці URL",
|
|
||||||
"pad.chat": "Чат",
|
|
||||||
"pad.chat.title": "Адкрыць чат для гэтага дакумэнту.",
|
|
||||||
"pad.chat.loadmessages": "Загрузіць болей паведамленьняў",
|
|
||||||
"timeslider.pageTitle": "Часавая шкала {{appTitle}}",
|
|
||||||
"timeslider.toolbar.returnbutton": "Вярнуцца да дакумэнту",
|
|
||||||
"timeslider.toolbar.authors": "Аўтары:",
|
|
||||||
"timeslider.toolbar.authorsList": "Няма аўтараў",
|
|
||||||
"timeslider.toolbar.exportlink.title": "Экспарт",
|
|
||||||
"timeslider.exportCurrent": "Экспартаваць актуальную вэрсію як:",
|
|
||||||
"timeslider.version": "Вэрсія {{version}}",
|
|
||||||
"timeslider.saved": "Захавана {{day}}.{{month}}.{{year}}",
|
|
||||||
"timeslider.playPause": "Прайграць / спыніць зьмест дакумэнту",
|
|
||||||
"timeslider.backRevision": "Вярнуць рэдагаваньне гэтага дакумэнту",
|
|
||||||
"timeslider.forwardRevision": "Перайсьці да наступнага рэдагаваньня гэтага дакумэнту",
|
|
||||||
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
|
||||||
"timeslider.month.january": "студзень",
|
|
||||||
"timeslider.month.february": "люты",
|
|
||||||
"timeslider.month.march": "сакавік",
|
|
||||||
"timeslider.month.april": "красавік",
|
|
||||||
"timeslider.month.may": "травень",
|
|
||||||
"timeslider.month.june": "чэрвень",
|
|
||||||
"timeslider.month.july": "ліпень",
|
|
||||||
"timeslider.month.august": "жнівень",
|
|
||||||
"timeslider.month.september": "верасень",
|
|
||||||
"timeslider.month.october": "кастрычнік",
|
|
||||||
"timeslider.month.november": "лістапад",
|
|
||||||
"timeslider.month.december": "сьнежань",
|
|
||||||
"timeslider.unnamedauthors": "{{num}} {[plural(num) one: безыменны аўтар, few: безыменныя аўтары, many: безыменных аўтараў, other: безыменных аўтараў ]}",
|
|
||||||
"pad.savedrevs.marked": "Гэтая вэрсія цяпер пазначаная як захаваная",
|
|
||||||
"pad.savedrevs.timeslider": "Вы можаце пабачыць захаваныя вэрсіі з дапамогай шкалы часу",
|
|
||||||
"pad.userlist.entername": "Увядзіце вашае імя",
|
|
||||||
"pad.userlist.unnamed": "безыменны",
|
|
||||||
"pad.userlist.guest": "Госьць",
|
|
||||||
"pad.userlist.deny": "Адхіліць",
|
|
||||||
"pad.userlist.approve": "Зацьвердзіць",
|
|
||||||
"pad.editbar.clearcolors": "Ачысьціць аўтарскія колеры ва ўсім дакумэнце?",
|
|
||||||
"pad.impexp.importbutton": "Імпартаваць зараз",
|
|
||||||
"pad.impexp.importing": "Імпартаваньне…",
|
|
||||||
"pad.impexp.confirmimport": "Імпарт файла перазапіша цяперашні тэкст дакумэнту. Вы ўпэўненыя, што хочаце працягваць?",
|
|
||||||
"pad.impexp.convertFailed": "Не атрымалася імпартаваць гэты файл. Калі ласка, выкарыстайце іншы фармат дакумэнту або скапіюйце ўручную.",
|
|
||||||
"pad.impexp.padHasData": "Мы не змаглі імпартаваць гэты файл, бо дакумэнт ужо мае зьмены, калі ласка, імпартуйце ў новы дакумэнт",
|
|
||||||
"pad.impexp.uploadFailed": "Загрузка не атрымалася, калі ласка, паспрабуйце яшчэ раз",
|
|
||||||
"pad.impexp.importfailed": "Памылка імпарту",
|
|
||||||
"pad.impexp.copypaste": "Калі ласка, скапіюйце і ўстаўце",
|
|
||||||
"pad.impexp.exportdisabled": "Экспарт у фармаце {{type}} адключаны. Калі ласка, зьвярніцеся да вашага сыстэмнага адміністратара па падрабязнасьці."
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Vodnokon4e",
|
|
||||||
"StanProg"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "Нов пад",
|
|
||||||
"index.createOpenPad": "или създаване/отваряне на пад с име:",
|
|
||||||
"pad.toolbar.bold.title": "Получер (Ctrl+B)",
|
|
||||||
"pad.toolbar.italic.title": "Наклонен (Ctrl+I)",
|
|
||||||
"pad.toolbar.underline.title": "Подчертан (Ctrl+U)",
|
|
||||||
"pad.toolbar.strikethrough.title": "Зачеркнат (Ctrl+5)",
|
|
||||||
"pad.toolbar.ol.title": "Подреден списък (Ctrl+Shift+N)",
|
|
||||||
"pad.toolbar.ul.title": "Неподреден списък (Ctrl+Shift+L)",
|
|
||||||
"pad.toolbar.indent.title": "Отстъп (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "Премахване на отстъпа (Shift+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "Отмяна (Ctrl+Z)",
|
|
||||||
"pad.toolbar.redo.title": "Връщане (Ctrl+Y)",
|
|
||||||
"pad.toolbar.settings.title": "Настройки",
|
|
||||||
"pad.colorpicker.save": "Съхраняване",
|
|
||||||
"pad.colorpicker.cancel": "Отказване",
|
|
||||||
"pad.loading": "Зареждане...",
|
|
||||||
"pad.wrongPassword": "Неправилна парола",
|
|
||||||
"pad.settings.language": "Език:",
|
|
||||||
"pad.importExport.exportetherpad": "Etherpad",
|
|
||||||
"pad.importExport.exporthtml": "HTML",
|
|
||||||
"pad.importExport.exportplain": "Обикновен текст",
|
|
||||||
"pad.importExport.exportword": "Microsoft Word",
|
|
||||||
"pad.importExport.exportpdf": "PDF",
|
|
||||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
|
||||||
"pad.modals.cancel": "Отказване",
|
|
||||||
"pad.modals.userdup": "Отворен в друг прозорец",
|
|
||||||
"pad.modals.userdup.explanation": "Изглежда, че този пад е отворен на повече от един раздел в браузъра на компютъра.",
|
|
||||||
"pad.modals.looping.explanation": "Има проблеми с комуникацията със сървъра за синхронизация.",
|
|
||||||
"pad.modals.looping.cause": "Може би сте свързани чрез несъвместима защитна стена или прокси.",
|
|
||||||
"pad.modals.initsocketfail": "Сървърът е недостъпен.",
|
|
||||||
"pad.modals.initsocketfail.explanation": "Свързването със сървъра за синхронизация е неуспешно.",
|
|
||||||
"pad.modals.initsocketfail.cause": "Това вероятно се дължи на проблем с браузъра Ви или връзката Ви с Интернет.",
|
|
||||||
"pad.modals.slowcommit.explanation": "Сървърът не отговаря.",
|
|
||||||
"pad.modals.slowcommit.cause": "Това може да се дължи на проблеми с мрежовите връзки.",
|
|
||||||
"pad.modals.deleted": "Изтрито.",
|
|
||||||
"pad.share.readonly": "Само за четене",
|
|
||||||
"pad.share.link": "Препратка",
|
|
||||||
"pad.share.emebdcode": "Постави URL",
|
|
||||||
"pad.chat": "Чат",
|
|
||||||
"pad.chat.title": "Отваряне на чат за този пад.",
|
|
||||||
"pad.chat.loadmessages": "Зареждане на повече съобщения",
|
|
||||||
"timeslider.toolbar.returnbutton": "Връщане към пада",
|
|
||||||
"timeslider.toolbar.authors": "Автори:",
|
|
||||||
"timeslider.toolbar.authorsList": "Няма автори",
|
|
||||||
"timeslider.toolbar.exportlink.title": "Изнасяне",
|
|
||||||
"timeslider.exportCurrent": "Изнасяне на текущата версия като:",
|
|
||||||
"timeslider.version": "Версия {{version}}",
|
|
||||||
"timeslider.month.january": "януари",
|
|
||||||
"timeslider.month.february": "февруари",
|
|
||||||
"timeslider.month.march": "март",
|
|
||||||
"timeslider.month.april": "април",
|
|
||||||
"timeslider.month.may": "май",
|
|
||||||
"timeslider.month.june": "юни",
|
|
||||||
"timeslider.month.july": "юли",
|
|
||||||
"timeslider.month.august": "август",
|
|
||||||
"timeslider.month.september": "септември",
|
|
||||||
"timeslider.month.october": "октомври",
|
|
||||||
"timeslider.month.november": "ноември",
|
|
||||||
"timeslider.month.december": "декември",
|
|
||||||
"pad.userlist.entername": "Въведете вашето име",
|
|
||||||
"pad.userlist.guest": "Гост"
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Baloch Afghanistan"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "یاداشتی نوکین کتابچه",
|
|
||||||
"index.createOpenPad": "یا جوڑ\t کورتین/پاچ کورتین یک کتابچه ئی یاداشتی بی نام:",
|
|
||||||
"pad.toolbar.bold.title": "پررنگ (Ctrl-B)",
|
|
||||||
"pad.toolbar.italic.title": "چوّٹ (Ctrl-I)",
|
|
||||||
"pad.toolbar.underline.title": "جهلگ خط (Ctrl-U)",
|
|
||||||
"pad.toolbar.strikethrough.title": "خط وارته (Ctrl+5)",
|
|
||||||
"pad.toolbar.ol.title": "ترتیب بوتگین لر لیست (Ctrl+Shift+N)",
|
|
||||||
"pad.toolbar.ul.title": "ترتیب نه بوتگین لر لیست (Ctrl+Shift+L)",
|
|
||||||
"pad.toolbar.indent.title": "بیئتئ بوتگین (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "در آتگی (Shift+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "باطلکورتین (Ctrl-Z)",
|
|
||||||
"pad.toolbar.redo.title": "شه نوک (Ctrl-Y)",
|
|
||||||
"pad.toolbar.clearAuthorship.title": "نویسوکئ رنگانی پاک کورتین (Ctrl+Shift+C)",
|
|
||||||
"pad.toolbar.import_export.title": "بی تئ کورتین/دَر کورتین شه/بی رکم رکمین قالیبان",
|
|
||||||
"pad.toolbar.timeslider.title": "وختئ لَگوشوک",
|
|
||||||
"pad.toolbar.savedRevision.title": "نسخه ئی ذخیره کورتین",
|
|
||||||
"pad.toolbar.settings.title": "تنظیمات",
|
|
||||||
"pad.colorpicker.save": "ذخیره",
|
|
||||||
"pad.colorpicker.cancel": "کنسیل",
|
|
||||||
"pad.loading": "لودینگ...",
|
|
||||||
"pad.wrongPassword": "شمی پاسورد جووان نه اینت",
|
|
||||||
"pad.settings.padSettings": "یاداشتئ دفترچه ئی تنظیمات",
|
|
||||||
"pad.settings.myView": "نئ دیست",
|
|
||||||
"pad.settings.stickychat": "هبر موچین وختا بی دیستئ تاکدیمئ سرا بیئت",
|
|
||||||
"pad.settings.colorcheck": "نویسوکی رنگ ئان",
|
|
||||||
"pad.settings.linenocheck": "خط ئانی نمبر",
|
|
||||||
"pad.settings.rtlcheck": "محتوایی وانتین شه راست بی چپا؟",
|
|
||||||
"pad.settings.fontType": "قلم رکم:",
|
|
||||||
"pad.settings.fontType.normal": "ساددگ",
|
|
||||||
"pad.settings.fontType.monospaced": "Monospace",
|
|
||||||
"pad.settings.globalView": "سراسرین دیست یا نما",
|
|
||||||
"pad.settings.language": "زبان:",
|
|
||||||
"pad.importExport.exporthtml": "HTML",
|
|
||||||
"pad.importExport.exportplain": "ساده گین متن",
|
|
||||||
"pad.importExport.exportword": "Microsoft Word",
|
|
||||||
"pad.importExport.exportpdf": "PDF",
|
|
||||||
"pad.importExport.exportopen": "ODF (پاچین سندئ قالب)",
|
|
||||||
"pad.importExport.abiword.innerHTML": "شما تا توانیت که شه ساده گین متنی ئین قالب یا اچتیامال بی تئ کنیت . په گیشتیرین کارا ئییان پیشرفته ئین بی تئ کورتینا <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">AbiWord</a> نصب کنیت.",
|
|
||||||
"pad.modals.connected": "وصل بوت.",
|
|
||||||
"pad.modals.userdup": "نوکین دروازه گئ پاچ کورتین",
|
|
||||||
"pad.modals.unauth": "مجاز نه اینت",
|
|
||||||
"pad.modals.deleted.explanation": "ای یاداشتی دفترچه پاک بوته.",
|
|
||||||
"pad.share.readonly": "فقط وانتین",
|
|
||||||
"pad.share.link": "لینک",
|
|
||||||
"pad.chat": "چت وهبر",
|
|
||||||
"timeslider.toolbar.exportlink.title": "دَر کورتین",
|
|
||||||
"timeslider.month.january": "جنوری",
|
|
||||||
"timeslider.month.february": "فیبروری",
|
|
||||||
"timeslider.month.march": "مارچ",
|
|
||||||
"timeslider.month.april": "اپریل",
|
|
||||||
"timeslider.month.may": "می",
|
|
||||||
"timeslider.month.june": "جون",
|
|
||||||
"timeslider.month.july": "جولای",
|
|
||||||
"timeslider.month.august": "اگوست",
|
|
||||||
"timeslider.month.september": "سیپٹمبر",
|
|
||||||
"timeslider.month.october": "اکتوبر",
|
|
||||||
"timeslider.month.november": "نوامبر",
|
|
||||||
"timeslider.month.december": "ڈ\tسمبر",
|
|
||||||
"timeslider.unnamedauthors": "{{num}} بی نامین نویسوک",
|
|
||||||
"pad.userlist.entername": "وتئ ناما نیویشته بکنیت",
|
|
||||||
"pad.userlist.unnamed": "بی نام",
|
|
||||||
"pad.userlist.guest": "مهمان",
|
|
||||||
"pad.userlist.deny": "رد کورتین",
|
|
||||||
"pad.userlist.approve": "قبول کورتین",
|
|
||||||
"pad.impexp.importbutton": "انون بی تئ کن",
|
|
||||||
"pad.impexp.importing": "بی بی تئ کورتینی حالا...",
|
|
||||||
"pad.impexp.uploadFailed": "آپلود انجام نه بوت، پدا کوشش کن",
|
|
||||||
"pad.impexp.copypaste": "کپی پیست کَنیت"
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
{
|
|
||||||
"@metadata": {
|
|
||||||
"authors": [
|
|
||||||
"Bellayet",
|
|
||||||
"Nasir8891",
|
|
||||||
"Sankarshan",
|
|
||||||
"Aftab1995",
|
|
||||||
"Aftabuzzaman"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"index.newPad": "নতুন প্যাড",
|
|
||||||
"index.createOpenPad": "অথবা নাম লিখে প্যাড খুলুন/তৈরী করুন:",
|
|
||||||
"pad.toolbar.bold.title": "গাঢ় (Ctrl-B)",
|
|
||||||
"pad.toolbar.italic.title": "বাঁকা (Ctrl+I)",
|
|
||||||
"pad.toolbar.underline.title": "নিম্নরেখা (Ctrl+U)",
|
|
||||||
"pad.toolbar.strikethrough.title": "অবচ্ছেদন (Ctrl+5)",
|
|
||||||
"pad.toolbar.ol.title": "সারিবদ্ধ তালিকা (Ctrl+Shift+N)",
|
|
||||||
"pad.toolbar.ul.title": "অসারিবদ্ধ তালিকা (Ctrl+Shift+L)",
|
|
||||||
"pad.toolbar.indent.title": "প্রান্তিককরণ (TAB)",
|
|
||||||
"pad.toolbar.unindent.title": "আউটডেন্ট (Shift+TAB)",
|
|
||||||
"pad.toolbar.undo.title": "বাতিল করুন (Ctrl-Z)",
|
|
||||||
"pad.toolbar.redo.title": "পুনরায় করুন (Ctrl-Y)",
|
|
||||||
"pad.toolbar.clearAuthorship.title": "কৃতি রং পরিষ্কার করুন (Ctrl+Shift+C)",
|
|
||||||
"pad.toolbar.import_export.title": "ভিন্ন ফাইল বিন্যাসে আমদানি/রপ্তানি করুন",
|
|
||||||
"pad.toolbar.timeslider.title": "টাইমস্লাইডার",
|
|
||||||
"pad.toolbar.savedRevision.title": "সংস্করণ সংরক্ষণ করুন",
|
|
||||||
"pad.toolbar.settings.title": "সেটিং",
|
|
||||||
"pad.toolbar.embed.title": "এই প্যাডটি শেয়ার ও এম্বেড করুন",
|
|
||||||
"pad.toolbar.showusers.title": "এই প্যাডের ব্যবহারকারীদের দেখান",
|
|
||||||
"pad.colorpicker.save": "সংরক্ষণ",
|
|
||||||
"pad.colorpicker.cancel": "বাতিল",
|
|
||||||
"pad.loading": "লোড হচ্ছে...",
|
|
||||||
"pad.noCookie": "কুকি পাওয়া যায়নি। দয়া করে আপনার ব্রাউজারে কুকি অনুমতি দিন!",
|
|
||||||
"pad.passwordRequired": "এই প্যাড-টি দেখার জন্য আপনাকে পাসওয়ার্ড ব্যবহার করতে হবে",
|
|
||||||
"pad.permissionDenied": "দুঃখিত, এ প্যাড-টি দেখার অধিকার আপনার নেই",
|
|
||||||
"pad.wrongPassword": "আপনার পাসওয়ার্ড সঠিক নয়",
|
|
||||||
"pad.settings.padSettings": "প্যাডের স্থাপন",
|
|
||||||
"pad.settings.myView": "আমার দৃশ্য",
|
|
||||||
"pad.settings.stickychat": "চ্যাট সক্রীনে প্রদর্শন করা হবে",
|
|
||||||
"pad.settings.chatandusers": "চ্যাট এবং ব্যবহারকারী দেখান",
|
|
||||||
"pad.settings.colorcheck": "লেখকদের নিজস্ব নির্বাচিত রং",
|
|
||||||
"pad.settings.linenocheck": "লাইন নম্বর",
|
|
||||||
"pad.settings.rtlcheck": "ডান থেকে বামে বিষয়বস্তু পড়বেন?",
|
|
||||||
"pad.settings.fontType": "ফন্টের প্রকার:",
|
|
||||||
"pad.settings.fontType.normal": "সাধারণ",
|
|
||||||
"pad.settings.fontType.monospaced": "Monospace",
|
|
||||||
"pad.settings.globalView": "সর্বব্যাপী দৃশ্য",
|
|
||||||
"pad.settings.language": "ভাষা:",
|
|
||||||
"pad.importExport.import_export": "আমদানি/রপ্তানি",
|
|
||||||
"pad.importExport.import": "কোন টেক্সট ফাইল বা নথি আপলোড করুন",
|
|
||||||
"pad.importExport.importSuccessful": "সফল!",
|
|
||||||
"pad.importExport.export": "এই প্যাডটি রপ্তানি করুন:",
|
|
||||||
"pad.importExport.exportetherpad": "ইথারপ্যাড",
|
|
||||||
"pad.importExport.exporthtml": "এইচটিএমএল",
|
|
||||||
"pad.importExport.exportplain": "সাধারণ লেখা",
|
|
||||||
"pad.importExport.exportword": "মাইক্রোসফট ওয়ার্ড",
|
|
||||||
"pad.importExport.exportpdf": "পিডিএফ",
|
|
||||||
"pad.importExport.exportopen": "ওডিএফ (ওপেন ডকুমেন্ট ফরম্যাট)",
|
|
||||||
"pad.modals.connected": "যোগাযোগ সফল",
|
|
||||||
"pad.modals.reconnecting": "আপনার প্যাডের সাথে সংযোগস্থাপন করা হচ্ছে..",
|
|
||||||
"pad.modals.forcereconnect": "পুনরায় সংযোগস্থাপনের চেষ্টা",
|
|
||||||
"pad.modals.userdup": "অন্য উইন্ডো-তে খোলা হয়েছে",
|
|
||||||
"pad.modals.unauth": "আপনার অধিকার নেই",
|
|
||||||
"pad.modals.initsocketfail": "সার্ভারে পৌঁছানো যাচ্ছে না।",
|
|
||||||
"pad.modals.slowcommit.explanation": "সার্ভার সাড়া দিচ্ছে না।",
|
|
||||||
"pad.modals.deleted": "অপসারিত।",
|
|
||||||
"pad.modals.deleted.explanation": "এই প্যাডটি অপসারণ করা হয়েছে।",
|
|
||||||
"pad.modals.disconnected": "আপনি সংযোগ বিচ্ছিন্ন হয়েছে গেছে।",
|
|
||||||
"pad.modals.disconnected.explanation": "সার্ভারের সাথে যোগাযোগ করা যাচ্ছে না",
|
|
||||||
"pad.share": "শেয়ার করুন",
|
|
||||||
"pad.share.readonly": "শুধু পড়া",
|
|
||||||
"pad.share.link": "লিংক",
|
|
||||||
"pad.share.emebdcode": "ইউআরএল সংযোজন",
|
|
||||||
"pad.chat": "চ্যাট",
|
|
||||||
"pad.chat.title": "এই প্যাডের জন্য চ্যাট চালু করুন।",
|
|
||||||
"pad.chat.loadmessages": "আরও বার্তা লোড করুন",
|
|
||||||
"timeslider.toolbar.returnbutton": "প্যাডে ফিরে যাও",
|
|
||||||
"timeslider.toolbar.authors": "লেখকগণ:",
|
|
||||||
"timeslider.toolbar.authorsList": "কোনো লেখক নেই",
|
|
||||||
"timeslider.toolbar.exportlink.title": "রপ্তানি",
|
|
||||||
"timeslider.exportCurrent": "বর্তমান সংস্করণটি রপ্তানি করুন:",
|
|
||||||
"timeslider.version": "সংস্করণ {{version}}",
|
|
||||||
"timeslider.saved": "সংরক্ষিত হয় {{month}} {{day}}, {{year}}",
|
|
||||||
"timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
|
||||||
"timeslider.month.january": "জানুয়ারি",
|
|
||||||
"timeslider.month.february": "ফেব্রুয়ারি",
|
|
||||||
"timeslider.month.march": "মার্চ",
|
|
||||||
"timeslider.month.april": "এপ্রিল",
|
|
||||||
"timeslider.month.may": "মে",
|
|
||||||
"timeslider.month.june": "জুন",
|
|
||||||
"timeslider.month.july": "জুলাই",
|
|
||||||
"timeslider.month.august": "আগস্ট",
|
|
||||||
"timeslider.month.september": "সেপ্টেম্বর",
|
|
||||||
"timeslider.month.october": "অক্টোবর",
|
|
||||||
"timeslider.month.november": "নভেম্বর",
|
|
||||||
"timeslider.month.december": "ডিসেম্বর",
|
|
||||||
"timeslider.unnamedauthors": "নামবিহীন {{num}} জন {[plural(num) one: লেখক, other: লেখক ]}",
|
|
||||||
"pad.userlist.entername": "আপনার নাম লিখুন",
|
|
||||||
"pad.userlist.unnamed": "কোন নাম নির্বাচন করা হয়নি",
|
|
||||||
"pad.userlist.guest": "অতিথি",
|
|
||||||
"pad.userlist.deny": "প্রত্যাখ্যান",
|
|
||||||
"pad.userlist.approve": "অনুমোদিত",
|
|
||||||
"pad.impexp.importbutton": "এখন আমদানি করুন",
|
|
||||||
"pad.impexp.importing": "আমদানি হচ্ছে...",
|
|
||||||
"pad.impexp.padHasData": "আমরা এই ফাইলটি আমদানি করতে সক্ষম হয়নি কারণ এই প্যাড ইতিমধ্যে পরিবর্তিত হয়েছে, দয়া করে একটি নতুন প্যাডে অামদানি করুন।",
|
|
||||||
"pad.impexp.uploadFailed": "আপলোড করতে ব্যর্থ, দয়া করে আবার চেষ্টা করুন",
|
|
||||||
"pad.impexp.importfailed": "আমদানি ব্যর্থ",
|
|
||||||
"pad.impexp.copypaste": "দয়া করে অনুলিপি প্রতিলেপন করুন",
|
|
||||||
"pad.impexp.exportdisabled": "{{type}} হিসেবে রপ্তানি করা নিষ্ক্রিয় আছে। বিস্তারিত জানার জন্য আপনার সিস্টেম প্রশাসকের সাথে যোগাযোগ করুন।"
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue