Initial commit
This commit is contained in:
commit
4f415b6190
77 changed files with 52208 additions and 0 deletions
661
LICENSE
Normal file
661
LICENSE
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are 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.
|
||||
|
||||
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.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
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 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 work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero 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 Affero 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 Affero 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 Affero 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.
|
||||
|
||||
Mobilizon
|
||||
Copyright (C) 2018 - 2024 Framasoft
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
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 AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
11
bin/transform_weblate.sh
Normal file
11
bin/transform_weblate.sh
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Transform Weblate JSON files to js files
|
||||
#
|
||||
# 1) download the translation files as a zip from
|
||||
# https://weblate.framasoft.org/projects/mobilizon/
|
||||
# 2) unzip the archive, copy all frontend/src/i18n/ to res/locale
|
||||
# 3) run this script from the base directory
|
||||
|
||||
find ./res/locale -name *.json -exec sh -c 'sed -e "1s/^/CTX.setMessages\(/" -e "\$s/\$/);/" $0 > ./res/locale/$(basename $0 .json).js' {} \;
|
||||
rm ./res/locale/*.json
|
||||
81
embed_group.html
Normal file
81
embed_group.html
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Mobilistache, Mobilizon with a mustache</title>
|
||||
<link rel="stylesheet" type="text/css" href="res/css/base.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/layout.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_base.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_button.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_date.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_event.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_group.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_tag.css">
|
||||
<script type="text/javascript" src="src/context.js"></script>
|
||||
<script type="text/javascript" src="src/global.js"></script>
|
||||
<script type="text/javascript" src="src/route.js"></script>
|
||||
<script type="text/javascript" src="src/model/event.js"></script>
|
||||
<script type="text/javascript" src="src/model/group.js"></script>
|
||||
<script type="text/javascript" src="src/request/req_group.js"></script>
|
||||
<script type="text/javascript" src="src/widgets/button_view.js"></script>
|
||||
<script type="text/javascript" src="src/widgets/date_view.js"></script>
|
||||
<script type="text/javascript" src="src/widgets/event_view.js"></script>
|
||||
<script type="text/javascript" src="src/widgets/tag_tpl.js"></script>
|
||||
<script type="text/javascript">
|
||||
class PageGroupEmbed extends Page {
|
||||
boot() {
|
||||
var query = splitHandle(GET("handle"));
|
||||
if (!query) {
|
||||
console.error("Error reading handle from " + location.href);
|
||||
document.getElementById("content").innerHTML = "<p>Incorrect handle \"" + GET("handle") + "\", check the \"handle\" parameter in your url.</p>";
|
||||
return;
|
||||
}
|
||||
var host = query.host;
|
||||
var name = query.name;
|
||||
var customCss = GET("css");
|
||||
if (customCss) {
|
||||
var css = document.createElement("link");
|
||||
css.setAttribute("rel", "stylesheet");
|
||||
css.setAttribute("type", "text/css");
|
||||
css.setAttribute("href", customCss);
|
||||
document.getElementsByTagName("head")[0].appendChild(css);
|
||||
}
|
||||
if (host && name) {
|
||||
requestGroup(host, name, function(request, status, result) {
|
||||
if (status === 200) {
|
||||
setTitle(result.name);
|
||||
var content = new WidgetGroupEmbed(result);
|
||||
content.showIn(document.getElementById("content"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const PAGE_GROUP_EMBED = new PageGroupEmbed();
|
||||
|
||||
class WidgetGroupEmbed extends Widget {
|
||||
constructor(group) {
|
||||
super();
|
||||
this.group = group;
|
||||
this.root = document.createElement("section");
|
||||
this.root.classList.add("group-detail", "l-section");
|
||||
var template = `<ul class="l-nobullet l-centered"></ul>`;
|
||||
this.root.innerHTML = template;
|
||||
var events = [];
|
||||
var eventList = this.root.getElementsByTagName("ul")[0];
|
||||
this.group.getEvents().forEach(evt => {
|
||||
var evtItem = new WidgetEventListItem(evt, 2, {linkToInstance: true});
|
||||
evtItem.showIn(eventList);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function embedRoute() {
|
||||
CTX.init();
|
||||
CTX.setPage(PAGE_GROUP_EMBED);
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', embedRoute);
|
||||
</script>
|
||||
</head>
|
||||
<body id="content" class="l-centered"></body>
|
||||
</html>
|
||||
45
index.html
Normal file
45
index.html
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Mobilistache, Mobilizon with a mustache</title>
|
||||
<link rel="stylesheet" type="text/css" href="res/css/base.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/layout.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_base.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_button.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_date.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_event.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_group.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/css/module_tag.css">
|
||||
<script type="text/javascript" src="src/context.js"></script>
|
||||
<script type="text/javascript" src="src/global.js"></script>
|
||||
<script type="text/javascript" src="src/route.js"></script>
|
||||
<script type="text/javascript" src="src/model/event.js"></script>
|
||||
<script type="text/javascript" src="src/model/group.js"></script>
|
||||
<script type="text/javascript" src="src/model/host.js"></script>
|
||||
<script type="text/javascript" src="src/model/user.js"></script>
|
||||
<script type="text/javascript" src="src/pages/page_event.js"></script>
|
||||
<script type="text/javascript" src="src/pages/page_event_edit.js"></script>
|
||||
<script type="text/javascript" src="src/pages/page_group.js"></script>
|
||||
<script type="text/javascript" src="src/pages/page_index.js"></script>
|
||||
<script type="text/javascript" src="src/pages/page_logout.js"></script>
|
||||
<script type="text/javascript" src="src/pages/page_user.js"></script>
|
||||
<script type="text/javascript" src="src/request/req_event.js"></script>
|
||||
<script type="text/javascript" src="src/request/req_host.js"></script>
|
||||
<script type="text/javascript" src="src/request/req_login.js"></script>
|
||||
<script type="text/javascript" src="src/request/req_group.js"></script>
|
||||
<script type="text/javascript" src="src/widgets/base_view.js"></script>
|
||||
<script type="text/javascript" src="src/widgets/button_view.js"></script>
|
||||
<script type="text/javascript" src="src/widgets/date_view.js"></script>
|
||||
<script type="text/javascript" src="src/widgets/event_view.js"></script>
|
||||
<script type="text/javascript" src="src/widgets/group_view.js"></script>
|
||||
<script type="text/javascript" src="src/widgets/tag_tpl.js"></script>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('DOMContentLoaded', boot);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="header"></nav>
|
||||
<div id="content"></div>
|
||||
</body>
|
||||
</html>
|
||||
66
res/css/base.css
Normal file
66
res/css/base.css
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Base rules
|
||||
* See smacss.com
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: #27272a;
|
||||
color: #d1d5db;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
color: #ffffff;
|
||||
}
|
||||
h1 {
|
||||
font-size: xx-large;
|
||||
font-weight: bold;
|
||||
}
|
||||
h2 {
|
||||
font-size: x-large;
|
||||
font-weight: bold;
|
||||
}
|
||||
h3 {
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
menu {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
button > svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
input[type=text], input[type=datetime-local],
|
||||
input[type=password],
|
||||
select,
|
||||
textarea {
|
||||
background-color: #3f3f46;
|
||||
color: #d1d5db;
|
||||
border-radius: 0.2rem;
|
||||
border-width: 0.1rem;
|
||||
border-color: #e5e7eb;
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
input:focus, select:focus, textarea:focus {
|
||||
outline: 0.2rem solid #3b82f6;
|
||||
}
|
||||
|
||||
45
res/css/layout.css
Normal file
45
res/css/layout.css
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Layout rules
|
||||
* See smacss.com
|
||||
*/
|
||||
|
||||
.l-section {
|
||||
max-width: 70rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.l-subsection {
|
||||
max-width: 25rem;
|
||||
flex-grow: 1;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.l-nobullet {
|
||||
list-style-type: none;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.l-centered {
|
||||
margin: auto;
|
||||
}
|
||||
.l-centering > * {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.l-flexcenter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
column-gap: 0.4rem;
|
||||
}
|
||||
.l-flexjustify {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
column-gap: 0.4rem;
|
||||
}
|
||||
|
||||
.l-flexfill {
|
||||
flex-grow: 1;
|
||||
}
|
||||
66
res/css/module_base.css
Normal file
66
res/css/module_base.css
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
.header {
|
||||
background-color: #18181b;
|
||||
padding: 0.6rem 1rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
object-fit: cover;
|
||||
object-position: 50% 50%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.notice {
|
||||
font-size: small;
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.form-field,
|
||||
.form-control {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.form-field > label,
|
||||
.form-field > .label-like {
|
||||
display: block;
|
||||
}
|
||||
.form-field.form-field-checkbox > label {
|
||||
display: inline-block;
|
||||
}
|
||||
.form-field > input {
|
||||
display: inline-block;
|
||||
}
|
||||
.form-field > .input-like {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
.form-field-short {
|
||||
max-width: 30rem;
|
||||
}
|
||||
|
||||
.input-withicon {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.input-icon > button {
|
||||
background-color: transparent;
|
||||
border: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
19
res/css/module_button.css
Normal file
19
res/css/module_button.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
.btn {
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem 1rem;
|
||||
text-decoration: none;
|
||||
border-width: 0px;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.btn-nav {
|
||||
color: #ffffff;
|
||||
background-color: #1e7b9d;
|
||||
}
|
||||
.btn-nav:hover {
|
||||
background-color: #155668;
|
||||
}
|
||||
.btn-nav:active {
|
||||
box-shadow: 0px 0px 0px 0.25rem #93c5fd;
|
||||
}
|
||||
37
res/css/module_date.css
Normal file
37
res/css/module_date.css
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Date modules
|
||||
* See smacss.com, src/widgets/date_tpl.js
|
||||
*/
|
||||
.date-icon {
|
||||
padding: 0.1rem 0.5rem;
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
border-top: 0.5rem solid #f3425f;
|
||||
border-radius: 0.5rem;
|
||||
background-color: #374151;
|
||||
font-size: small;
|
||||
font-variant: small-caps;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.date-icon-short {
|
||||
line-height: 1.25;
|
||||
min-width: 2.5rem;
|
||||
}
|
||||
|
||||
.date-icon-full > div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
column-gap: 0.3rem;
|
||||
}
|
||||
.date-icon-date {
|
||||
font-weight: bold;
|
||||
font-size: large;
|
||||
}
|
||||
.date-icon-time {
|
||||
font-weight: bold;
|
||||
}
|
||||
72
res/css/module_event.css
Normal file
72
res/css/module_event.css
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Event list item module
|
||||
* See smacss.com, src/widgets/event_view.js
|
||||
*/
|
||||
|
||||
.event-banner {
|
||||
max-height: 20rem;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.event-banner > img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0.5rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.event-listitem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 0.5rem;
|
||||
background-color: #474467;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem;
|
||||
max-width: 50rem;
|
||||
}
|
||||
|
||||
.event-listitem-banner {
|
||||
width: 12rem;
|
||||
min-width: 12rem;
|
||||
height: 10rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.event-listitem-banner > img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.event-listitem-content {
|
||||
padding: 0.5rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.event-listitem header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.event-listitem h1,
|
||||
.event-listitem h2,
|
||||
.event-listitem h3,
|
||||
.event-listitem h4,
|
||||
.event-listitem h5,
|
||||
.event-listitem h6 {
|
||||
font-size: large;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.event-listitem nav {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.event-detail > header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
77
res/css/module_group.css
Normal file
77
res/css/module_group.css
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
.group-banner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.group-banner-background {
|
||||
height: 30vh;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
object-position: 50% 50%;
|
||||
}
|
||||
|
||||
.group-banner > div {
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.group-banner-avatar {
|
||||
position: absolute;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
top: -7rem;
|
||||
}
|
||||
|
||||
.group-listitem {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
column-gap: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
background-color: #474467;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem;
|
||||
max-width: 50rem;
|
||||
}
|
||||
|
||||
.group-listitem-avatar {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.group-listitem-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.event-listitem header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.group-listitem h1,
|
||||
.group-listitem h2,
|
||||
.group-listitem h3,
|
||||
.group-listitem h4,
|
||||
.group-listitem h5,
|
||||
.group-listitem h6 {
|
||||
font-size: large;
|
||||
margin: 0px;
|
||||
|
||||
}
|
||||
|
||||
.group-listitem-handle {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.group-listitem nav {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
15
res/css/module_tag.css
Normal file
15
res/css/module_tag.css
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
|
||||
.taglist {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 0.3rem;
|
||||
}
|
||||
.tag {
|
||||
padding: 0.3rem;
|
||||
border-radius: 0.2rem;
|
||||
background-color: #e6e4f4;
|
||||
font-size: small;
|
||||
color: #3c416e;
|
||||
}
|
||||
1257
res/locale/ar.js
Normal file
1257
res/locale/ar.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/be.js
Normal file
1257
res/locale/be.js
Normal file
File diff suppressed because it is too large
Load diff
1260
res/locale/bn.js
Normal file
1260
res/locale/bn.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/ca.js
Normal file
1257
res/locale/ca.js
Normal file
File diff suppressed because it is too large
Load diff
1661
res/locale/cs.js
Normal file
1661
res/locale/cs.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/cy.js
Normal file
1257
res/locale/cy.js
Normal file
File diff suppressed because it is too large
Load diff
219
res/locale/da.js
Normal file
219
res/locale/da.js
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
CTX.setMessages({
|
||||
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Et brugervenligt, befriende og etisk værktøj for at samles, organisere og mobilisere.",
|
||||
"A validation email was sent to {email}": "En godkendelsesemail er blevet sendt til {email}",
|
||||
"Abandon editing": "Afbryd redigering",
|
||||
"About": "Om",
|
||||
"About Mobilizon": "Om Mobilizon",
|
||||
"About this event": "Om denne begivenhed",
|
||||
"About this instance": "Om denne udbyder",
|
||||
"Accepted": "Accepteret",
|
||||
"Account": "Konto",
|
||||
"Add": "Tilføj",
|
||||
"Add a note": "Tilføj et notat",
|
||||
"Add an address": "Tilføj en addresse",
|
||||
"Add an instance": "Tilføj en udbyder",
|
||||
"Add some tags": "Tilføj nogle nøgleord",
|
||||
"Add to my calendar": "Tilføj til min kalender",
|
||||
"Additional comments": "Yderligere kommentarer",
|
||||
"Admin": "Administrator",
|
||||
"Admin settings successfully saved.": "Administrator indstillinger er blevet gemt.",
|
||||
"Administration": "Administrering",
|
||||
"All the places have already been taken": "Alle pladserne er allerede optaget",
|
||||
"Allow registrations": "Tillad registrering",
|
||||
"Anonymous participant": "Anonym deltager",
|
||||
"Anonymous participants will be asked to confirm their participation through e-mail.": "Anonyme deltagere vil blive bedt om at bekræfte deres deltagelse via e-mail.",
|
||||
"Anonymous participations": "Anonyme deltagelser",
|
||||
"Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "Er du helt sikker på at du vil slette hele din konto? Alt vil forsvinde. Identiteter, indstillinger, skabte begivenheder, beskeder og deltagelser vil være borte for evigt.",
|
||||
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Er du sikker på at du vil <b>slette</b> kommentaren? Denne handling kan ikke fortrydes.",
|
||||
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the discussion with the event creator or edit its event instead.": "Er du sikker på at du vil <b>slette</b> begivenheden? Denne handling kan ikke fortrydes. Du kan overveje at snakke med begivenhedens skaber eller at redigere begivenheden i stedet.",
|
||||
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Er du sikker på at du vil annullere at skabe begivenheden? Alle ændringer vil gå tabt.",
|
||||
"Are you sure you want to cancel the event edition? You'll lose all modifications.": "Er du sikker på at du vil annullere at redigere begivenheden? Alle ændringer vil gå tabt.",
|
||||
"Are you sure you want to cancel your participation at event \"{title}\"?": "Er du sikker på at du vil annullere din deltagelse i begivenheden \"{title}\"?",
|
||||
"Are you sure you want to delete this event? This action cannot be reverted.": "Er du sikker på du vil slette begivenheden? Denne handling kan ikke fortrydes.",
|
||||
"Avatar": "Avatar",
|
||||
"Back to previous page": "Tilbage til forrige side",
|
||||
"Before you can login, you need to click on the link inside it to validate your account.": "Før du kan logge ind skal du klikke på linket i den for at bekræfte din konto.",
|
||||
"By {username}": "Af {username}",
|
||||
"Cancel": "Annuller",
|
||||
"Cancel anonymous participation": "Annuller anonym deltagelse",
|
||||
"Cancel creation": "Annuller skabelse",
|
||||
"Cancel edition": "Annuller redigering",
|
||||
"Cancel my participation request…": "Annuller min anmodning om deltagelse…",
|
||||
"Cancel my participation…": "Annuller min deltagelse…",
|
||||
"Cancelled: Won't happen": "Afbrudt: Sker ikke",
|
||||
"Change": "Ændre",
|
||||
"Change my email": "Skift email",
|
||||
"Change my identity…": "Ændre min identitet…",
|
||||
"Change my password": "Skift kodeord",
|
||||
"Clear": "Ryd",
|
||||
"Click to upload": "Klik for at uploade",
|
||||
"Close": "Luk",
|
||||
"Close comments for all (except for admins)": "Luk kommentarer for alle (undtagen administratorer)",
|
||||
"Closed": "Lukket",
|
||||
"Comment deleted": "Kommentar slettet",
|
||||
"Comments": "Kommentarer",
|
||||
"Confirm my participation": "Bekræft min deltagelse",
|
||||
"Confirmed: Will happen": "Bekræftet: Kommer til at ske",
|
||||
"Continue editing": "Fortsæt med at redigere",
|
||||
"Country": "Land",
|
||||
"Create": "Skab",
|
||||
"Create a new event": "Lav en ny begivenhed",
|
||||
"Create a new group": "Lav en ny gruppe",
|
||||
"Create a new identity": "Lav en ny identitet",
|
||||
"Create group": "Skab en gruppe",
|
||||
"Create my event": "Skab min begivenhed",
|
||||
"Create my group": "Skab min gruppe",
|
||||
"Create my profile": "Skab min profil",
|
||||
"Create token": "Skab token",
|
||||
"Current identity has been changed to {identityName} in order to manage this event.": "Den aktive identitet er blevet ændret til {identityName} for at håndtere denne begivenhed.",
|
||||
"Current page": "Nuværende side",
|
||||
"Custom": "Tilpasset",
|
||||
"Custom URL": "Tilpasset addresse",
|
||||
"Custom text": "Tilpasset tekst",
|
||||
"Dashboard": "Dashboard",
|
||||
"Date": "Dato",
|
||||
"Date and time settings": "Dato og tidsinstillinger",
|
||||
"Date parameters": "Datoparametre",
|
||||
"Default": "Standard",
|
||||
"Delete": "Slet",
|
||||
"Delete account": "Slet konto",
|
||||
"Delete event": "Slet begivenhed",
|
||||
"Delete everything": "Slet alt",
|
||||
"Delete my account": "Slet min konto",
|
||||
"Delete this identity": "Slet denne identitet",
|
||||
"Delete your identity": "Slet din identitet",
|
||||
"Delete {eventTitle}": "Slet {eventTitle}",
|
||||
"Delete {preferredUsername}": "Slet {preferredUsername}",
|
||||
"Deleting comment": "Sletter kommentar",
|
||||
"Deleting event": "Sletter begivenhed",
|
||||
"Deleting my account will delete all of my identities.": "Hvis jeg sletter min konto, bliver alle mine identiteter slettet.",
|
||||
"Deleting your Mobilizon account": "Sletter din Mobilizon konto",
|
||||
"Description": "Beskrivelse",
|
||||
"Display name": "Viste navn",
|
||||
"Display participation price": "Vis pris for deltagelse",
|
||||
"Domain": "Domæne",
|
||||
"Draft": "Kladde",
|
||||
"Drafts": "Kladder",
|
||||
"Edit": "Rediger",
|
||||
"Eg: Stockholm, Dance, Chess…": "F.eks.: Stockholm, Dans, Skak…",
|
||||
"Either on the {instance} instance or on another instance.": "Enten på {instance} udbyderen eller på en anden udbyder.",
|
||||
"Either the account is already validated, either the validation token is incorrect.": "Enten er kontoen allerede godkendt, eller valideringskoden forkert.",
|
||||
"Either the email has already been changed, either the validation token is incorrect.": "Enten er emailadressen allerede blevet ændret, eller valideringskoden er forkert.",
|
||||
"Either the participation request has already been validated, either the validation token is incorrect.": "Enten er deltagelsesanmodningen allerede blevet godkendt, eller valideringskoden er forkert.",
|
||||
"Email": "Email",
|
||||
"Ends on…": "Slutter…",
|
||||
"Enter the link URL": "Indtast linket",
|
||||
"Error while changing email": "Fejl under ændring af emailadresse",
|
||||
"Error while validating account": "Fejl under godkendelse af konto",
|
||||
"Error while validating participation request": "Fejl under godkendelse af deltagelsesanmodning",
|
||||
"Event": "Begivenhed",
|
||||
"Event already passed": "Begivenheden er ovre",
|
||||
"Event cancelled": "Begivenheden er aflyst",
|
||||
"Event creation": "Skabelse af begivenhed",
|
||||
"Event edition": "Redigering af begivenhed",
|
||||
"Event list": "Liste af begivenheder",
|
||||
"Event page settings": "Indstillinger for begivenhedens side",
|
||||
"Event to be confirmed": "Begivenheden skal bekræftes",
|
||||
"Event {eventTitle} deleted": "Begivenheden {eventTitle} blev slettet",
|
||||
"Event {eventTitle} reported": "Begivenheden {eventTitle} blev indmeldt",
|
||||
"Events": "Begivenheder",
|
||||
"Ex: mobilizon.fr": "F.eks: mobilizon.fr",
|
||||
"Explore": "Udforsk",
|
||||
"Failed to save admin settings": "Kunne ikke gemme admin indstillinger",
|
||||
"Featured events": "Udvalgte begivenheder",
|
||||
"Federation": "Federation",
|
||||
"Find an address": "Find en adresse",
|
||||
"Find an instance": "Find en udbyder",
|
||||
"Followers": "Følgere",
|
||||
"Followings": "Følger",
|
||||
"For instance: London, Taekwondo, Architecture…": "For eksempel: London, Taekwondo, Arkitektur…",
|
||||
"Forgot your password ?": "Glemt dit kodeord?",
|
||||
"From the {startDate} at {startTime} to the {endDate}": "Fra d. {startDate} kl. {startTime} til d. {endDate}",
|
||||
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "Fra d. {startDate} kl. {startTime} til d. {endDate} kl. {endTime}",
|
||||
"From the {startDate} to the {endDate}": "Fra d. {startDate} til d. {endDate}",
|
||||
"Gather ⋅ Organize ⋅ Mobilize": "Samles ⋅ Organiser ⋅ Mobiliser",
|
||||
"General": "Generelt",
|
||||
"General information": "Generel information",
|
||||
"Getting location": "Henter placering",
|
||||
"Go": "Gå",
|
||||
"Group name": "Gruppenavn",
|
||||
"Group {displayName} created": "Gruppen {displayName} er oprettet",
|
||||
"Groups": "Grupper",
|
||||
"Headline picture": "Hovedbillede",
|
||||
"Hide replies": "Skjul svar",
|
||||
"I create an identity": "Jeg skaber en identitet",
|
||||
"I don't have a Mobilizon account": "Jeg har ikke en Mobilizon konto",
|
||||
"I have a Mobilizon account": "Jeg har en Mobilizon konto",
|
||||
"I have an account on another Mobilizon instance.": "Jeg har en konto på en anden Mobilizon udbyder.",
|
||||
"I participate": "Jeg deltager",
|
||||
"I want to allow people to participate without an account.": "Jeg vil lade personer uden en konto deltage.",
|
||||
"I want to approve every participation request": "Jeg vil godkende for alle deltagelsesanmodninger",
|
||||
"Identity {displayName} created": "Identiteten {displayName} er skabt",
|
||||
"Identity {displayName} deleted": "Identiteten {displayName} er slettet",
|
||||
"Identity {displayName} updated": "Identiteten {displayName} er opdateret",
|
||||
"If an account with this email exists, we just sent another confirmation email to {email}": "Hvis en konto med denne emailadresse findes, har vi lige sendt en bekræftelsesmail til {email}",
|
||||
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "Hvis denne identitet er den eneste administrator af nogle grupper, skal du slette grupperne før du kan slette identiteten.",
|
||||
"If you want, you may send a message to the event organizer here.": "Hvis du vil kan du sende en besked til begivenhedens arrangør her.",
|
||||
"Instance Name": "Udbyderens navn",
|
||||
"Instance Terms": "Udbyderens brugsvilkår",
|
||||
"Instance Terms Source": "Kilde til udbyderens vilkår",
|
||||
"Instance Terms URL": "Adresse til udbyderens vilkår",
|
||||
"Instance settings": "Indstillinger for udbyderen",
|
||||
"Instances": "Udbydere",
|
||||
"Join <b>{instance}</b>, a Mobilizon instance": "Bliv medlem af <b>{instance}</b>, en Mobilizon udbyder",
|
||||
"Last published event": "Nyeste begivenhed",
|
||||
"Last week": "Sidste uge",
|
||||
"Learn more": "Lær mere",
|
||||
"Learn more about Mobilizon": "Lær mere om Mobilizon",
|
||||
"Leave event": "Forlad begivenhed",
|
||||
"Leaving event \"{title}\"": "Forlader begivenheden \"{title}\"",
|
||||
"License": "Licens",
|
||||
"Limited number of places": "Begrænset antal pladser",
|
||||
"Load more": "Indlæs flere",
|
||||
"Locality": "Sted",
|
||||
"Log in": "Log ind",
|
||||
"Log out": "Log ud",
|
||||
"Login": "Log ind",
|
||||
"Login on Mobilizon!": "Log ind på Mobilizon!",
|
||||
"Login on {instance}": "Log ind på {instance}",
|
||||
"Manage participations": "Håndter deltagelser",
|
||||
"Mark as resolved": "Marker som løst",
|
||||
"Members": "Medlemmer",
|
||||
"Message": "Besked",
|
||||
"Mobilizon is a federated network. You can interact with this event from a different server.": "Mobilizon er et føderalt netværk. Du kan interagere med denne begivenhed fra andre udbydere.",
|
||||
"Moderated comments (shown after approval)": "Modererede kommentarer (vist efter godkendelse)",
|
||||
"Moderation": "Moderering",
|
||||
"Moderation log": "Moderationslog",
|
||||
"My account": "Min konto",
|
||||
"My events": "Mine begivenheder",
|
||||
"My identities": "Mine identiteter",
|
||||
"Name": "Navn",
|
||||
"New email": "Ny email",
|
||||
"New note": "Nyt notat",
|
||||
"New password": "Nyt kodeord",
|
||||
"New profile": "Ny profil",
|
||||
"Next page": "Næste side",
|
||||
"No address defined": "Ingen adresse givet",
|
||||
"No closed reports yet": "Ingen behandlede indmeldinger endnu",
|
||||
"No comment": "Ingen kommentar",
|
||||
"No comments yet": "Ingen kommentarer endnu",
|
||||
"No end date": "Ingen slutdato",
|
||||
"No events found": "Ingen begivenheder fundet",
|
||||
"No group found": "Ingen gruppe fundet",
|
||||
"No groups found": "Ingen grupper fundet",
|
||||
"No instance follows your instance yet.": "Ingen udbydere følger din udbyder endnu.",
|
||||
"No instance to approve|Approve instance|Approve {number} instances": "Ingen udbydere at godkende|Godkend udbyder|Godkend {number} udbydere",
|
||||
"No instance to reject|Reject instance|Reject {number} instances": "Ingen udbydere at afvise|Afvis udbyder|Afvis {number} udbydere",
|
||||
"No instance to remove|Remove instance|Remove {number} instances": "Ingen udbydere at fjerne|Fjern udbyder|Fjern {number} udbydere",
|
||||
"No message": "Ingen besked",
|
||||
"No open reports yet": "Ingen åbne indmeldinger endnu",
|
||||
"No participant to approve|Approve participant|Approve {number} participants": "Ingen deltagere at godkende|Godkend deltager|Godkend {number} deltagere",
|
||||
"No participant to reject|Reject participant|Reject {number} participants": "Ingen deltagere at afvise|Afvis deltagere|Afvis {number} deltagere",
|
||||
"No resolved reports yet": "Ingen løste indmeldinger endnu",
|
||||
"No results for \"{queryText}\"": "Ingen resultater for \"{queryText}\"",
|
||||
"Notes": "Notater",
|
||||
"Number of places": "Antal steder",
|
||||
"OK": "OK",
|
||||
"Old password": "Gammelt kodeord",
|
||||
"Please do not use it in any real way.": "Brug det venligst ikke som andet end en prøve."
|
||||
});
|
||||
1501
res/locale/de.js
Normal file
1501
res/locale/de.js
Normal file
File diff suppressed because it is too large
Load diff
4
res/locale/dsb.js
Normal file
4
res/locale/dsb.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
CTX.setMessages({
|
||||
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Jaden pśezźojny, emancipaciski a etiski narzadow za zběrać, organizować a mobilizować.",
|
||||
"Please do not use it in any real way.": "Prošu južo w žadnym realnym spěchowanju nježiwajće."
|
||||
});
|
||||
1661
res/locale/en_US.js
Normal file
1661
res/locale/en_US.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/eo.js
Normal file
1257
res/locale/eo.js
Normal file
File diff suppressed because it is too large
Load diff
1440
res/locale/es.js
Normal file
1440
res/locale/es.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/eu.js
Normal file
1257
res/locale/eu.js
Normal file
File diff suppressed because it is too large
Load diff
1265
res/locale/fa.js
Normal file
1265
res/locale/fa.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/fi.js
Normal file
1257
res/locale/fi.js
Normal file
File diff suppressed because it is too large
Load diff
1655
res/locale/fr_FR.js
Normal file
1655
res/locale/fr_FR.js
Normal file
File diff suppressed because it is too large
Load diff
1340
res/locale/gd.js
Normal file
1340
res/locale/gd.js
Normal file
File diff suppressed because it is too large
Load diff
1660
res/locale/gl.js
Normal file
1660
res/locale/gl.js
Normal file
File diff suppressed because it is too large
Load diff
380
res/locale/he.js
Normal file
380
res/locale/he.js
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
CTX.setMessages({
|
||||
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "כלי ידידותי למשתמש.ת, חופשי ואתי להתאספות, להתארגנות ולגיוס לפעולה משותפת.",
|
||||
"A validation email was sent to {email}": "הודעת אימות נשלחה בדואר אלקטרוני לכתובת {email}",
|
||||
"Abandon editing": "עזיבת עריכה",
|
||||
"About": "אודות",
|
||||
"About Mobilizon": "אודות מוביליזון",
|
||||
"About this event": "אודות האירוע",
|
||||
"About this instance": "אודות האתר",
|
||||
"Accepted": "התקבל",
|
||||
"Account": "חשבון",
|
||||
"Add": "הוספה",
|
||||
"Add a note": "הוספת הערה",
|
||||
"Add an address": "הוספת כתובת",
|
||||
"Add an instance": "הוספת אתר",
|
||||
"Add some tags": "הוספת תגיות",
|
||||
"Add to my calendar": "הוספה ללוח השנה שלי",
|
||||
"Additional comments": "הערות נוספות",
|
||||
"Admin": "מנהל.ת",
|
||||
"Admin settings successfully saved.": "הגדרות מנהל.ת נשמרו בהצלחה.",
|
||||
"Administration": "ניהול",
|
||||
"All the places have already been taken": "כל המקומות כבר תפוסים",
|
||||
"Allow registrations": "אפשור הרשמה",
|
||||
"Anonymous participant": "משתתפ.ת בלתי מזוהה",
|
||||
"Anonymous participants will be asked to confirm their participation through e-mail.": "משתתפים.ות בלתי מזוהים.ות יתבקשו לאשר השתתפות באמצעות דואר אלקטרוני.",
|
||||
"Anonymous participations": "השתתפות בלתי מזוהה",
|
||||
"Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "האם את.ה בטוח.ה שאת.ה רוצה למחוק לגמרי את החשבון שלך? תאבד.י הכל. זהויות, הגדרות, אירועים שנוצרו, הודעות והשתתפויות יימחקו לצמיתות.",
|
||||
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "האם את.ה בטוח.ה שאת.ה רוצה <b>למחוק</b> את התגובה? לא ניתן יהיה לבטל את הפעולה.",
|
||||
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the discussion with the event creator or edit its event instead.": "האם את.ה בטוח.ה שאת.ה רוצה <b>למחוק</b> את האירוע? לא ניתן יהיה לבטל את הפעולה. ייתכן שתרצ.י ליצור קשר עם יוצר.ת האירוע, או לערוך את האירוע, במקום למחוק.",
|
||||
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "האם את.ה בטוח.ה שאת.ה רוצה לבטל את יצירת האירוע? כל השינויים יאבדו.",
|
||||
"Are you sure you want to cancel the event edition? You'll lose all modifications.": "האם את.ה בטוח.ה שאת.ה רוצה לבטל את עריכת האירוע? כל השינויים יאבדו.",
|
||||
"Are you sure you want to cancel your participation at event \"{title}\"?": "האם את.ה בטוח.ה שאת.ה רוצה לבטל את השתתפותך באירוע \"{title}\"?",
|
||||
"Are you sure you want to delete this event? This action cannot be reverted.": "האם את.ה בטוח.ה שאת.ה רוצה למחוק את האירוע? לא ניתן יהיה לבטל את הפעולה.",
|
||||
"Avatar": "תמונה",
|
||||
"Back to previous page": "חזרה לדף הקודם",
|
||||
"Before you can login, you need to click on the link inside it to validate your account.": "לפני שתוכל.י להתחבר, עלייך ללחוץ על הקישור שבתוך ההודעה כדי לאשר את החשבון שלך.",
|
||||
"By {username}": "נכתב על־ידי {username}",
|
||||
"Cancel": "ביטול",
|
||||
"Cancel anonymous participation": "ביטול השתתפות בלתי מזוהה",
|
||||
"Cancel creation": "ביטול יצירה",
|
||||
"Cancel edition": "ביטול עריכה",
|
||||
"Cancel my participation request…": "ביטול בקשת ההשתתפות שלי…",
|
||||
"Cancel my participation…": "ביטול ההשתתפות שלי…",
|
||||
"Cancelled: Won't happen": "מבוטל: לא יתקיים",
|
||||
"Change": "שינוי",
|
||||
"Change my email": "שינוי כתובת דואר אלקטרוני",
|
||||
"Change my identity…": "שינוי הזהות שלי…",
|
||||
"Change my password": "שינוי ססמה",
|
||||
"Clear": "ניקוי",
|
||||
"Click to upload": "לחצ.י להעלאה",
|
||||
"Close": "סגירה",
|
||||
"Close comments for all (except for admins)": "ביטול תגובות לכולןם (מלבד מנהלים.ות)",
|
||||
"Closed": "סגור",
|
||||
"Comment deleted": "התגובה נמחקה",
|
||||
"Comment from {'@'}{username} reported": "התגובה של {'@'}{username} דווחה",
|
||||
"Comments": "תגובות",
|
||||
"Confirm my participation": "אישור ההשתתפות שלי",
|
||||
"Confirm my particpation": "אישור ההשתתפות שלי",
|
||||
"Confirmed: Will happen": "מאושר: יתקיים",
|
||||
"Continue editing": "המשך עריכה",
|
||||
"Country": "מדינה",
|
||||
"Create": "יצירה",
|
||||
"Create a new event": "יצירת אירוע חדש",
|
||||
"Create a new group": "יצירת קבוצה חדשה",
|
||||
"Create a new identity": "יצירת זהות חדשה",
|
||||
"Create group": "יצירת קבוצה",
|
||||
"Create my event": "יצירת אירוע",
|
||||
"Create my group": "יצירת קבוצה",
|
||||
"Create my profile": "יצירת הפרופיל שלי",
|
||||
"Create token": "יצירת אסימון",
|
||||
"Current identity has been changed to {identityName} in order to manage this event.": "הזהות הנוכחית שונתה ל־{identityName} כדי לנהל אירוע זה.",
|
||||
"Current page": "הדף הנוכחי",
|
||||
"Custom": "התאמה אישית",
|
||||
"Custom URL": "קישור מותאם אישית",
|
||||
"Custom text": "טקסט מותאם אישית",
|
||||
"Dashboard": "לוח בקרה",
|
||||
"Date": "תאריך",
|
||||
"Date and time settings": "הגדרות זמן ותאריך",
|
||||
"Date parameters": "מאפייני תאריך",
|
||||
"Default": "ברירת מחדל",
|
||||
"Delete": "מחיקה",
|
||||
"Delete account": "מחיקת חשבון",
|
||||
"Delete event": "מחיקת אירוע",
|
||||
"Delete everything": "מחיקת הכל",
|
||||
"Delete my account": "מחיקת החשבון שלי",
|
||||
"Delete this identity": "מחיקת זהות",
|
||||
"Delete your identity": "מחיקת הזהות שלך",
|
||||
"Delete {eventTitle}": "מחיקת {eventTitle}",
|
||||
"Delete {preferredUsername}": "מחיקת {preferredUsername}",
|
||||
"Deleting comment": "מוחק תגובה",
|
||||
"Deleting event": "מוחק אירוע",
|
||||
"Deleting my account will delete all of my identities.": "מחיקת החשבון שלי תמחק את כל הזהויות שלי.",
|
||||
"Deleting your Mobilizon account": "מחיקת החשבון שלך במוביליזון",
|
||||
"Description": "תיאור",
|
||||
"Display name": "שם תצוגה",
|
||||
"Display participation price": "הצגת מחיר השתתפות",
|
||||
"Draft": "טיוטה",
|
||||
"Drafts": "טיוטות",
|
||||
"Edit": "עריכה",
|
||||
"Eg: Stockholm, Dance, Chess…": "למשל: תל אביב, ריקוד, שחמט…",
|
||||
"Either on the {instance} instance or on another instance.": "או באתר {instance} או באתר אחר.",
|
||||
"Either the account is already validated, either the validation token is incorrect.": "החשבון כבר מאומת, או שקוד האימות שגוי.",
|
||||
"Either the email has already been changed, either the validation token is incorrect.": "או שכתובת הדואר האלקטרוני כבר שונתה, או שקוד האימות שגוי.",
|
||||
"Either the participation request has already been validated, either the validation token is incorrect.": "או שבקשת ההשתתפות כבר אומתה, או שקוד האימות שגוי.",
|
||||
"Email": "דואר אלקטרוני",
|
||||
"Ends on…": "נמשך עד…",
|
||||
"Enter the link URL": "הזינ.י את הקישור",
|
||||
"Error while changing email": "שגיאה בעת שינוי כתובת דואר אלקטרוני",
|
||||
"Error while validating account": "שגיאה בעת אימות חשבון",
|
||||
"Error while validating participation request": "שגיאה בעת אימות בקשת השתתפות",
|
||||
"Event": "אירוע",
|
||||
"Event already passed": "האירוע כבר חלף",
|
||||
"Event cancelled": "האירוע בוטל",
|
||||
"Event creation": "יצירת אירוע",
|
||||
"Event edition": "עריכת אירוע",
|
||||
"Event list": "רשימת אירועים",
|
||||
"Event page settings": "הגדרות עמוד אירוע",
|
||||
"Event to be confirmed": "אירוע לאישור",
|
||||
"Event {eventTitle} deleted": "האירוע {eventTitle} נמחק",
|
||||
"Event {eventTitle} reported": "האירוע {eventTitle} דווח",
|
||||
"Events": "אירועים",
|
||||
"Ex: mobilizon.fr": "למשל: mobilizon.fr",
|
||||
"Explore": "סיור",
|
||||
"Failed to save admin settings": "שמירת הגדרות ניהול נכשלה",
|
||||
"Featured events": "אירועים מומלצים",
|
||||
"Federation": "ביזור",
|
||||
"Find an address": "מציאת כתובת",
|
||||
"Find an instance": "מציאת אתר",
|
||||
"Followers": "עוקבים.ות",
|
||||
"Followings": "עוקב.ת אחרי",
|
||||
"For instance: London, Taekwondo, Architecture…": "לדוגמה: תל אביב, אייקידו, ארכיטקטורה…",
|
||||
"Forgot your password ?": "שכחת ססמה?",
|
||||
"From the {startDate} at {startTime} to the {endDate}": "מתאריך {startDate} בשעה {startTime} עד התאריך {endDate}",
|
||||
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "מתאריך {startDate} בשעה {startTime} עד התאריך {endDate} בשעה {endTime}",
|
||||
"From the {startDate} to the {endDate}": "מהתאריך {startDate} עד התאריך {endDate}",
|
||||
"Gather ⋅ Organize ⋅ Mobilize": "להתאסף ⋅ להתארגן ⋅ לפעול",
|
||||
"General": "כללי",
|
||||
"General information": "מידע כללי",
|
||||
"Getting location": "מחפש מיקום",
|
||||
"Go": "קדימה",
|
||||
"Group name": "שם הקבוצה",
|
||||
"Group {displayName} created": "הקבוצה {displayName} נוצרה",
|
||||
"Groups": "קבוצות",
|
||||
"Headline picture": "תמונת כותרת",
|
||||
"Hide replies": "הסתרת תגובות",
|
||||
"I create an identity": "אני יוצר.ת זהות",
|
||||
"I don't have a Mobilizon account": "אין לי חשבון מוביליזון",
|
||||
"I have a Mobilizon account": "יש לי חשבון מוביליזון",
|
||||
"I have an account on another Mobilizon instance.": "יש לי חשבון באתר מוביליזון אחר.",
|
||||
"I participate": "אני משתתפ.ת",
|
||||
"I want to allow people to participate without an account.": "אני רוצה לאפשר לא.נשים להשתתף ללא חשבון.",
|
||||
"I want to approve every participation request": "אני רוצה לאשר כל בקשת השתתפות",
|
||||
"Identity {displayName} created": "הזהות {displayName} נוצרה",
|
||||
"Identity {displayName} deleted": "הזהות {displayName} נמחקה",
|
||||
"Identity {displayName} updated": "הזהות {displayName} עודכנה",
|
||||
"If an account with this email exists, we just sent another confirmation email to {email}": "אם קיים חשבון עם כתובת הדואר האלקטרוני הזו, שלחנו עוד הודעת אימות לכתובת {email}",
|
||||
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "אם הזהות הזו היא המנהלת היחידה של קבוצות כלשהן, עלייך למחוק אותן לפני שתוכלי למחוק את הזהות הזו.",
|
||||
"If you want, you may send a message to the event organizer here.": "אם את.ה רוצה, באפשרותך לשלוח כאן הודעה למארגנ.ת האירוע.",
|
||||
"Instance Name": "שם האתר",
|
||||
"Instance Terms": "תנאי השימוש של האתר",
|
||||
"Instance Terms Source": "תנאי השימוש של האתר",
|
||||
"Instance Terms URL": "קישור לתנאי השימוש של האתר",
|
||||
"Instance settings": "הגדרות האתר",
|
||||
"Instances": "אתרים",
|
||||
"Join <b>{instance}</b>, a Mobilizon instance": "הצטרפ.י ל־<b>{instance}</b>, אתר מוביליזון",
|
||||
"Last published event": "האירוע האחרון שפורסם",
|
||||
"Last week": "בשבוע שעבר",
|
||||
"Learn more": "למדו עוד",
|
||||
"Learn more about Mobilizon": "למדו עוד אודות מוביליזון",
|
||||
"Leave event": "עזיבת האירוע",
|
||||
"Leaving event \"{title}\"": "עוזב את האירוע \"{title}\"",
|
||||
"License": "רישיון",
|
||||
"Limited number of places": "מספר מוגבל של מקומות",
|
||||
"Load more": "לטעון עוד",
|
||||
"Locality": "מיקום",
|
||||
"Log in": "כניסה",
|
||||
"Log out": "יציאה",
|
||||
"Login": "כניסה",
|
||||
"Login on Mobilizon!": "כניסה למוביליזון!",
|
||||
"Login on {instance}": "כניסה לאתר {instance}",
|
||||
"Manage participations": "ניהול השתתפויות",
|
||||
"Mark as resolved": "סימון כנפתר",
|
||||
"Members": "חברים.ות",
|
||||
"Message": "הודעה",
|
||||
"Mobilizon is a federated network. You can interact with this event from a different server.": "מוביליזון היא רשת מבוזרת. ניתן לבצע פעולות בקשר לאירוע זה משרת אחר.",
|
||||
"Moderated comments (shown after approval)": "תגובות שממתינות לאישור",
|
||||
"Moderation": "אישור תגובות",
|
||||
"Moderation log": "היסטוריית אישור תגובות",
|
||||
"My account": "החשבון שלי",
|
||||
"My events": "האירועים שלי",
|
||||
"My identities": "הזהויות שלי",
|
||||
"Name": "שם",
|
||||
"New email": "הודעת דוא\"ל חדשה",
|
||||
"New note": "הערה חדשה",
|
||||
"New password": "ססמה חדשה",
|
||||
"New profile": "פרופיל חדש",
|
||||
"Next page": "הדף הבא",
|
||||
"No address defined": "לא הוגדרה כתובת",
|
||||
"No closed reports yet": "אין דיווחים סגורים עדיין",
|
||||
"No comment": "אין תגובות",
|
||||
"No comments yet": "אין תגובות עדיין",
|
||||
"No end date": "אין תאריך סיום",
|
||||
"No events found": "לא נמצאו אירועים",
|
||||
"No group found": "לא נמצאה קבוצה",
|
||||
"No groups found": "לא נמצאו קבוצות",
|
||||
"No instance follows your instance yet.": "עדיין אין אתר שעוקב אחרי האתר שלך.",
|
||||
"No instance to approve|Approve instance|Approve {number} instances": "אין אתרים לאשר|אישור האתר|אישור של {number} אתרים",
|
||||
"No instance to reject|Reject instance|Reject {number} instances": "אין אתר לדחות|דחיית האתר|דחיית {number} אתרים",
|
||||
"No instance to remove|Remove instance|Remove {number} instances": "אין אתרים להסיר|הסרת האתר|הסרת {number} אתרים",
|
||||
"No message": "אין הודעות",
|
||||
"No open reports yet": "אין דיווחים פתוחים עדיין",
|
||||
"No participant to approve|Approve participant|Approve {number} participants": "אין משתתפים.ות לאשר|אישור המשתתפ.ת|אישור של {number} משתתפים.ות",
|
||||
"No participant to reject|Reject participant|Reject {number} participants": "אין משתתפים.ות לדחות|דחיית המשתתפ.ת|דחיית {number} משתתפים.ות",
|
||||
"No resolved reports yet": "אין דיווחים פתורים עדיין",
|
||||
"No results for \"{queryText}\"": "אין תוצאות עבור \"{queryText}\"",
|
||||
"Notes": "הערות",
|
||||
"Number of places": "מספר מקומות",
|
||||
"OK": "אישור",
|
||||
"Old password": "ססמה ישנה",
|
||||
"On {date}": "בתאריך {date}",
|
||||
"On {date} ending at {endTime}": "בתאריך {date} מסתיים בשעה {endTime}",
|
||||
"On {date} from {startTime} to {endTime}": "בתאריך {date} מהשעה {startTime} עד השעה {endTime}",
|
||||
"On {date} starting at {startTime}": "בתאריך {date} החל מהשעה {startTime}",
|
||||
"Only accessible through link (private)": "נגיש רק דרך קישור (פרטי)",
|
||||
"Only alphanumeric lowercased characters and underscores are supported.": "רק אותיות קטנות באנגלית, ספרות וקו תחתי.",
|
||||
"Open": "פתיחה",
|
||||
"Opened reports": "דיווחים פתוחים",
|
||||
"Or": "או",
|
||||
"Organized": "מאורגן",
|
||||
"Organized by {name}": "מאורגן על־ידי {name}",
|
||||
"Organizer": "מארגנ.ת",
|
||||
"Other software may also support this.": "תוכנות אחרות עשויות גם הן לתמוך בזה.",
|
||||
"Otherwise this identity will just be removed from the group administrators.": "אחרת, זהות זו פשוט תוסר מניהול הקבוצה.",
|
||||
"Page": "דף",
|
||||
"Page limited to my group (asks for auth)": "הדף מוגבל לקבוצה שלי (מבקש להזדהות)",
|
||||
"Participant": "משתתפ.ת",
|
||||
"Participants": "משתתפים.ות",
|
||||
"Participate": "השתתפות",
|
||||
"Participate using your email address": "השתתפות בעזרת כתובת הדוא\"ל שלך",
|
||||
"Participation approval": "אישור השתתפות",
|
||||
"Participation confirmation": "אישור השתתפות",
|
||||
"Participation requested!": "נשלחה בקשת השתתפות!",
|
||||
"Password": "ססמה",
|
||||
"Password (confirmation)": "ססמה (וידוא)",
|
||||
"Password reset": "איפוס ססמה",
|
||||
"Past events": "אירועים שחלפו",
|
||||
"Pending": "ממתין",
|
||||
"Pick an identity": "בחירת זהות",
|
||||
"Please check your spam folder if you didn't receive the email.": "אנא בדק.י את תיקיית הספאם שלך אם לא קיבלת את הודעת הדוא\"ל.",
|
||||
"Please contact this instance's Mobilizon admin if you think this is a mistake.": "אנא צר.י קשר עם מנהל.ת האתר אם את.ה חושב.ת שזו טעות.",
|
||||
"Please enter your password to confirm this action.": "אנא הזינ.י את הסממה שלך כדי לאשר את הפעולה.",
|
||||
"Please make sure the address is correct and that the page hasn't been moved.": "אנא ודא.י שהכתובת נכונה ושהעמוד לא הוזז.",
|
||||
"Post a comment": "פרסום תגובה",
|
||||
"Post a reply": "פרסום תגובה",
|
||||
"Postal Code": "מיקוד",
|
||||
"Preferences": "העדפות",
|
||||
"Previous page": "הדף הקודם",
|
||||
"Privacy Policy": "מדיניות פרטיות",
|
||||
"Private event": "אירוע פרטי",
|
||||
"Private feeds": "היזנים פרטיים",
|
||||
"Profiles": "פרופילים",
|
||||
"Public RSS/Atom Feed": "ערוץ RSS/Atom פומבי",
|
||||
"Public comment moderation": "בקרת תגובות פומביות",
|
||||
"Public event": "אירוע פומבי",
|
||||
"Public feeds": "היזנים פומביים",
|
||||
"Public iCal Feed": "ערוץ iCal פומבי",
|
||||
"Publish": "פרסום",
|
||||
"Published events with <b>{comments}</b> comments and <b>{participations}</b> confirmed participations": "אירועים שפורסמו שיש להם <b>{comments}</b> תגובות ו־<b>{participations}</b> אישורי השתתפות",
|
||||
"RSS/Atom Feed": "היזן רסס/אטום",
|
||||
"Region": "אזור",
|
||||
"Registration is allowed, anyone can register.": "ההרשמה מאופשרת, כל אחד.ת יכול.ה להירשם.",
|
||||
"Registration is closed.": "ההרשמה סגורה.",
|
||||
"Registration is currently closed.": "ההרשמה סגורה כעת.",
|
||||
"Rejected": "נדחה",
|
||||
"Reopen": "פתיחה מחדש",
|
||||
"Reply": "תגובה",
|
||||
"Report": "דיווח",
|
||||
"Report this comment": "דיווח על תגובה זו",
|
||||
"Report this event": "דיווח על אירוע זה",
|
||||
"Reported": "מדווח",
|
||||
"Reported by": "דווח על־ידי",
|
||||
"Reported by someone on {domain}": "דווח על־ידי מישהו.י מהאתר {domain}",
|
||||
"Reported by {reporter}": "דווח על־ידי {reporter}",
|
||||
"Reported identity": "זהות מדווחת",
|
||||
"Reports": "דיווחים",
|
||||
"Reset my password": "איפוס הססמה שלי",
|
||||
"Resolved": "נפתר",
|
||||
"Resource provided is not an URL": "המשאב שהוזן אינו קישור תקין",
|
||||
"Role": "תפקיד",
|
||||
"Save": "שמירה",
|
||||
"Save draft": "שמירת טיוטה",
|
||||
"Search": "חיפוש",
|
||||
"Search events, groups, etc.": "חיפוש אירועים, קבוצות וכו'",
|
||||
"Searching…": "מחפש…",
|
||||
"Send email": "שליחת דוא\"ל",
|
||||
"Send the report": "שליחת הדיווח",
|
||||
"Set an URL to a page with your own terms.": "הגדרת קישור לעמוד עם תנאים משלך.",
|
||||
"Settings": "הגדרות",
|
||||
"Share this event": "שיתוף אירוע זה",
|
||||
"Show map": "הצגת מפה",
|
||||
"Show remaining number of places": "הצגת מספר המקומות שנותרו",
|
||||
"Show the time when the event begins": "הצגת זמן תחילת האירוע",
|
||||
"Show the time when the event ends": "הצגת זמן סיום האירוע",
|
||||
"Sign up": "הרשמה",
|
||||
"Starts on…": "מתחיל ב…",
|
||||
"Status": "מצב",
|
||||
"Street": "רחוב",
|
||||
"Tentative: Will be confirmed later": "לא סופי: יאושר בהמשך",
|
||||
"Terms": "תנאים",
|
||||
"The account's email address was changed. Check your emails to verify it.": "כתובת הדוא\"ל של החשבון שונתה. בדק.י את תיבת הדוא\"ל שלך כדי לאמת את השינוי.",
|
||||
"The actual number of participants may differ, as this event is hosted on another instance.": "מספר המשתתפים.ות האמיתי עשוי להיות שונה, כי האירוע מתפרסם דרך אתר מוביליזון אחר.",
|
||||
"The content came from another server. Transfer an anonymous copy of the report?": "התוכן הגיע משרת אחר. האם להעביר עותק אנונימי של הדיווח?",
|
||||
"The draft event has been updated": "טיוטת האירוע עודכנה",
|
||||
"The event has been created as a draft": "האירוע נוצר כטיוטה",
|
||||
"The event has been published": "האירוע פורסם",
|
||||
"The event has been updated": "האירוע עודכן",
|
||||
"The event has been updated and published": "האירוע עודכן ופורסם",
|
||||
"The event organiser has chosen to validate manually participations. Do you want to add a little note to explain why you want to participate to this event?": "בקשות השתתפות מאושרות ידנית על־ידי מארגנ.ת האירוע. האם ברצונך להוסיף הערה עם הסבר מדוע את.ה רוצה להשתתף באירוע זה?",
|
||||
"The event organizer didn't add any description.": "מארגנ.ת האירוע לא הוסיפ.ה תיאור.",
|
||||
"The event organizer manually approves participations. Since you've chosen to participate without an account, please explain why you want to participate to this event.": "בקשות השתתפות מאושרות ידנית על־ידי מארגנ.ת האירוע. היות ובחרת להשתתף ללא חשבון, אנא צרפ.י הסבר מדוע את.ה רוצה להשתתף באירוע זה.",
|
||||
"The event title will be ellipsed.": "כותרת האירוע תוצג באופן מקוצר עם שלוש נקודות בסוף.",
|
||||
"The page you're looking for doesn't exist.": "העמוד שחיפשת לא קיים.",
|
||||
"The password was successfully changed": "הססמה שונתה בהצלחה",
|
||||
"The report will be sent to the moderators of your instance. You can explain why you report this content below.": "הדיווח יישלח למנהלים.ות של השרת שלך. ניתן להסביר למטה את סיבת הדיווח של תוכן זה.",
|
||||
"The {default_terms} will be used. They will be translated in the user's language.": "ייעשה שימוש ב{default_terms}. הם יתורגמו לשפה של המשתמש.ת.",
|
||||
"There are {participants} participants.": "יש {participants} משתתפים.ות.",
|
||||
"There will be no way to recover your data.": "לא תהיה אפשרות לשחזר את המידע שלך.",
|
||||
"These events may interest you": "האירועים האלה עשויים לעניין אותך",
|
||||
"This Mobilizon instance and this event organizer allows anonymous participations, but requires validation through email confirmation.": "שרת מוביליזון זה ואירוע זה מאפשרים בקשת השתתפות ללא חשבון, אך נדרש אימות באמצעות דואר אלקטרוני.",
|
||||
"This information is saved only on your computer. Click for details": "מידע זה נשמר רק על המחשב שלך. לפרטים לחצ.י",
|
||||
"This instance isn't opened to registrations, but you can register on other instances.": "שרת זה אינו פתוח להרשמה, אך ניתן להירשם בשרתים אחרים.",
|
||||
"This is a demonstration site to test Mobilizon.": "זהו אתר הדגמה לצורך בדיקה של מוביליזון.",
|
||||
"This will delete / anonymize all content (events, comments, messages, participations…) created from this identity.": "הדבר ימחק / יהפוך לאנונימי את כל התוכן (אירועים, תגובות, הודעות, אישורי השתתפות…) שנוצר מתוך זהות זו.",
|
||||
"Title": "כותרת",
|
||||
"To confirm, type your event title \"{eventTitle}\"": "לאישור, נא להזין את כותרת האירוע \"{eventTitle}\"",
|
||||
"To confirm, type your identity username \"{preferredUsername}\"": "לאישור, נא להזין את שם המשתמש.ת שלך \"{preferredUsername}\"",
|
||||
"Transfer to {outsideDomain}": "העברה לשרת {outsideDomain}",
|
||||
"Type": "סוג",
|
||||
"URL": "קישור",
|
||||
"Unfortunately, your participation request was rejected by the organizers.": "למרבה הצער, בקשת ההשתתפות שלך נדחתה על־ידי המארגניםות.",
|
||||
"Unknown": "לא ידוע",
|
||||
"Unknown error.": "שגיאה לא ידועה.",
|
||||
"Unsaved changes": "שינויים שלא נשמרו",
|
||||
"Upcoming": "אירועים קרובים",
|
||||
"Update event {name}": "עדכון האירוע {name}",
|
||||
"Update my event": "עדכון האירוע שלי",
|
||||
"Updated": "עודכן",
|
||||
"Use my location": "המיקום שלי",
|
||||
"Username": "שם משתמש.ת",
|
||||
"Users": "משתמשיםות",
|
||||
"View a reply": "ללא הצגת תגובות|הצגת תגובה אחת|הצגת {totalReplies} תגובות",
|
||||
"View event page": "הצגת עמוד האירוע",
|
||||
"View everything": "הצגת הכל",
|
||||
"View page on {hostname} (in a new window)": "הצגת העמוד ב־{hostname} (בחלון חדש)",
|
||||
"Visible everywhere on the web (public)": "זמין לצפייה מכל מקום ברשת (ציבורי)",
|
||||
"Waiting for organization team approval.": "בהמתנה לאישור מצוות המארגניםות.",
|
||||
"Warning": "אזהרה",
|
||||
"We just sent an email to {email}": "הרגע שלחנו דוא\"ל לכתובת {email}",
|
||||
"We will redirect you to your instance in order to interact with this event": "אנחנו נעביר אותך לשרת שלך כדי לתקשר עם אירוע זה",
|
||||
"Website / URL": "כתובת אתר / URL",
|
||||
"Welcome back {username}!": "ברוכ.ה הבא.ה, {username}!",
|
||||
"Welcome back!": "ברוכ.ה הבא.ה!",
|
||||
"Welcome to Mobilizon, {username}!": "ברוכ.ה הבא.ה למוביליזון, {username}!",
|
||||
"Who can view this event and participate": "מי יכול.ה לצפות באירוע ולהשתתף בו",
|
||||
"You are participating in this event anonymously": "את.ה משתתפ.ת באירוע זה באופן אנונימי",
|
||||
"You are participating in this event anonymously but didn't confirm participation": "את.ה משתתפ.ת באירוע זה באופן אנונימי אך טרם אישרת את השתתפותך",
|
||||
"You can add tags by hitting the Enter key or by adding a comma": "ניתן להוסיף תגיות על־ידי לחיצה על מקש Enter או על־ידי הוספת פסיק",
|
||||
"You can try another search term or drag and drop the marker on the map": "ניתן לנסות חיפוש נוסף או לגרור את הסמן שעל המפה",
|
||||
"You have cancelled your participation": "ביטלת את השתתפותך",
|
||||
"You have one event in {days} days.": "אין לך אירועים ב־{days} days | יש לך אירוע אחד ב־{days} ימים. | יש לך {count} אירועים ב־{days} ימים",
|
||||
"You have one event today.": "אין לך אירועים היום | יש לך אירוע אחד היום | יש לך {count} אירועים היום",
|
||||
"You have one event tomorrow.": "אין לך אירועים מחר | יש לך אירוע אחד מחר | יש לך {count} אירועים מחר",
|
||||
"You need to login.": "יש להתחבר.",
|
||||
"You will be redirected to the original instance": "את.ה תועבר.י לאתר המקורי",
|
||||
"You wish to participate to the following event": "את.ה מבקש.ת להשתתף באירוע הבא",
|
||||
"You'll receive a confirmation email.": "תקבל.י הודעת דוא\"ל לאישור.",
|
||||
"Your account has been successfully deleted": "החשבון שלך נמחק בהצלחה",
|
||||
"Your account has been validated": "החשבון שלך אומת",
|
||||
"Your account is being validated": "החשבון שלך ממתין לאימות",
|
||||
"Your account is nearly ready, {username}": "החשבון שלך כמעט מוכן, {username}"
|
||||
});
|
||||
1615
res/locale/hr.js
Normal file
1615
res/locale/hr.js
Normal file
File diff suppressed because it is too large
Load diff
1661
res/locale/hu.js
Normal file
1661
res/locale/hu.js
Normal file
File diff suppressed because it is too large
Load diff
1268
res/locale/id.js
Normal file
1268
res/locale/id.js
Normal file
File diff suppressed because it is too large
Load diff
1658
res/locale/it.js
Normal file
1658
res/locale/it.js
Normal file
File diff suppressed because it is too large
Load diff
1293
res/locale/ja.js
Normal file
1293
res/locale/ja.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/kab.js
Normal file
1257
res/locale/kab.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/kn.js
Normal file
1257
res/locale/kn.js
Normal file
File diff suppressed because it is too large
Load diff
38
res/locale/ko.js
Normal file
38
res/locale/ko.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
CTX.setMessages({
|
||||
"About": "소개",
|
||||
"About this instance": "이 인스턴스에 대하여",
|
||||
"Accepted": "수락함",
|
||||
"Account": "계정",
|
||||
"Add": "추가하기",
|
||||
"Add a note": "노트 남기기",
|
||||
"Add an address": "주소 추가하기",
|
||||
"Add an instance": "인스턴스 추가하기",
|
||||
"Add some tags": "몇 가지 태그 추가하기",
|
||||
"Add to my calendar": "내 달력에 추가하기",
|
||||
"Additional comments": "추가 코멘트",
|
||||
"Admin": "관리자",
|
||||
"Admin dashboard": "관리자 대시보드",
|
||||
"Admin settings": "관리자 설정",
|
||||
"Admin settings successfully saved.": "관리자 설정을 저장했습니다.",
|
||||
"Administration": "관리",
|
||||
"Allow registrations": "가입 허용하기",
|
||||
"Anonymous participant": "익명의 참가자",
|
||||
"Apply filters": "필터 적용",
|
||||
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "이 코멘트를 <b>삭제</b>합니까? 이 행동으 되돌릴 수 없습니다.",
|
||||
"Avatar": "아바타",
|
||||
"Back to previous page": "이전 페이지로 돌아가기",
|
||||
"Cancel": "취소",
|
||||
"Cancelled": "취소함",
|
||||
"Categories": "범주",
|
||||
"Change the filters.": "필터를 바꿉니다.",
|
||||
"Clear": "비우기",
|
||||
"Close": "닫기",
|
||||
"Confirmed": "확인함",
|
||||
"Create identity": "정체성 만들기",
|
||||
"Dashboard": "대시보드",
|
||||
"Group profiles": "그룹 프로필",
|
||||
"Mobilizon": "Mobilizon",
|
||||
"Reports list": "제보 목록",
|
||||
"Software details: {software_details}": "소프트웨어 세부사항: {software_details}",
|
||||
"Visit {instance_domain}": "{instance_domain} 인스턴스 방문하기"
|
||||
});
|
||||
1547
res/locale/nl.js
Normal file
1547
res/locale/nl.js
Normal file
File diff suppressed because it is too large
Load diff
1443
res/locale/nn.js
Normal file
1443
res/locale/nn.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/oc.js
Normal file
1257
res/locale/oc.js
Normal file
File diff suppressed because it is too large
Load diff
1647
res/locale/pl.js
Normal file
1647
res/locale/pl.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/pt.js
Normal file
1257
res/locale/pt.js
Normal file
File diff suppressed because it is too large
Load diff
1258
res/locale/pt_BR.js
Normal file
1258
res/locale/pt_BR.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/ru.js
Normal file
1257
res/locale/ru.js
Normal file
File diff suppressed because it is too large
Load diff
1257
res/locale/sl.js
Normal file
1257
res/locale/sl.js
Normal file
File diff suppressed because it is too large
Load diff
1641
res/locale/sv.js
Normal file
1641
res/locale/sv.js
Normal file
File diff suppressed because it is too large
Load diff
1
res/locale/th.js
Normal file
1
res/locale/th.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
CTX.setMessages({});
|
||||
208
res/locale/tr.js
Normal file
208
res/locale/tr.js
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
CTX.setMessages({
|
||||
"A user-friendly, emancipatory and ethical tool for gathering, organising, and mobilising.": "Toplanma, organize olma ve harekete geçme için kullanıcı dostu, özgürleştirici ve etik bir araç.",
|
||||
"A validation email was sent to {email}": "{email} adresine bir doğrulama e-postası gönderildi",
|
||||
"Abandon editing": "Düzenlemeyi bırakın",
|
||||
"About": "Hakkında",
|
||||
"About Mobilizon": "Mobilizon Hakkında",
|
||||
"About this event": "Bu etkinlik hakkında",
|
||||
"About this instance": "Bu oluşum hakkında",
|
||||
"Accepted": "Kabul Edildi",
|
||||
"Account": "Hesap",
|
||||
"Add": "Ekle",
|
||||
"Add a note": "Bir not ekleyin",
|
||||
"Add an address": "Bir adres ekleyin",
|
||||
"Add an instance": "Bir oluşum ekleyin",
|
||||
"Add some tags": "Bazı etiketler ekleyin",
|
||||
"Add to my calendar": "Takvimime ekle",
|
||||
"Additional comments": "Ek yorumlar",
|
||||
"Admin": "Admin",
|
||||
"Admin settings successfully saved.": "Admin ayarları başarıyla kaydedildi.",
|
||||
"Administration": "Administration",
|
||||
"All the places have already been taken": "Tüm alanlar çoktan alındı",
|
||||
"Allow registrations": "Kayıtlara izin ver",
|
||||
"Anonymous participant": "Anonim katılımcı",
|
||||
"Anonymous participants will be asked to confirm their participation through e-mail.": "Anonim katılımcılardan e-mail aracılığıyla katılımlarını doğrulamaları istenecektir.",
|
||||
"Anonymous participations": "Anonim katılımcılar",
|
||||
"Are you really sure you want to delete your whole account? You'll lose everything. Identities, settings, events created, messages and participations will be gone forever.": "Tüm hesabınızı silmek istediğinizden gerçekten emin misiniz? Her şeyinizi kaybedeceksiniz. Kimlikler, ayarlar, oluşturulan etkinlikler, mesajlar ve katılımlar tamamen yok olacak.",
|
||||
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone.": "Bu yorumu <b>silmek </b> istediğinizden emin misiniz? Bu işlem geri alınamaz.",
|
||||
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the discussion with the event creator or edit its event instead.": "Bu etkinliği<b>silmek </b> istediğinizden emin misiniz? Bu eylem geri alınamaz. Bunun yerine etkinliği oluşturan kişiyle konuşmak veya etkinliğini düzenlemek isteyebilirsiniz.",
|
||||
"Are you sure you want to cancel the event creation? You'll lose all modifications.": "Etkinlik oluşturma işlemini iptal etmek istediğinizden emin misiniz? Tüm değişiklikler kaybolacak.",
|
||||
"Are you sure you want to cancel the event edition? You'll lose all modifications.": "Etkinlik düzenlemesini iptal etmek istediğinizden emin misiniz? Tüm değişiklikleri kaybedeceksiniz.",
|
||||
"Are you sure you want to cancel your participation at event \"{title}\"?": "\"{title}\" etkinliğine katılımınızı iptal etmek istediğinizden emin misiniz?",
|
||||
"Are you sure you want to delete this event? This action cannot be reverted.": "Bu etkinliği silmek istediğinizden emin misiniz? Bu eylem geri döndürülemez.",
|
||||
"Avatar": "Avatar",
|
||||
"Back to previous page": "Önceki sayfaya dön",
|
||||
"Before you can login, you need to click on the link inside it to validate your account.": "Giriş yapmadan önce, hesabınızı doğrulamak için gönderilen bağlantıya tıklamanız gerekir.",
|
||||
"By {username}": "{username} tarafından",
|
||||
"Cancel": "İptal et",
|
||||
"Cancel anonymous participation": "Anonim katılımı iptal et",
|
||||
"Cancel creation": "Oluşturmayı iptal et",
|
||||
"Cancel edition": "Düzenlemeyi iptal et.",
|
||||
"Cancel my participation request…": "Katılım talebimi iptal edin…",
|
||||
"Cancel my participation…": "Katılımımı iptal edin…",
|
||||
"Cancelled: Won't happen": "İptal edildi: Olmayacak",
|
||||
"Change": "Değiştir",
|
||||
"Change my email": "E-posta adresimi değiştir",
|
||||
"Change my identity…": "Kimliğimi değiştir…",
|
||||
"Change my password": "Parolamı değiştir",
|
||||
"Clear": "Temizle",
|
||||
"Click to upload": "Yüklemek için tıklayın",
|
||||
"Close": "Kapat",
|
||||
"Close comments for all (except for admins)": "Yorumları herkes için kapatın (yöneticiler hariç)",
|
||||
"Closed": "Kapalı",
|
||||
"Comment deleted": "Yorum silindi",
|
||||
"Comment from {'@'}{username} reported": "{'@'}{username} tarafından yapılan yorum rapor edildi",
|
||||
"Comments": "Yorumlar",
|
||||
"Confirm my participation": "Katılımımı onayla",
|
||||
"Confirm my particpation": "Katılımımı onayla",
|
||||
"Confirmed: Will happen": "Onaylandı: Gerçekleşecek",
|
||||
"Continue editing": "Düzenlemeye devam et",
|
||||
"Country": "Ülke",
|
||||
"Create": "Oluştur",
|
||||
"Create a new event": "Yeni bir etkinlik oluşturun",
|
||||
"Create a new group": "Yeni bir grup oluşturun",
|
||||
"Create a new identity": "Yeni bir kimlik oluşturun",
|
||||
"Create group": "Grup oluştur",
|
||||
"Create my event": "Etkinliğimi oluştur",
|
||||
"Create my group": "Grubumu oluştur",
|
||||
"Create my profile": "Profilimi oluştur",
|
||||
"Create token": "Token oluştur",
|
||||
"Current identity has been changed to {identityName} in order to manage this event.": "Bu etkinliği yönetmek için mevcut kimlik {identityName} olarak değiştirildi.",
|
||||
"Current page": "Geçerli sayfa",
|
||||
"Custom": "Özel",
|
||||
"Custom URL": "Özel URL",
|
||||
"Custom text": "Özel metin",
|
||||
"Dashboard": "Kontrol Paneli",
|
||||
"Date": "Tarih",
|
||||
"Date and time settings": "Tarih ve saat ayarları",
|
||||
"Date parameters": "Tarih parametreleri",
|
||||
"Default": "Varsayılan",
|
||||
"Delete": "Sil",
|
||||
"Delete account": "Hesabı sil",
|
||||
"Delete event": "Etkinliği sil",
|
||||
"Delete everything": "Her şeyi sil",
|
||||
"Delete my account": "Hesabımı sil",
|
||||
"Delete this identity": "Bu kimliği silin",
|
||||
"Delete your identity": "Kimliğinizi silin",
|
||||
"Delete {eventTitle}": "{eventTitle} Sil",
|
||||
"Delete {preferredUsername}": "{preferredUsername} Sil",
|
||||
"Deleting comment": "Yorumları sil",
|
||||
"Deleting event": "Etkinliği iptal et",
|
||||
"Deleting my account will delete all of my identities.": "Hesabımı silmek tüm kimliklerimi silecektir.",
|
||||
"Deleting your Mobilizon account": "Mobilizon hesabınızı silme",
|
||||
"Description": "Açıklama",
|
||||
"Display name": "Görünen ad",
|
||||
"Display participation price": "Katılım ücretini göster",
|
||||
"Domain": "Alan adı",
|
||||
"Draft": "Taslak",
|
||||
"Drafts": "Taslaklar",
|
||||
"Edit": "Düzenle",
|
||||
"Eg: Stockholm, Dance, Chess…": "Örneğin: İstanbul, Dans, Satranç…",
|
||||
"Either on the {instance} instance or on another instance.": "Ya {instance} oluşumunda ya da başka bir oluşumda.",
|
||||
"Either the account is already validated, either the validation token is incorrect.": "Hesap zaten doğrulanmış ya da doğrulama tokeni yanlış.",
|
||||
"Either the email has already been changed, either the validation token is incorrect.": "E-posta zaten değiştirilmiş ya da doğrulama tokeni yanlış.",
|
||||
"Either the participation request has already been validated, either the validation token is incorrect.": "Katılım talebi zaten doğrulanmış ya da doğrulama tokeni yanlış.",
|
||||
"Email": "E-mail",
|
||||
"Ends on…": "Bitecek…",
|
||||
"Enter the link URL": "Bağlantı URL'sini girin",
|
||||
"Error while changing email": "E-posta değiştirilirken hata oluştu",
|
||||
"Error while validating account": "Hesap doğrulanırken hata oluştu",
|
||||
"Error while validating participation request": "Katılım isteği doğrulanırken hata oluştu",
|
||||
"Event": "Etkinlik",
|
||||
"Event already passed": "Etkinliğin süresi çoktan bitti",
|
||||
"Event cancelled": "Etkinlik iptal edildi",
|
||||
"Event creation": "Etkinlik oluşturma",
|
||||
"Event edition": "Etkinlik düzenleme",
|
||||
"Event list": "Etkinlik listesi",
|
||||
"Event page settings": "Etkinlik sayfası ayarları",
|
||||
"Event to be confirmed": "Etkinlik doğrulanacak",
|
||||
"Event {eventTitle} deleted": "{eventTitle} etkinliği silindi",
|
||||
"Event {eventTitle} reported": "{eventTitle} etkinliği rapor edildi",
|
||||
"Events": "Etkinlikler",
|
||||
"Ex: mobilizon.fr": "Örn: mobilizon.fr",
|
||||
"Explore": "Keşfedin",
|
||||
"Failed to save admin settings": "Yönetici ayarlarını kaydetme başarısız",
|
||||
"Featured events": "Öne çıkan etkinlikler",
|
||||
"Federation": "Bir-ağ",
|
||||
"Find an address": "Bir adres bulun",
|
||||
"Find an instance": "Bir oluşum bulun",
|
||||
"Followers": "Takipçiler",
|
||||
"Followings": "Takip edilenler",
|
||||
"For instance: London, Taekwondo, Architecture…": "Örneğin: İstanbul, Taekwondo, Mimarlık…",
|
||||
"Forgot your password ?": "Parolanızı mı unuttunuz?",
|
||||
"From the {startDate} at {startTime} to the {endDate} at {endTime}": "{startDate} tarihi ve {startTime} ve saati ile {endDate} saat {endTime} arasında",
|
||||
"From the {startDate} to the {endDate}": "",
|
||||
"Gather ⋅ Organize ⋅ Mobilize": "Toplan ⋅ Organize ol ⋅ Harekete geç",
|
||||
"General": "Genel",
|
||||
"General information": "Genel bilgi",
|
||||
"Getting location": "Konum alınıyor",
|
||||
"Go": "Git",
|
||||
"Group name": "Grup adı",
|
||||
"Group {displayName} created": "{displayName} grubu oluşturuldu",
|
||||
"Groups": "Gruplar",
|
||||
"Headline picture": "Manşet resmi",
|
||||
"Hide replies": "Yanıtları gizle",
|
||||
"I create an identity": "Bir kimlik oluşturuyorum",
|
||||
"I don't have a Mobilizon account": "Mobilizon hesabım yok",
|
||||
"I have a Mobilizon account": "Mobilizon hesabım var",
|
||||
"I have an account on another Mobilizon instance.": "Başka bir Mobilizon oluşumunda hesabım var.",
|
||||
"I participate": "Ben de katılıyorum.",
|
||||
"I want to allow people to participate without an account.": "İnsanların bir hesap olmadan katılmalarına izin vermek istiyorum.",
|
||||
"I want to approve every participation request": "Her katılım talebini onaylamak istiyorum",
|
||||
"Identity {displayName} created": "{displayName} kimliği oluşturuldu",
|
||||
"Identity {displayName} deleted": "{displayName} kimliği silindi",
|
||||
"Identity {displayName} updated": "{displayName} kimliği güncellendi",
|
||||
"If an account with this email exists, we just sent another confirmation email to {email}": "Bu e-postaya sahip bir hesap varsa, {email} adresine yeni bir onay e-postası gönderdik",
|
||||
"If this identity is the only administrator of some groups, you need to delete them before being able to delete this identity.": "Bu kimlik bazı grupların tek yöneticisi ise, bu kimliği silebilmeniz için önce o grupları silmeniz gerekir.",
|
||||
"If you want, you may send a message to the event organizer here.": "İsterseniz etkinlik organizatörüne buradan mesaj gönderebilirsiniz.",
|
||||
"Instance Name": "Oluşum adı",
|
||||
"Instance Terms": "Oluşum Şartları",
|
||||
"Instance Terms Source": "Oluşum Şartları Kaynağı",
|
||||
"Instance Terms URL": "Oluşum Şartları URL'si",
|
||||
"Instance settings": "Oluşum Ayarları",
|
||||
"Instances": "Oluşumlar",
|
||||
"Join <b>{instance}</b>, a Mobilizon instance": "Bir Mobilizon oluşumuna <b>{instance}</b> katılın",
|
||||
"Last published event": "Son yayınlanan etkinlik",
|
||||
"Last week": "Geçen hafta",
|
||||
"Learn more": "Daha fazlasını öğrenin",
|
||||
"Learn more about Mobilizon": "Mobilizon hakkında daha fazla bilgi edinin",
|
||||
"Leave event": "Etkinlikten ayrılın",
|
||||
"Leaving event \"{title}\"": "\"{title}\" etkinliğinden çıkılıyor",
|
||||
"License": "Lisans",
|
||||
"Limited number of places": "Sınırlı sayıda yer",
|
||||
"Load more": "Daha fazla yükle",
|
||||
"Locality": "Yerellik",
|
||||
"Log in": "Giriş yap",
|
||||
"Log out": "Çıkış yap",
|
||||
"Login": "Giriş Yap",
|
||||
"Login on Mobilizon!": "Mobilizon'a giriş yapın!",
|
||||
"Login on {instance}": "{instance}'da oturum aç",
|
||||
"Manage participations": "Katılımları yönetin",
|
||||
"Mark as resolved": "Çözüldü olarak işaretle",
|
||||
"Members": "Üyeler",
|
||||
"Message": "Mesaj",
|
||||
"Mobilizon is a federated network. You can interact with this event from a different server.": "Mobilizon özerkleşmiş bir ağdır. Bu etkinlik için farklı bir sunucudan etkileşim kurabilirsiniz.",
|
||||
"Moderated comments (shown after approval)": "Denetlenen yorumlar (onaylandıktan sonra gösterilir)",
|
||||
"Moderation": "Moderasyon",
|
||||
"Moderation log": "Moderasyon günlüğü",
|
||||
"My account": "Hesabım",
|
||||
"My events": "Benim etkinliklerim",
|
||||
"My identities": "Benim kimliklerim",
|
||||
"Name": "İsim",
|
||||
"New email": "Yeni e-posta",
|
||||
"New note": "Yeni not",
|
||||
"New password": "Yeni parola",
|
||||
"New profile": "Yeni profil",
|
||||
"Next page": "Sonraki sayfa",
|
||||
"No address defined": "Hiçbir adres tanımlanmadı",
|
||||
"No closed reports yet": "",
|
||||
"No comment": "Yorum yok",
|
||||
"No comments yet": "Henüz yorum yok",
|
||||
"No end date": "Bitiş tarihi yok",
|
||||
"No events found": "Hiçbir etkinlik bulunamadı",
|
||||
"No group found": "Grup bulunamadı",
|
||||
"No groups found": "Gruplar bulunamadı",
|
||||
"No instance follows your instance yet.": "Henüz hiçbir oluşum sizin oluşumunuzu takip etmiyor.",
|
||||
"No instance to approve|Approve instance|Approve {number} instances": "Onaylanacak oluşum yok| Oluşum onayla| {number} oluşumu onayla",
|
||||
"Please do not use it in any real way.": "Lütfen bunu gerçek anlamda kullanmayın."
|
||||
});
|
||||
3
res/locale/tt.js
Normal file
3
res/locale/tt.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
CTX.setMessages({
|
||||
"Add": "Өстәргә"
|
||||
});
|
||||
1452
res/locale/zh_Hans.js
Normal file
1452
res/locale/zh_Hans.js
Normal file
File diff suppressed because it is too large
Load diff
1296
res/locale/zh_Hant.js
Normal file
1296
res/locale/zh_Hant.js
Normal file
File diff suppressed because it is too large
Load diff
190
src/context.js
Normal file
190
src/context.js
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
const LANGUAGES = {
|
||||
ar: "اَلْعَرَبِيَّةُ",
|
||||
be: "Беларуская мова",
|
||||
bn: "বাংলা",
|
||||
ca: "Català",
|
||||
cs: "Čeština",
|
||||
cy: "Cymraeg",
|
||||
da: "Dansk",
|
||||
de: "Deutch",
|
||||
dsb: "",
|
||||
en_US: "English (US)",
|
||||
eo: "Esperanto",
|
||||
es: "Español",
|
||||
eu: "",
|
||||
fa: "فارسی",
|
||||
fi: "Suomi",
|
||||
fr_FR: "Français",
|
||||
gd: "Gàidhlig",
|
||||
gl: "Galego",
|
||||
he: "עברית",
|
||||
hr: "Hrvatski",
|
||||
hu: "Magyar nyelv",
|
||||
id: "bahasa Indonesia",
|
||||
it: "Italiano",
|
||||
ja: "日本語",
|
||||
kab: "",
|
||||
kn: "",
|
||||
ko: "한국어",
|
||||
nl: "Nederlands",
|
||||
nn: "Norsk Nynorsk",
|
||||
oc: "Occitan",
|
||||
pl: "Polski",
|
||||
pt_BR: "Português brasileiro",
|
||||
pt: "Português",
|
||||
ru: "Русский язык",
|
||||
sl: "Slovenščina",
|
||||
sv: "Svenska",
|
||||
th: "ภาษาไทย",
|
||||
tr: "Türkçe",
|
||||
tt: "",
|
||||
zh_Hans: "简体字",
|
||||
zh_Hant: "繁體字"
|
||||
};
|
||||
const LANG_FALLBACK = "en_US";
|
||||
|
||||
class Context {
|
||||
lang;
|
||||
langMessages;
|
||||
langLoaded;
|
||||
ready;
|
||||
|
||||
init() {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
var lang = sessionStorage.getItem("lang");
|
||||
if (lang && LANGUAGES[lang]) {
|
||||
var newLang = sessionStorage.getItem("newLang");
|
||||
if (newLang && LANGUAGES[newLang]) {
|
||||
this.lang = newLang;
|
||||
sessionStorage.removeItem("newLang");
|
||||
this.loadLanguage();
|
||||
} else {
|
||||
this.lang = lang;
|
||||
this.loadLanguage();
|
||||
}
|
||||
} else {
|
||||
var languages = navigator.languages;
|
||||
var found = false;
|
||||
for (var i = 0; i < languages.length; i++) {
|
||||
var lang = languages[i].replace("-", "_");
|
||||
if (LANGUAGES[lang]) {
|
||||
this.lang = lang;
|
||||
this.loadLanguage();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
this.lang = LANG_FALLBACK;
|
||||
this.loadLanguage();
|
||||
}
|
||||
}
|
||||
var user = sessionStorage.getItem("user");
|
||||
if (user) {
|
||||
var rawUser = JSON.parse(user);
|
||||
this.user = User.fromData(rawUser.host, rawUser.data);
|
||||
}
|
||||
var hostConfig = sessionStorage.getItem("host");
|
||||
if (hostConfig) {
|
||||
var rawConfig = JSON.parse(hostConfig);
|
||||
this.hostConfig = HostConfig.fromData(rawConfig.host, rawConfig.data);
|
||||
}
|
||||
this.initialized = true;
|
||||
}
|
||||
loadLanguage() {
|
||||
if (this.langMessages) {
|
||||
return;
|
||||
}
|
||||
var lang = this.lang;
|
||||
if (!LANGUAGES[lang]) {
|
||||
lang = LANG_FALLBACK;
|
||||
}
|
||||
// Use <script>, XHR request is likely to be blocked by CORS locally
|
||||
var script = document.createElement("script");
|
||||
script.setAttribute("type", "text/javascript");
|
||||
script.setAttribute("src", "res/locale/" + this.lang + ".js");
|
||||
document.getElementsByTagName("head")[0].appendChild(script);
|
||||
return true;
|
||||
}
|
||||
switchLanguage(newLang) {
|
||||
sessionStorage.setItem("newLang", newLang);
|
||||
}
|
||||
setMessages(dict) {
|
||||
this.langMessages = dict;
|
||||
sessionStorage.setItem("lang", this.lang);
|
||||
this.langLoaded = true;
|
||||
this.checkReady();
|
||||
}
|
||||
checkReady() {
|
||||
if (this.langLoaded && this.page) {
|
||||
this.page.boot();
|
||||
}
|
||||
}
|
||||
setPage(page) {
|
||||
this.page = page;
|
||||
this.checkReady();
|
||||
}
|
||||
setUser(user) {
|
||||
this.user = user;
|
||||
if (user !== null) {
|
||||
sessionStorage.setItem("user", JSON.stringify(this.user));
|
||||
} else {
|
||||
sessionStorage.removeItem("user");
|
||||
}
|
||||
}
|
||||
getUser() {
|
||||
return this.user;
|
||||
}
|
||||
setHostConfig(hostConfig) {
|
||||
this.hostConfig = hostConfig;
|
||||
if (hostConfig !== null) {
|
||||
sessionStorage.setItem("host", JSON.stringify(this.hostConfig));
|
||||
} else {
|
||||
sessionStorage.removeItem("host");
|
||||
}
|
||||
}
|
||||
getHostConfig() {
|
||||
return this.hostConfig;
|
||||
}
|
||||
}
|
||||
|
||||
const CTX = new Context();
|
||||
|
||||
/**
|
||||
* Traduki mesaĝo por ordinaran tekston.
|
||||
*/
|
||||
function t(message, replacements = {}) {
|
||||
if (CTX.lang && CTX.langMessages[message]) {
|
||||
var msg = CTX.langMessages[message];
|
||||
Object.keys(replacements).forEach(key => {
|
||||
msg = msg.replaceAll("{" + key + "}", replacements[key]);
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traduki mesaĝo por HTML-trajton.
|
||||
*/
|
||||
function ta(message, replacements = {}) {
|
||||
return t(message, replacements).replace(/"/g, """);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traduki la nomon de la rolo.
|
||||
* @param role La rolo por traduki.
|
||||
* @return La tradukita nomo por la rolo. La angla vorto en la okaso se
|
||||
* la tradukito ne eksistas.
|
||||
*/
|
||||
function trole(role) {
|
||||
if (role === ROLE.ADMINISTRATOR) {
|
||||
return t("Administrator");
|
||||
} else if (role === ROLE.MODERATOR) {
|
||||
return t("Moderator");
|
||||
} else {
|
||||
return role;
|
||||
}
|
||||
}
|
||||
89
src/global.js
Normal file
89
src/global.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
function boot() {
|
||||
CTX.init();
|
||||
route();
|
||||
}
|
||||
|
||||
function GET(param) {
|
||||
param = param.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
|
||||
var regexS = "[\\?&]" + param + "=([^&#]*)";
|
||||
var regex = new RegExp(regexS);
|
||||
var results = regex.exec(window.location.href);
|
||||
if (results === null)
|
||||
return null;
|
||||
else
|
||||
return decodeURIComponent(results[1]);
|
||||
}
|
||||
|
||||
function escAttr(string) {
|
||||
if (string) {
|
||||
return string.replace(/"/g, """)
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function setTitle(title) {
|
||||
document.getElementsByTagName("title")[0].innerHTML = title + " — Mobilistache";
|
||||
}
|
||||
|
||||
function isHandle(handle) {
|
||||
if (typeof handle !== "string") {
|
||||
return false;
|
||||
}
|
||||
if (!handle.startsWith("@")) {
|
||||
return false;
|
||||
}
|
||||
return handle.split("@").length >= 3;
|
||||
}
|
||||
function splitHandle(handle) {
|
||||
if (!isHandle(handle)) {
|
||||
return null;
|
||||
}
|
||||
var parts = handle.split("@");
|
||||
if (parts.length < 3) {
|
||||
return null;
|
||||
}
|
||||
var host = parts[parts.length -1];
|
||||
var name = parts.slice(1, -1).join("@");
|
||||
return ({host: host, name: name});
|
||||
}
|
||||
function getHandle(host, name) {
|
||||
return "@" + name + "@" + host;
|
||||
}
|
||||
|
||||
function getGroupUrl(handle) {
|
||||
return "./index.html?page=group&handle=" + encodeURIComponent(handle);
|
||||
}
|
||||
|
||||
function getEventUrl(host, eventUuid) {
|
||||
return "./index.html?page=event&host=" + encodeURIComponent(host) + "&event=" + encodeURIComponent(eventUuid);
|
||||
}
|
||||
|
||||
function getAddEventUrl(group) {
|
||||
return ("index.html?page=add-event&group=" + encodeURIComponent(group.getUsername()));
|
||||
}
|
||||
|
||||
function replaceContent(parentElement, contentElement) {
|
||||
while (parentElement.hasChildNodes()) {
|
||||
parentElement.removeChild(firstChild);
|
||||
}
|
||||
parentElement.appendChild(contentElement);
|
||||
}
|
||||
|
||||
class Widget {
|
||||
root;
|
||||
/**
|
||||
* Add this widget as a child of the given element.
|
||||
* @param element The element to add the widget to.
|
||||
*/
|
||||
showIn(element) {
|
||||
element.appendChild(this.root);
|
||||
}
|
||||
/**
|
||||
* Add this widget in place of the given element.
|
||||
* @param element The element to merge the widget into.
|
||||
* All attributes of the element are discarded.
|
||||
*/
|
||||
mergeInto(element) {
|
||||
element.parentNode.replaceChild(this.root, element);
|
||||
}
|
||||
}
|
||||
128
src/model/event.js
Normal file
128
src/model/event.js
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* La konzceptado de eventoj.
|
||||
* Vidi mobilizon/lib/graphql/event.ts pri la datumoj.
|
||||
*/
|
||||
class Event {
|
||||
|
||||
/**
|
||||
* La plata datumoj el la servilo.
|
||||
*/
|
||||
data;
|
||||
|
||||
constructor(host) {
|
||||
this.host = host;
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Konstrui evento per datumoj elde servilo.
|
||||
* @param host La servilo kie la evento estas registra.
|
||||
* @param content La datumoj elde la servilo.
|
||||
* @return {Object} La evento.
|
||||
*/
|
||||
static fromData(host, content) {
|
||||
if (typeof content === "string") {
|
||||
content = JSON.parse(content);
|
||||
}
|
||||
var event = new Event(host);
|
||||
event.data = content;
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ricevi la URL pri la rubando de la evento.
|
||||
* @return {String} La URL pri la rubando de la evento, aŭ null.
|
||||
*/
|
||||
getPictureUrl() {
|
||||
return this.data.picture?.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ricevi la komenca dato de la evento.
|
||||
* @return {Date} La komenka dato pri la evento, aŭ null.
|
||||
*/
|
||||
getBeginDate() {
|
||||
if (this.data.beginsOn) {
|
||||
return new Date(this.data.beginsOn);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Ricevi la fina dato de la evento.
|
||||
* @return {Date} La fina dato pri la evento, aŭ null.
|
||||
*/
|
||||
getEndDate() {
|
||||
if (this.data.endsOn) {
|
||||
return new Date(this.data.endsOn);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ricevi la kompleta adreso de la evento.
|
||||
* @return {String} La kompleta adreso.
|
||||
*/
|
||||
getPhysicalAddressString() {
|
||||
if (this.data.physicalAddress) {
|
||||
var addr = this.data.physicalAddress.description;
|
||||
if (this.data.physicalAddress.locality) {
|
||||
return addr += ", " + this.data.physicalAddress.locality;
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getTags() {
|
||||
var tags = [];
|
||||
this.data.tags?.forEach(t => {
|
||||
tags.push({slug: t.slug, title: t.title});
|
||||
});
|
||||
return tags;
|
||||
}
|
||||
|
||||
getDescription(cutAtChar = 0) {
|
||||
let desc = this.data?.description;
|
||||
if (desc == null) {
|
||||
return null;
|
||||
}
|
||||
if (cutAtChar <= 0) {
|
||||
return desc;
|
||||
} else {
|
||||
let wordCut = 0;
|
||||
let i = 0;
|
||||
while (i < Math.min(desc.length, cutAtChar)) {
|
||||
if (desc.charAt(i) == " " || desc.charAt(i) == "\n") {
|
||||
wordCut = i;
|
||||
}
|
||||
i++;
|
||||
if (i == desc.length) {
|
||||
wordCut = i;
|
||||
}
|
||||
}
|
||||
if (wordCut < desc.length) {
|
||||
return desc.substring(0, wordCut) + "…";
|
||||
} else {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTicketUri() {
|
||||
if (this.data.metadata) {
|
||||
var ticket = this.data.metadata.find(m => m.key === "mz:ticket:external_uri");
|
||||
if (ticket) {
|
||||
return ticket.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recivi la URL de la evento en la originala servilo.
|
||||
* @return {String} La URL de la evento en la originala servilo.
|
||||
*/
|
||||
getOnInstanceUrl() {
|
||||
return "https://" + encodeURIComponent(this.host) + "/events/" + encodeURIComponent(this.data.uuid);
|
||||
}
|
||||
}
|
||||
51
src/model/group.js
Normal file
51
src/model/group.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
class Group {
|
||||
static fromData(host, content) {
|
||||
if (typeof content === "string") {
|
||||
content = JSON.parse(content);
|
||||
}
|
||||
var group = new Group();
|
||||
group.host = host;
|
||||
group.data = content;
|
||||
return group;
|
||||
}
|
||||
|
||||
getHandle() {
|
||||
return "@" + this.getUsername() + "@" + this.host;
|
||||
}
|
||||
|
||||
getAvatarUrl() {
|
||||
return this.data.avatar?.url;
|
||||
}
|
||||
getBannerUrl() {
|
||||
return this.data.banner?.url;
|
||||
}
|
||||
getUsername() {
|
||||
return this.data.preferredUsername;
|
||||
}
|
||||
|
||||
getLocationString() {
|
||||
return this.data.physicalAddress?.description;
|
||||
}
|
||||
|
||||
getIcsUrl() {
|
||||
return this.data.url + "/feed/ics";
|
||||
}
|
||||
getAtomUrl() {
|
||||
return this.data.url + "/feed/atom";
|
||||
}
|
||||
|
||||
getEvents() {
|
||||
var events = [];
|
||||
for (var evt in this.data.organizedEvents?.elements) {
|
||||
events.push(Event.fromData(this.host, this.data.organizedEvents.elements[evt]));
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
canBeEditedBy(role) {
|
||||
return role === ROLE.ADMINISTRATOR;
|
||||
}
|
||||
canManageEvents(role) {
|
||||
return role === ROLE.ADMINISTRATOR || role === ROLE.MODERATOR;
|
||||
}
|
||||
}
|
||||
25
src/model/host.js
Normal file
25
src/model/host.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
class HostConfig {
|
||||
static fromConfigData(host, content) {
|
||||
if (typeof content === "string") {
|
||||
content = JSON.parse(content);
|
||||
}
|
||||
var host = new HostConfig(host);
|
||||
host.config.data = content;
|
||||
// La horzonoj ne devenas el la konfiguraĵo de la servilo
|
||||
// sed povas deveni el sessionStorage.
|
||||
if (content.timezones) {
|
||||
host.timezones = content.timezones;
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
constructor(host) {
|
||||
this.host = host;
|
||||
this.config = {};
|
||||
this.timezones = [];
|
||||
}
|
||||
|
||||
setTimezonesFromData(timezones) {
|
||||
this.timezones = timezones;
|
||||
}
|
||||
}
|
||||
81
src/model/user.js
Normal file
81
src/model/user.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
class User {
|
||||
static fromData(host, content) {
|
||||
if (typeof content === "string") {
|
||||
content = JSON.parse(content);
|
||||
}
|
||||
var user = new User();
|
||||
user.host = host;
|
||||
user.data = content;
|
||||
user.memberships = [];
|
||||
if (content.memberships) {
|
||||
content.memberships.forEach(m => {
|
||||
user.memberships.push(Membership.fromData(host, m));
|
||||
});
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
getMemberships() {
|
||||
var memberships = [];
|
||||
var host = this.host;
|
||||
if (this.data.memberships) {
|
||||
this.data.memberships.forEach(m => {
|
||||
memberships.push(Membership.fromData(host, m));
|
||||
});
|
||||
}
|
||||
return memberships;
|
||||
}
|
||||
|
||||
findMyGroup(groupUsername) {
|
||||
var membership = this.memberships.find(m => m.group.username === groupUsername);
|
||||
if (membership) {
|
||||
return membership.group;
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
getGroupRole(group) {
|
||||
var membership = this.memberships.find(m => m.group.username === group.username);
|
||||
if (membership) {
|
||||
return membership.role;
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
canManageEvent(event) {
|
||||
return false;
|
||||
// participations.length > 0 && participations[0].role === ParticipantRole.CREATOR
|
||||
// || MemberRole.MODERATOR|MemberRole.ADMINISTRATOR in person.memberships.elements[0].role
|
||||
}
|
||||
canEditGroup(group) {
|
||||
return group.canBeEditedBy(this.getGroupRole(group));
|
||||
}
|
||||
canManageGroupEvents(group) {
|
||||
return group.canManageEvents(this.getGroupRole(group));
|
||||
}
|
||||
}
|
||||
|
||||
const ROLE = {
|
||||
ADMINISTRATOR: "ADMINISTRATOR",
|
||||
MODERATOR: "MODERATOR",
|
||||
// MEMBER,
|
||||
// GUEST,
|
||||
// PENDING,
|
||||
// REJECTED,
|
||||
// FOLLOWER (not a group membership status per see from the API)
|
||||
}
|
||||
|
||||
class Membership {
|
||||
static fromData(host, content) {
|
||||
var membership = new Membership();
|
||||
membership.role = content.role;
|
||||
if (content.group) {
|
||||
membership.group = Group.fromData(host, content.group.data);
|
||||
return membership;
|
||||
} else if (content.parent.type === "GROUP") {
|
||||
membership.group = Group.fromData(host, content.parent);
|
||||
return membership;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
24
src/pages/page_event.js
Normal file
24
src/pages/page_event.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
class PageEventDetail extends Page {
|
||||
boot() {
|
||||
super.boot();
|
||||
var template = `
|
||||
<header id="event-banner"></header>
|
||||
<main id="event-content" class="l-section l-centered"></main>
|
||||
`
|
||||
document.getElementById("content").innerHTML = template;
|
||||
var host = GET("host");
|
||||
var uuid = GET("event");
|
||||
if (host && uuid) {
|
||||
requestEvent(host, uuid, function(request, status, result) {
|
||||
if (status === 200) {
|
||||
setTitle(result.title);
|
||||
var banner = new WidgetEventBanner(result);
|
||||
banner.mergeInto(document.getElementById("event-banner"));
|
||||
var widget = new WidgetEventDetail(result);
|
||||
widget.showIn(document.getElementById("event-content"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const PAGE_EVENT_DETAIL = new PageEventDetail();
|
||||
37
src/pages/page_event_edit.js
Normal file
37
src/pages/page_event_edit.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
class PageEventEdit extends Page {
|
||||
boot() {
|
||||
super.boot();
|
||||
var template = `
|
||||
<main id="event-edit" class="l-section l-centered">
|
||||
<h1>{{Event}}</h1>
|
||||
<div class="l-flexcenter">
|
||||
<div id="event-form"></div>
|
||||
<div id="event-preview"></div>
|
||||
</div>
|
||||
</main>
|
||||
`;
|
||||
var groupUsername = GET("group");
|
||||
var eventUuid = GET("event");
|
||||
if (groupUsername) {
|
||||
var group = CTX.getUser().findMyGroup(groupUsername);
|
||||
this.event = new Event(CTX.getUser().host, group);
|
||||
document.getElementById("content").innerHTML = template
|
||||
.replace("{{Event}}", t("Create a new event"));"Event edition"
|
||||
var formWidget = new WidgetEventForm(this.event);
|
||||
var previewWidget = formWidget.createPreview();
|
||||
formWidget.showIn(document.getElementById("event-form"));
|
||||
previewWidget.showIn(document.getElementById("event-preview"));
|
||||
} else if (eventUuid) {
|
||||
var host = CTX.getUser().host;
|
||||
requestEvent(host, uuid, function(request, status, result) {
|
||||
if (status === 200) {
|
||||
document.getElementById("content").innerHTML = template
|
||||
.replace("{{Event}}", t("Event edition"));
|
||||
var widget = new WidgetEventForm(CTX, result);
|
||||
widget.showIn(document.getElementById("event-edit"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const PAGE_EVENT_EDIT = new PageEventEdit();
|
||||
31
src/pages/page_group.js
Normal file
31
src/pages/page_group.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
class PageGroupPublic extends Page {
|
||||
boot() {
|
||||
super.boot();
|
||||
var query = splitHandle(GET("handle"));
|
||||
if (!query) {
|
||||
console.error("Error reading handle from " + location.href);
|
||||
location.replace("index.html");
|
||||
return;
|
||||
}
|
||||
var template = `
|
||||
<header id="group-banner"></header>
|
||||
<main id="group-content" class="l-section l-centered"></main>
|
||||
`;
|
||||
document.getElementById("content").innerHTML = template;
|
||||
var host = query.host;
|
||||
var name = query.name;
|
||||
if (host && name) {
|
||||
requestGroup(host, name, function(request, status, result) {
|
||||
if (status === 200) {
|
||||
setTitle(result.name);
|
||||
var banner = new WidgetGroupBanner(result);
|
||||
banner.mergeInto(document.getElementById("group-banner"));
|
||||
var content = new WidgetGroupDetail(result, CTX.getUser()?.getGroupRole(result));
|
||||
content.showIn(document.getElementById("group-content"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const PAGE_GROUP_PUBLIC = new PageGroupPublic();
|
||||
|
||||
121
src/pages/page_index.js
Normal file
121
src/pages/page_index.js
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
class PageIndex extends Page {
|
||||
boot(ctx) {
|
||||
super.boot();
|
||||
this.ctx = ctx;
|
||||
var template = `
|
||||
<section class="l-section l-centered">
|
||||
<h2>Explorer</h2>
|
||||
<div class="l-flexcenter">
|
||||
<div class="l-subsection">
|
||||
<h3>Parcourir une instance</h3>
|
||||
<form method="get" action="index.html" onsubmit="javascript:return PAGE_INDEX.goInstance();">
|
||||
<div class="form-field">
|
||||
<input type="hidden" name="page" value="instance">
|
||||
<label for="instance">Instance</label>
|
||||
<input id="instance" name="instance" type="text" disabled="true">
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<input type="submit" value="Parcourir" class="btn btn-nav" disabled="true">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="l-subsection">
|
||||
<h3>Consulter un groupe</h3>
|
||||
<form method="get" action="index.html" onsubmit="javascript:return PAGE_INDEX.searchGroup();">
|
||||
<div class="form-field">
|
||||
<input type="hidden" name="page" value="group">
|
||||
<label for="group-handle">Nom et instance</label>
|
||||
<input id="group-handle" name="handle" type="text" placeholder="@nom@instance">
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<input type="submit" value="Rechercher" class="btn btn-nav">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="l-centered l-subsection">
|
||||
<h2>Connexion</h2>
|
||||
<div class="l-flexcenter">
|
||||
<form class="l-flexfill" method="get" action="index.html" onsubmit="javascript:return PAGE_INDEX.login();">
|
||||
<div class="form-field">
|
||||
<label for="user-host">Instance</label>
|
||||
<input id="user-host" name="user-host" type="text">
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="user-email">{{Email}}</label>
|
||||
<input id="user-email" name="user-email" type="text">
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="user-password">{{Password}}</label>
|
||||
<input id="user-password" name="user-password" type="password">
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<input type="submit" value="{{Log in}}" class="btn btn-nav">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
document.getElementById("content").innerHTML = template
|
||||
.replace("{{Email}}", t("Email address"))
|
||||
.replace("{{Password}}", t("Password"))
|
||||
.replace("{{Log in}}", ta("Log in"));
|
||||
new WidgetInputPassword("user-password", "user-password").mergeInto(document.getElementById("user-password"));
|
||||
}
|
||||
goInstance() {
|
||||
return false;
|
||||
}
|
||||
searchGroup() {
|
||||
var value = document.getElementById("group-handle").value;
|
||||
if (!isHandle(value)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
login() {
|
||||
var host = document.getElementById("user-host").value;
|
||||
if (host.startsWith("http://")) {
|
||||
host = host.substring(7);
|
||||
} else if (host.startsWith("https://")) {
|
||||
host = host.substring(8);
|
||||
}
|
||||
var email = document.getElementById("user-email").value;
|
||||
var password = document.getElementById("user-password").value;
|
||||
requestLogin(host, email, password, this.loginCallback.bind(this));
|
||||
return false;
|
||||
}
|
||||
loginCallback(request, status, result) {
|
||||
if (status === 200) {
|
||||
CTX.setUser(result);
|
||||
requestHostConfig(host, this.hostConfigCallback.bind(this));
|
||||
requestTimezones(host, this.hostTimezonesCallback.bind(this));
|
||||
} else if (status === 404) {
|
||||
|
||||
}
|
||||
}
|
||||
// TODO: See promise for mutex https://developer.mozilla.org/fr/docs/Web/JavaScript/Guide/Using_promises
|
||||
hostConfigCallback(request, status, result) {
|
||||
if (status === 200) {
|
||||
this.hostConfig = HostConfig.formConfigData(result);
|
||||
if (this.timezonesData) {
|
||||
this.hostConfig.setTimezonesFromData(this.timezoneData);
|
||||
this.loginFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
hostTimezonesCallback(request, status, result) {
|
||||
if (status === 200) {
|
||||
if (this.hostConfig) {
|
||||
this.hostConfig.setTimezonesFromData(result);
|
||||
this.loginFinished();
|
||||
} else {
|
||||
this.timezonesData = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
loginFinished() {
|
||||
location.replace("index.html");
|
||||
}
|
||||
}
|
||||
const PAGE_INDEX = new PageIndex();
|
||||
26
src/pages/page_logout.js
Normal file
26
src/pages/page_logout.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
class PageLogout extends Page {
|
||||
boot() {
|
||||
var user = CTX.getUser();
|
||||
var thiss = this;
|
||||
requestLogout(user, function(request, status, response) {
|
||||
if (status === 200) {
|
||||
thiss.onLogout();
|
||||
} else {
|
||||
var root = document.getElementById("content")
|
||||
console.info(response);
|
||||
var msg = document.createElement("p");
|
||||
msg.innerHTML = `Erreur {{status}} : {{message}}`
|
||||
.replace("{{status}}", status)
|
||||
.replace("{{message}}", response);
|
||||
root.appendChild(msg);
|
||||
var homeBtn = new WidgetButtonNav("Accueil", "index.html");
|
||||
homeBtn.showIn(root);
|
||||
}
|
||||
});
|
||||
}
|
||||
onLogout() {
|
||||
CTX.setUser(null);
|
||||
location.replace("index.html");
|
||||
}
|
||||
}
|
||||
const PAGE_LOGOUT = new PageLogout();
|
||||
23
src/pages/page_user.js
Normal file
23
src/pages/page_user.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
class PageUserHome extends Page {
|
||||
boot() {
|
||||
super.boot();
|
||||
this.user = CTX.getUser();
|
||||
this.myGroups = this.user.memberships;
|
||||
var template = `
|
||||
<main id="user-content" class="l-section l-centered">
|
||||
<h2>{{My groups}}</h2>
|
||||
<ul class="l-nobullet l-centered"></menu>
|
||||
</main>
|
||||
`;
|
||||
this.root = document.getElementById("content")
|
||||
this.root.innerHTML = template
|
||||
.replace("{{My groups}}", t("My groups"));
|
||||
var groupList = this.root.getElementsByTagName("ul")[0];
|
||||
this.myGroups.forEach(m => {
|
||||
var group = m.group;
|
||||
var e = new WidgetGroupListItem(group, 3, m.role);
|
||||
e.showIn(groupList);
|
||||
});
|
||||
}
|
||||
}
|
||||
const PAGE_USER = new PageUserHome();
|
||||
185
src/request/req_event.js
Normal file
185
src/request/req_event.js
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
*/
|
||||
function requestEvent(host, uuid, callback) {
|
||||
var request = new XMLHttpRequest();
|
||||
var process = function () {
|
||||
if (request.readyState === XMLHttpRequest.DONE) {
|
||||
if (request.status === 200) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
data = data["data"];
|
||||
var event = Event.fromData(host, data["event"]);
|
||||
callback(request, request.status, event);
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, request.responseText);
|
||||
}
|
||||
};
|
||||
request.onreadystatechange = process;
|
||||
request.open("POST", "https://" + host + "/api/" + uuid);
|
||||
request.setRequestHeader("Content-type", "application/json");
|
||||
var content = {
|
||||
operationName:"FetchEvent",
|
||||
variables:{
|
||||
uuid: uuid
|
||||
},
|
||||
query: `query FetchEvent($uuid: UUID!) {
|
||||
event(uuid: $uuid) {
|
||||
...FullEvent
|
||||
__typename
|
||||
}
|
||||
}
|
||||
fragment FullEvent on Event {
|
||||
id
|
||||
uuid
|
||||
url
|
||||
local
|
||||
title
|
||||
description
|
||||
beginsOn
|
||||
endsOn
|
||||
status
|
||||
visibility
|
||||
joinOptions
|
||||
externalParticipationUrl
|
||||
draft
|
||||
language
|
||||
category
|
||||
picture {
|
||||
id
|
||||
url
|
||||
name
|
||||
metadata {
|
||||
width
|
||||
height
|
||||
blurhash
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
publishAt
|
||||
onlineAddress
|
||||
phoneAddress
|
||||
physicalAddress {
|
||||
...AdressFragment
|
||||
__typename
|
||||
}
|
||||
organizerActor {
|
||||
...ActorFragment
|
||||
__typename
|
||||
}
|
||||
contacts {
|
||||
...ActorFragment
|
||||
__typename
|
||||
}
|
||||
attributedTo {
|
||||
...ActorFragment
|
||||
__typename
|
||||
}
|
||||
participantStats {
|
||||
going
|
||||
notApproved
|
||||
participant
|
||||
__typename
|
||||
}
|
||||
tags {
|
||||
...TagFragment
|
||||
__typename
|
||||
}
|
||||
options {
|
||||
...EventOptions
|
||||
__typename
|
||||
}
|
||||
metadata {
|
||||
key
|
||||
title
|
||||
value
|
||||
type
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
|
||||
fragment AdressFragment on Address {
|
||||
id
|
||||
description
|
||||
geom
|
||||
street
|
||||
locality
|
||||
postalCode
|
||||
region
|
||||
country
|
||||
type
|
||||
url
|
||||
originId
|
||||
timezone
|
||||
pictureInfo {
|
||||
url
|
||||
author {
|
||||
name
|
||||
url
|
||||
__typename
|
||||
}
|
||||
source {
|
||||
name
|
||||
url
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
|
||||
fragment TagFragment on Tag {
|
||||
id
|
||||
slug
|
||||
title
|
||||
__typename
|
||||
}
|
||||
|
||||
fragment EventOptions on EventOptions {
|
||||
maximumAttendeeCapacity
|
||||
remainingAttendeeCapacity
|
||||
showRemainingAttendeeCapacity
|
||||
anonymousParticipation
|
||||
showStartTime
|
||||
showEndTime
|
||||
timezone
|
||||
offers {
|
||||
price
|
||||
priceCurrency
|
||||
url
|
||||
__typename
|
||||
}
|
||||
participationConditions {
|
||||
title
|
||||
content
|
||||
url
|
||||
__typename
|
||||
}
|
||||
attendees
|
||||
program
|
||||
commentModeration
|
||||
showParticipationPrice
|
||||
hideOrganizerWhenGroupEvent
|
||||
isOnline
|
||||
__typename
|
||||
}
|
||||
|
||||
fragment ActorFragment on Actor {
|
||||
id
|
||||
avatar {
|
||||
id
|
||||
url
|
||||
__typename
|
||||
}
|
||||
type
|
||||
preferredUsername
|
||||
name
|
||||
domain
|
||||
summary
|
||||
url
|
||||
__typename
|
||||
}`
|
||||
};
|
||||
request.send(JSON.stringify(content));
|
||||
}
|
||||
261
src/request/req_group.js
Normal file
261
src/request/req_group.js
Normal file
File diff suppressed because one or more lines are too long
226
src/request/req_host.js
Normal file
226
src/request/req_host.js
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
function requestHostConfig(host) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === XMLHttpRequest.DONE) {
|
||||
if (request.status === 200) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
if (data.errors) {
|
||||
var err = data.errors[0];
|
||||
if (err.status_code) {
|
||||
callback(request, err.status_code, err.message);
|
||||
return;
|
||||
} else {
|
||||
callback(request, 403, err.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
data = data["data"]["config"];
|
||||
if (!data) {
|
||||
console.error("No timezone data");
|
||||
callback(request, 500, "No timezone data");
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, data);
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, request.responseText);
|
||||
}
|
||||
};
|
||||
request.open("POST", "https://" + host + "/api");
|
||||
request.setRequestHeader("Content-type", "application/json");
|
||||
var content = {
|
||||
operationName:"FullConfig",
|
||||
variables:{},
|
||||
query: `query FullConfig {
|
||||
config {
|
||||
name
|
||||
description
|
||||
slogan
|
||||
version
|
||||
registrationsOpen
|
||||
registrationsAllowlist
|
||||
demoMode
|
||||
countryCode
|
||||
languages
|
||||
eventCategories {
|
||||
id
|
||||
label
|
||||
__typename
|
||||
}
|
||||
anonymous {
|
||||
participation {
|
||||
allowed
|
||||
validation {
|
||||
email {
|
||||
enabled
|
||||
confirmationRequired
|
||||
__typename
|
||||
}
|
||||
captcha {
|
||||
enabled
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
eventCreation {
|
||||
allowed
|
||||
validation {
|
||||
email {
|
||||
enabled
|
||||
confirmationRequired
|
||||
__typename
|
||||
}
|
||||
captcha {
|
||||
enabled
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
reports {
|
||||
allowed
|
||||
__typename
|
||||
}
|
||||
actorId
|
||||
__typename
|
||||
}
|
||||
location {
|
||||
latitude
|
||||
longitude
|
||||
__typename
|
||||
}
|
||||
maps {
|
||||
tiles {
|
||||
endpoint
|
||||
attribution
|
||||
__typename
|
||||
}
|
||||
routing {
|
||||
type
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
geocoding {
|
||||
provider
|
||||
autocomplete
|
||||
__typename
|
||||
}
|
||||
resourceProviders {
|
||||
type
|
||||
endpoint
|
||||
software
|
||||
__typename
|
||||
}
|
||||
features {
|
||||
groups
|
||||
eventCreation
|
||||
eventExternal
|
||||
antispam
|
||||
__typename
|
||||
}
|
||||
restrictions {
|
||||
onlyAdminCanCreateGroups
|
||||
onlyGroupsCanCreateEvents
|
||||
__typename
|
||||
}
|
||||
auth {
|
||||
ldap
|
||||
databaseLogin
|
||||
oauthProviders {
|
||||
id
|
||||
label
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
uploadLimits {
|
||||
default
|
||||
avatar
|
||||
banner
|
||||
__typename
|
||||
}
|
||||
instanceFeeds {
|
||||
enabled
|
||||
__typename
|
||||
}
|
||||
webPush {
|
||||
enabled
|
||||
publicKey
|
||||
__typename
|
||||
}
|
||||
analytics {
|
||||
id
|
||||
enabled
|
||||
configuration {
|
||||
key
|
||||
value
|
||||
type
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
search {
|
||||
global {
|
||||
isEnabled
|
||||
isDefault
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
exportFormats {
|
||||
eventParticipants
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
}`
|
||||
};
|
||||
request.send(JSON.stringify(content));
|
||||
}
|
||||
|
||||
function requestTimezones(host) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === XMLHttpRequest.DONE) {
|
||||
if (request.status === 200) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
if (data.errors) {
|
||||
var err = data.errors[0];
|
||||
if (err.status_code) {
|
||||
callback(request, err.status_code, err.message);
|
||||
return;
|
||||
} else {
|
||||
callback(request, 403, err.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
data = data["data"]["config"]["timezones"];
|
||||
if (!data) {
|
||||
console.error("No timezone data");
|
||||
callback(request, 500, "No timezone data");
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, data);
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, request.responseText);
|
||||
}
|
||||
};
|
||||
request.open("POST", "https://" + host + "/api");
|
||||
request.setRequestHeader("Content-type", "application/json");
|
||||
var content = {
|
||||
operationName:"Timezones",
|
||||
variables:{},
|
||||
query: `query Timezones {
|
||||
config {
|
||||
timezones
|
||||
__typename
|
||||
}
|
||||
}`
|
||||
};
|
||||
request.send(JSON.stringify(content));
|
||||
}
|
||||
197
src/request/req_login.js
Normal file
197
src/request/req_login.js
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
function requestLogin(host, email, password, callback) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === XMLHttpRequest.DONE) {
|
||||
if (request.status === 200) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
if (data.errors) {
|
||||
var err = data.errors[0];
|
||||
if (err.status_code) {
|
||||
callback(request, err.status_code, err.message);
|
||||
return;
|
||||
} else {
|
||||
callback(request, 403, err.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
data = data["data"];
|
||||
if (!data) {
|
||||
console.error("No login data");
|
||||
callback(request, 500, "No login data");
|
||||
return;
|
||||
}
|
||||
var user = User.fromData(host, data["login"]);
|
||||
requestLoggedMembership(host, user, callback);
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, request.responseText);
|
||||
}
|
||||
};
|
||||
request.open("POST", "https://" + host + "/api");
|
||||
request.setRequestHeader("Content-type", "application/json");
|
||||
var content = {
|
||||
operationName:"Login",
|
||||
variables:{
|
||||
email: email,
|
||||
password: password
|
||||
},
|
||||
query: `mutation Login($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password) {
|
||||
accessToken
|
||||
refreshToken
|
||||
user {
|
||||
id
|
||||
email
|
||||
role
|
||||
locale
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}}`
|
||||
};
|
||||
request.send(JSON.stringify(content));
|
||||
}
|
||||
|
||||
function requestLoggedMembership(host, loggedUser, callback) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === XMLHttpRequest.DONE) {
|
||||
if (request.status === 200) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
if (data.errors) {
|
||||
var err = data.errors[0];
|
||||
if (err.status_code) {
|
||||
callback(request, err.status_code, err.message);
|
||||
return;
|
||||
} else {
|
||||
callback(request, 400, err.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
data = data["data"];
|
||||
if (!data) {
|
||||
console.error("No membership data");
|
||||
callback(request, 500, "No membership data");
|
||||
return;
|
||||
}
|
||||
data = data["loggedUser"]["memberships"]["elements"];
|
||||
var memberships = [];
|
||||
data.forEach(d => {
|
||||
var m = Membership.fromData(host, d);
|
||||
if (m) {
|
||||
memberships.push(m);
|
||||
} else {
|
||||
console.error("Could not parse membership from " + d);
|
||||
}
|
||||
});
|
||||
loggedUser.memberships = memberships;
|
||||
callback(request, request.status, loggedUser);
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, request.responseText);
|
||||
}
|
||||
};
|
||||
request.open("POST", "https://" + host + "/api");
|
||||
request.setRequestHeader("Content-type", "application/json");
|
||||
request.setRequestHeader("authorization", "Bearer " + loggedUser.data.accessToken);
|
||||
var content = {
|
||||
operationName:"LoggedUserMemberships",
|
||||
variables:{},
|
||||
query: `query LoggedUserMemberships($membershipName: String) {
|
||||
loggedUser {
|
||||
id
|
||||
memberships(name: $membershipName) {
|
||||
total
|
||||
elements {
|
||||
id
|
||||
role
|
||||
actor {
|
||||
...ActorFragment
|
||||
__typename
|
||||
}
|
||||
parent {
|
||||
...ActorFragment
|
||||
organizedEvents {
|
||||
elements {
|
||||
id
|
||||
title
|
||||
picture {
|
||||
id
|
||||
url
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
total
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
invitedBy {
|
||||
...ActorFragment
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
}
|
||||
|
||||
fragment ActorFragment on Actor {
|
||||
id
|
||||
avatar {
|
||||
id
|
||||
url
|
||||
__typename
|
||||
}
|
||||
type
|
||||
preferredUsername
|
||||
name
|
||||
domain
|
||||
summary
|
||||
url
|
||||
__typename
|
||||
}`
|
||||
};
|
||||
request.send(JSON.stringify(content));
|
||||
}
|
||||
|
||||
function requestLogout(loggedUser, callback) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === XMLHttpRequest.DONE) {
|
||||
if (request.status === 200) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
if (data.errors) {
|
||||
var err = data.errors[0];
|
||||
if (err.status_code) {
|
||||
callback(request, err.status_code, err.message);
|
||||
return;
|
||||
} else {
|
||||
callback(request, 403, err.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
data = data["data"];
|
||||
callback(request, request.status, data);
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, request.responseText);
|
||||
}
|
||||
};
|
||||
request.open("POST", "https://" + loggedUser.host + "/api");
|
||||
request.setRequestHeader("Content-type", "application/json");
|
||||
request.setRequestHeader("authorization", "Bearer " + loggedUser.data.accessToken);
|
||||
var content = {
|
||||
operationName:"Logout",
|
||||
variables:{
|
||||
refreshToken: loggedUser.data.refreshToken
|
||||
},
|
||||
query: `mutation Logout($refreshToken: String!) {
|
||||
logout(refreshToken: $refreshToken)
|
||||
}`
|
||||
};
|
||||
request.send(JSON.stringify(content));
|
||||
}
|
||||
226
src/request/req_meta.js
Normal file
226
src/request/req_meta.js
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
function requestHostConfig(host) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === XMLHttpRequest.DONE) {
|
||||
if (request.status === 200) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
if (data.errors) {
|
||||
var err = data.errors[0];
|
||||
if (err.status_code) {
|
||||
callback(request, err.status_code, err.message);
|
||||
return;
|
||||
} else {
|
||||
callback(request, 403, err.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
data = data["data"]["config"];
|
||||
if (!data) {
|
||||
console.error("No timezone data");
|
||||
callback(request, 500, "No timezone data");
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, data);
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, request.responseText);
|
||||
}
|
||||
};
|
||||
request.open("POST", "https://" + host + "/api");
|
||||
request.setRequestHeader("Content-type", "application/json");
|
||||
var content = {
|
||||
operationName:"FullConfig",
|
||||
variables:{},
|
||||
query: `query FullConfig {
|
||||
config {
|
||||
name
|
||||
description
|
||||
slogan
|
||||
version
|
||||
registrationsOpen
|
||||
registrationsAllowlist
|
||||
demoMode
|
||||
countryCode
|
||||
languages
|
||||
eventCategories {
|
||||
id
|
||||
label
|
||||
__typename
|
||||
}
|
||||
anonymous {
|
||||
participation {
|
||||
allowed
|
||||
validation {
|
||||
email {
|
||||
enabled
|
||||
confirmationRequired
|
||||
__typename
|
||||
}
|
||||
captcha {
|
||||
enabled
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
eventCreation {
|
||||
allowed
|
||||
validation {
|
||||
email {
|
||||
enabled
|
||||
confirmationRequired
|
||||
__typename
|
||||
}
|
||||
captcha {
|
||||
enabled
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
reports {
|
||||
allowed
|
||||
__typename
|
||||
}
|
||||
actorId
|
||||
__typename
|
||||
}
|
||||
location {
|
||||
latitude
|
||||
longitude
|
||||
__typename
|
||||
}
|
||||
maps {
|
||||
tiles {
|
||||
endpoint
|
||||
attribution
|
||||
__typename
|
||||
}
|
||||
routing {
|
||||
type
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
geocoding {
|
||||
provider
|
||||
autocomplete
|
||||
__typename
|
||||
}
|
||||
resourceProviders {
|
||||
type
|
||||
endpoint
|
||||
software
|
||||
__typename
|
||||
}
|
||||
features {
|
||||
groups
|
||||
eventCreation
|
||||
eventExternal
|
||||
antispam
|
||||
__typename
|
||||
}
|
||||
restrictions {
|
||||
onlyAdminCanCreateGroups
|
||||
onlyGroupsCanCreateEvents
|
||||
__typename
|
||||
}
|
||||
auth {
|
||||
ldap
|
||||
databaseLogin
|
||||
oauthProviders {
|
||||
id
|
||||
label
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
uploadLimits {
|
||||
default
|
||||
avatar
|
||||
banner
|
||||
__typename
|
||||
}
|
||||
instanceFeeds {
|
||||
enabled
|
||||
__typename
|
||||
}
|
||||
webPush {
|
||||
enabled
|
||||
publicKey
|
||||
__typename
|
||||
}
|
||||
analytics {
|
||||
id
|
||||
enabled
|
||||
configuration {
|
||||
key
|
||||
value
|
||||
type
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
search {
|
||||
global {
|
||||
isEnabled
|
||||
isDefault
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
exportFormats {
|
||||
eventParticipants
|
||||
__typename
|
||||
}
|
||||
__typename
|
||||
}
|
||||
}`
|
||||
};
|
||||
request.send(JSON.stringify(content));
|
||||
}
|
||||
|
||||
function requestTimezones(host) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === XMLHttpRequest.DONE) {
|
||||
if (request.status === 200) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
if (data.errors) {
|
||||
var err = data.errors[0];
|
||||
if (err.status_code) {
|
||||
callback(request, err.status_code, err.message);
|
||||
return;
|
||||
} else {
|
||||
callback(request, 403, err.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
data = data["data"]["config"]["timezones"];
|
||||
if (!data) {
|
||||
console.error("No timezone data");
|
||||
callback(request, 500, "No timezone data");
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, data);
|
||||
return;
|
||||
}
|
||||
callback(request, request.status, request.responseText);
|
||||
}
|
||||
};
|
||||
request.open("POST", "https://" + host + "/api");
|
||||
request.setRequestHeader("Content-type", "application/json");
|
||||
var content = {
|
||||
operationName:"Timezones",
|
||||
variables:{},
|
||||
query: `query Timezones {
|
||||
config {
|
||||
timezones
|
||||
__typename
|
||||
}
|
||||
}`
|
||||
};
|
||||
request.send(JSON.stringify(content));
|
||||
}
|
||||
35
src/route.js
Normal file
35
src/route.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
class Page {
|
||||
boot() {
|
||||
new WidgetHeader(CTX).mergeInto(document.getElementById("header"));
|
||||
}
|
||||
}
|
||||
|
||||
function route() {
|
||||
var page = GET("page");
|
||||
if (!page) {
|
||||
page = "index";
|
||||
}
|
||||
if (page === "index") {
|
||||
if (CTX.getUser()) {
|
||||
CTX.setPage(PAGE_USER);
|
||||
} else {
|
||||
CTX.setPage(PAGE_INDEX);
|
||||
}
|
||||
} else if (page === "group") {
|
||||
CTX.setPage(PAGE_GROUP_PUBLIC);
|
||||
} else if (page === "event") {
|
||||
CTX.setPage(PAGE_EVENT_DETAIL);
|
||||
} else if (page === "add-event") {
|
||||
CTX.setPage(PAGE_EVENT_EDIT);
|
||||
} else if (page === "edit-event") {
|
||||
CTX.setPage(PAGE_EVENT_EDIT);
|
||||
} else if (page === "logout") {
|
||||
CTX.setPage(PAGE_LOGOUT);
|
||||
} else {
|
||||
if (CTX.getUser()) {
|
||||
CTX.setPage(PAGE_USER);
|
||||
} else {
|
||||
CTX.setPage(PAGE_INDEX);
|
||||
}
|
||||
}
|
||||
}
|
||||
214
src/widgets/base_view.js
Normal file
214
src/widgets/base_view.js
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/* Basaj fenestraĵoj per multaj paĝoj */
|
||||
|
||||
/**
|
||||
* La komona paĝokapo met navigadon, ensaluto kay elsaluto.
|
||||
*/
|
||||
class WidgetHeader extends Widget {
|
||||
constructor(ctx) {
|
||||
super();
|
||||
this.ctx = ctx;
|
||||
this.root = document.createElement("nav");
|
||||
this.root.classList.add("header");
|
||||
var template = `
|
||||
<menu><a href="index.html">{{Home}}</a>
|
||||
</menu>
|
||||
<menu><a href="index.html?page=logout">{{Log out}}</a></menu>
|
||||
`
|
||||
this.root.innerHTML = template
|
||||
.replace("{{Home}}", t("Home"))
|
||||
.replace("{{Log out}}", t("Log out"));
|
||||
if (!ctx.getUser()) {
|
||||
this.root.getElementsByTagName("menu")[1].remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* La komona paĝopiedo.
|
||||
*/
|
||||
class WidgetFooter extends Widget {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fenestraĵo por enmeti la pasvorton.
|
||||
*/
|
||||
class WidgetInputPassword extends Widget {
|
||||
constructor(id, name) {
|
||||
super();
|
||||
this.root = document.createElement("span");
|
||||
this.root.classList.add("input-like");
|
||||
this.input = document.createElement("input");
|
||||
this.input.classList.add("input-withicon");
|
||||
this.input.setAttribute("type", "password");
|
||||
this.input.setAttribute("id", id);
|
||||
this.input.setAttribute("name", name);
|
||||
this.root.appendChild(this.input);
|
||||
var toggleContainer = document.createElement("span");
|
||||
toggleContainer.classList.add("input-icon");
|
||||
this.root.appendChild(toggleContainer);
|
||||
this.toggle = document.createElement("button");
|
||||
this.toggle.setAttribute("type", "button");
|
||||
this.toggle.classList.add("btn");
|
||||
this.toggle.addEventListener("click", this.toggleVisibility.bind(this), false);
|
||||
this.toggle.innerHTML = `<svg fill="currentColor" width="18" height="18" viewBox="0 0 24 24">
|
||||
<path d="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z">
|
||||
</path></svg>`;
|
||||
toggleContainer.appendChild(this.toggle);
|
||||
}
|
||||
|
||||
toggleVisibility() {
|
||||
if (this.input.getAttribute("type") === "password") {
|
||||
this.input.setAttribute("type", "text");
|
||||
} else {
|
||||
this.input.setAttribute("type", "password");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fenestraĵo por enmeti tekston.
|
||||
*/
|
||||
class WidgetFormInputText extends Widget {
|
||||
constructor(id, name, label) {
|
||||
super();
|
||||
this.root = document.createElement("div");
|
||||
this.root.classList.add("form-field");
|
||||
this.label = document.createElement("label");
|
||||
this.label.setAttribute("for", id);
|
||||
this.label.innerHTML = label;
|
||||
this.root.appendChild(this.label);
|
||||
this.input = document.createElement("input");
|
||||
this.input.setAttribute("type", "text");
|
||||
this.input.setAttribute("id", id);
|
||||
this.input.setAttribute("name", name);
|
||||
this.root.appendChild(this.input);
|
||||
}
|
||||
getValue() {
|
||||
return this.input.value;
|
||||
}
|
||||
setValue(value) {
|
||||
this.input.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fenestraĵo por enmeti longan texton.
|
||||
*/
|
||||
class WidgetFormTextArea extends Widget {
|
||||
constructor(id, name, label) {
|
||||
super();
|
||||
this.root = document.createElement("div");
|
||||
this.root.classList.add("form-field");
|
||||
this.label = document.createElement("label");
|
||||
this.label.setAttribute("for", id);
|
||||
this.label.innerHTML = label;
|
||||
this.root.appendChild(this.label);
|
||||
this.input = document.createElement("textarea");
|
||||
this.input.setAttribute("rows", 10);
|
||||
this.input.setAttribute("id", id);
|
||||
this.input.setAttribute("name", name);
|
||||
this.root.appendChild(this.input);
|
||||
}
|
||||
getValue() {
|
||||
return this.input.value;
|
||||
}
|
||||
setValue(value) {
|
||||
this.input.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fenestraĵo por elekti unu valoron el multaj valoron.
|
||||
*/
|
||||
class WidgetFormSelect extends Widget {
|
||||
constructor(id, name, label, options) {
|
||||
super();
|
||||
this.root = document.createElement("div");
|
||||
this.root.classList.add("form-field");
|
||||
this.label = document.createElement("label");
|
||||
this.label.setAttribute("for", id);
|
||||
this.label.innerHTML = label;
|
||||
this.root.appendChild(this.label);
|
||||
this.select = document.createElement("select");
|
||||
this.select.setAttribute("id", id);
|
||||
this.select.setAttribute("name", name);
|
||||
options.forEach(o => {
|
||||
var option = document.createElement("option");
|
||||
option.setAttribute("value", o.value);
|
||||
option.innerHTML = o.text;
|
||||
this.select.appendChild(option);
|
||||
});
|
||||
this.root.appendChild(this.select);
|
||||
}
|
||||
getValue() {
|
||||
return this.select.value;
|
||||
}
|
||||
setValue(value) {
|
||||
this.select.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class WidgetFormCheckbox extends Widget {
|
||||
constructor(id, name, label) {
|
||||
super();
|
||||
this.root = document.createElement("div");
|
||||
this.root.classList.add("form-field", "form-field-checkbox");
|
||||
this.input = document.createElement("input");
|
||||
this.input.setAttribute("type", "checkbox");
|
||||
this.input.setAttribute("id", id);
|
||||
this.input.setAttribute("name", name);
|
||||
this.root.appendChild(this.input);
|
||||
this.label = document.createElement("label");
|
||||
this.label.setAttribute("for", id);
|
||||
this.label.innerHTML = label;
|
||||
this.root.appendChild(this.label);
|
||||
}
|
||||
getValue() {
|
||||
return this.input.checked;
|
||||
}
|
||||
setValue(value) {
|
||||
this.input.checked = value;
|
||||
}
|
||||
}
|
||||
|
||||
class WidgetFormDateTime extends Widget {
|
||||
constructor(id, name, label) {
|
||||
super();
|
||||
this.root = document.createElement("div");
|
||||
this.root.classList.add("form-field");
|
||||
this.label = document.createElement("label");
|
||||
this.label.setAttribute("for", id);
|
||||
this.label.innerHTML = label;
|
||||
this.root.appendChild(this.label);
|
||||
this.input = document.createElement("input");
|
||||
this.input.setAttribute("type", "datetime-local");
|
||||
this.input.setAttribute("id", id);
|
||||
this.input.setAttribute("name", name);
|
||||
this.root.appendChild(this.input);
|
||||
}
|
||||
getValue() {
|
||||
return this.input.value;
|
||||
}
|
||||
setValue(value) {
|
||||
this.input.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class WidgetFormImage extends Widget {
|
||||
constructor(id, name, label) {
|
||||
super();
|
||||
this.root = document.createElement("div");
|
||||
this.root.classList.add("form-field");
|
||||
this.label = document.createElement("label");
|
||||
this.label.setAttribute("for", id);
|
||||
this.label.innerHTML = label;
|
||||
this.root.appendChild(this.label);
|
||||
this.input = document.createElement("input");
|
||||
this.input.setAttribute("type", "file");
|
||||
this.input.setAttribute("accept", "image/gif,image/png,image/jpeg,image/webp");
|
||||
this.input.setAttribute("id", id);
|
||||
this.input.setAttribute("name", name);
|
||||
this.root.appendChild(this.input);
|
||||
}
|
||||
}
|
||||
9
src/widgets/button_view.js
Normal file
9
src/widgets/button_view.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
class WidgetButtonNav extends Widget {
|
||||
constructor(name, url) {
|
||||
super();
|
||||
this.root = document.createElement("a");
|
||||
this.root.classList.add("btn", "btn-nav");
|
||||
this.root.setAttribute("href", url);
|
||||
this.root.innerHTML = name;
|
||||
}
|
||||
}
|
||||
50
src/widgets/date_view.js
Normal file
50
src/widgets/date_view.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
function date_fullString(locale, date) {
|
||||
var root = document.createElement("time");
|
||||
root.setAttribute("datetime", date.toISOString());
|
||||
root.innerHTML = date.toLocaleDateString(locale, {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric"
|
||||
});
|
||||
return root;
|
||||
}
|
||||
|
||||
function date_shortString(locale, date) {
|
||||
return date.toLocaleDateString(locale, {
|
||||
weekday: "short",
|
||||
month: "short",
|
||||
day: "numeric"
|
||||
});
|
||||
}
|
||||
|
||||
function date_shortIcon(locale, dateStart, dateEnd = null) {
|
||||
var root = document.createElement("time");
|
||||
root.classList.add("date-icon", "date-icon-short");
|
||||
root.setAttribute("datetime", dateStart.getFullYear() + "-" + (dateStart.getMonth() + 1) + "-" + dateStart.getDate());
|
||||
root.innerHTML = "<span>{{day}}</span><span class=\"date-icon-date\">{{dateOfMonth}}</span><span>{{month}}</span>"
|
||||
.replace("{{day}}", dateStart.toLocaleDateString(locale, {weekday: "short"}))
|
||||
.replace("{{dateOfMonth}}", dateStart.toLocaleDateString(locale, {day: "numeric"}))
|
||||
.replace("{{month}}", dateStart.toLocaleDateString(locale, {month: "short"}));
|
||||
return root;
|
||||
}
|
||||
|
||||
function date_fullIcon(locale, dateStart, dateEnd) {
|
||||
var root = document.createElement("time");
|
||||
root.classList.add("date-icon", "date-icon-full");
|
||||
if (dateStart) {
|
||||
root.setAttribute("datetime", dateStart.toISOString());
|
||||
}
|
||||
root.innerHTML = `<div>
|
||||
<span>{{day}}</span>
|
||||
<span class=\"date-icon-date\">{{dateOfMonth}}</span>
|
||||
<span>{{month}}</span>
|
||||
</div>
|
||||
<div><span class=\"date-icon-time\">{{time}}</span></div>`
|
||||
.replace("{{day}}", dateStart ? dateStart.toLocaleDateString(locale, {weekday: "short"}) : "")
|
||||
.replace("{{dateOfMonth}}", dateStart ? dateStart.toLocaleDateString(locale, {day: "numeric"}) : "")
|
||||
.replace("{{month}}", dateStart ? dateStart.toLocaleDateString(locale, {month: "short"}) : "")
|
||||
.replace("{{time}}", dateStart ? dateStart.toLocaleTimeString(locale, {hour: "numeric", minute: "2-digit"}) : "");
|
||||
return root;
|
||||
}
|
||||
259
src/widgets/event_view.js
Normal file
259
src/widgets/event_view.js
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
/**
|
||||
* Fenestraĵo por eventkapo met rubando.
|
||||
*/
|
||||
class WidgetEventBanner extends Widget {
|
||||
constructor(event) {
|
||||
super();
|
||||
this.event = event;
|
||||
this.root = document.createElement("header");
|
||||
this.root.classList.add("event-banner");
|
||||
var template = `
|
||||
<img src="{{banner-url}}" alt="{{Banner}}">
|
||||
`;
|
||||
this.root.innerHTML = template
|
||||
.replace("{{banner-url}}", escAttr(this.event.getPictureUrl()))
|
||||
.replace("{{Banner}}", t("Banner"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fenestraĵo por eventinformoj: datumoj, loko, etikedoj...
|
||||
*/
|
||||
class WidgetEventDetail extends Widget {
|
||||
constructor(event) {
|
||||
super();
|
||||
this.event = event;
|
||||
this.root = document.createElement("article");
|
||||
this.root.classList.add("event-detail");
|
||||
var template = `<header>
|
||||
<div class=\"l-flexcenter\" data-field="beginsOn"></div>
|
||||
<h1 class="event-detail-title" data-field="title"></h1>
|
||||
</header>
|
||||
<section class="event-detail-info">
|
||||
<div data-field="tags"></div>
|
||||
<div data-field="description"></div>
|
||||
</section>
|
||||
<aside class="event-detail-meta">
|
||||
<h2>Infos pratiques</h2>
|
||||
</aside>
|
||||
`;
|
||||
this.root.innerHTML = template
|
||||
.replace("{{date}}", date_fullIcon(undefined, this.event.getBeginDate()).outerHTML);
|
||||
this.beginsOn = this.root.querySelector("[data-field=\"beginsOn\"]");
|
||||
this.title = this.root.querySelector("[data-field=\"title\"]");
|
||||
this.description = this.root.querySelector("[data-field=\"description\"]");
|
||||
this.tags = this.root.querySelector("[data-field=\"tags\"]");
|
||||
var aside = this.root.getElementsByTagName("aside")[0];
|
||||
var asideUsed = false;
|
||||
if (this.event.data.attributedTo) {
|
||||
var elem = document.createElement("div");
|
||||
var groupUrl = getGroupUrl(getHandle(this.event.host, this.event.data.attributedTo.preferredUsername));
|
||||
elem.innerHTML = t("Organized by {name}", {name: '<a href="' + escAttr(groupUrl) + '">' + this.event.data.attributedTo.name + '</a>'});
|
||||
aside.appendChild(elem);
|
||||
var asideUsed = true;
|
||||
}
|
||||
if (this.event.data.physicalAddress) {
|
||||
var elem = document.createElement("p");
|
||||
elem.classList.add("event-detail-location");
|
||||
elem.innerHTML = this.event.getPhysicalAddressString();
|
||||
aside.appendChild(elem);
|
||||
var asideUsed = true;
|
||||
}
|
||||
if (this.event.data.onlineAddress) {
|
||||
var elem = document.createElement("p");
|
||||
elem.classList.add("event-detail-website");
|
||||
elem.innerHTML = `{{Website}} <a href="{{website-href}}">{{website}}</a>`
|
||||
.replace("{{website-href}}", escAttr(this.event.data.onlineAddress))
|
||||
.replace("{{Website}}", t("Website"))
|
||||
.replace("{{website}}", this.event.data.onlineAddress);
|
||||
aside.appendChild(elem);
|
||||
var asideUsed = true;
|
||||
}
|
||||
if (this.event.getTicketUri()) {
|
||||
var elem = document.createElement("p");
|
||||
elem.classList.add("event-detail-ticket");
|
||||
elem.innerHTML = `{{Ticket}} <a href="{{ticket-href}}">{{ticket}}</a>`
|
||||
.replace("{{ticket-href}}", escAttr(this.event.getTicketUri()))
|
||||
.replace("{{Ticket}}", t("Online ticketing"))
|
||||
.replace("{{ticket}}", this.event.getTicketUri());
|
||||
aside.appendChild(elem);
|
||||
var asideUsed = true;
|
||||
}
|
||||
if (!asideUsed) {
|
||||
aside.remove();
|
||||
}
|
||||
this.update("beginsOn");
|
||||
this.update("title");
|
||||
this.update("description");
|
||||
this.update("tags");
|
||||
}
|
||||
update(field) {
|
||||
if (field === "title") {
|
||||
if (this.event.data?.title) {
|
||||
this.title.innerHTML = this.event.data.title;
|
||||
} else {
|
||||
this.title.innerHTML = "<" + t("Title") + ">"
|
||||
}
|
||||
} else if (field === "description") {
|
||||
let desc = this.event.getDescription();
|
||||
if (desc) {
|
||||
this.description.innerHTML = desc;
|
||||
} else {
|
||||
this.description.innerHTML = "";
|
||||
}
|
||||
} else if (field === "tags") {
|
||||
if (this.event.data?.tags?.length > 0) {
|
||||
this.tags.innerHTML = tag_list(this.event.data.tags);
|
||||
} else {
|
||||
this.tags.innerHTML = "";
|
||||
}
|
||||
} else if (field === "beginsOn") {
|
||||
if (this.event.data?.beginsOn && this.event.data?.showBeginsOn) {
|
||||
replaceContent(this.beginsOn, date_fullIcon(undefined, this.event.getBeginDate()));
|
||||
} else {
|
||||
this.beginsOn.innerHTML = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fenestraĵo por eventresumo destinita por montri multajn eventojn.
|
||||
*/
|
||||
class WidgetEventListItem extends Widget {
|
||||
constructor(event, titleLevel, options= {}) {
|
||||
super();
|
||||
options = Object.assign({linkToInstance: false, descriptionSize: 250}, options);
|
||||
this.event = event;
|
||||
this.root = document.createElement("li");
|
||||
this.root.classList.add("event-listitem");
|
||||
var template = `<div class="event-listitem-banner">
|
||||
<img src="{{banner-url}}" alt="{{Banner}}">
|
||||
</div>
|
||||
<div class="event-listitem-content">
|
||||
<header>
|
||||
{{date}}
|
||||
<div>
|
||||
<h{{level}}>{{title}}</h{{level}}>
|
||||
{{taglist}}
|
||||
</div>
|
||||
</header>
|
||||
<p>{{location}}</p>
|
||||
<div>{{short-description}}</div>
|
||||
<nav>{{button-more}}</nav>
|
||||
</div>
|
||||
`;
|
||||
var moreUrl = options.linkToInstance ? this.event.getOnInstanceUrl() : getEventUrl(event.host, event.data.uuid);
|
||||
var more = new WidgetButtonNav(t("View more"), moreUrl);
|
||||
this.root.innerHTML = template
|
||||
.replaceAll("{{level}}", titleLevel)
|
||||
.replace("{{title}}", this.event.data.title)
|
||||
.replace("{{date}}", date_shortIcon(undefined, this.event.getBeginDate()).outerHTML)
|
||||
.replace("{{location}}", this.event.getPhysicalAddressString())
|
||||
.replace("{{short-description}}", this.event.getDescription(options.descriptionSize))
|
||||
.replace("{{Banner}}", ta("Banner"))
|
||||
.replace("{{banner-url}}", escAttr(this.event.getPictureUrl()))
|
||||
.replace("{{taglist}}", tag_list(this.event.data.tags))
|
||||
.replace("{{button-more}}", more.root.outerHTML);
|
||||
}
|
||||
}
|
||||
|
||||
class WidgetEventForm extends Widget {
|
||||
constructor(event) {
|
||||
super();
|
||||
this.event = event;
|
||||
this.root = document.createElement("form");
|
||||
var template = `
|
||||
<fieldset>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
</fieldset>
|
||||
`;
|
||||
this.root.innerHTML = template;
|
||||
var field1 = this.root.getElementsByTagName("fieldset")[0];
|
||||
this.banner = new WidgetFormImage("edit-banner", "banner", t("Banner"))
|
||||
this.banner.showIn(field1);
|
||||
this.title = new WidgetFormInputText("edit-title", "title", t("Title"));
|
||||
this.title.input.addEventListener("change", this.#titleChanged.bind(this));
|
||||
this.title.showIn(field1);
|
||||
|
||||
var short = document.createElement("div");
|
||||
short.classList.add("l-flexjustify");
|
||||
this.category = new WidgetFormSelect("edit-category", "category", t("Category"), []);
|
||||
this.category.root.classList.add("l-flexfill", "form-field-short");
|
||||
this.category.showIn(short);
|
||||
this.tags = new WidgetFormInputText("edit-tags", "tags", t("Add some tags") + " <span class=\"notice\">" + t("(without #, separated by a space, 10 max)") + "</span>");
|
||||
this.tags.root.classList.add("l-flexfill", "form-field-short");
|
||||
this.tags.input.addEventListener("change", this.#tagsChanged.bind(this));
|
||||
this.tags.showIn(short);
|
||||
field1.appendChild(short);
|
||||
|
||||
this.description = new WidgetFormTextArea("edit-description", "description", t("Description"));
|
||||
this.description.input.addEventListener("change", this.#descriptionChanged.bind(this));
|
||||
this.description.showIn(field1);
|
||||
|
||||
this.timezone = new WidgetFormSelect("edit-timezone", "timezone", t("Timezone"), []);
|
||||
this.timezone.showIn(this.root.getElementsByTagName("fieldset")[1]);
|
||||
this.beginsOn = new WidgetFormDateTime("edit-begins-on", "begins-on", t("Begins on"));
|
||||
this.beginsOn.input.addEventListener("change", this.#beginsOnChanged.bind(this));
|
||||
this.beginsOn.showIn(this.root.getElementsByTagName("fieldset")[1]);
|
||||
this.showBeginsOn = new WidgetFormCheckbox("edit-show-begins-on", "show-begins-on", t("Show the time when the event begins"));
|
||||
this.showBeginsOn.setValue(true);
|
||||
this.showBeginsOn.input.addEventListener("change", this.#showBeginsOnChanged.bind(this));
|
||||
this.showBeginsOn.showIn(this.root.getElementsByTagName("fieldset")[1]);
|
||||
this.endsOn = new WidgetFormDateTime("edit-ends-on", "ends-on", t("Ends on…"));
|
||||
this.endsOn.showIn(this.root.getElementsByTagName("fieldset")[1]);
|
||||
this.showEndsOn = new WidgetFormCheckbox("edit-show-ends-on", "show-ends-on", t("Show the time when the event ends"));
|
||||
this.showEndsOn.showIn(this.root.getElementsByTagName("fieldset")[1]);
|
||||
|
||||
this.url = new WidgetFormInputText("edit-url", "url", t("Website / URL"));
|
||||
this.url.showIn(this.root.getElementsByTagName("fieldset")[2]);
|
||||
this.ticketing = new WidgetFormInputText("edit-ticketing", "ticketing", t("Online ticketing"));
|
||||
this.ticketing.showIn(this.root.getElementsByTagName("fieldset")[2]);
|
||||
|
||||
this.visibility = new WidgetFormSelect("edit-visibility", "visibility", t("Who can view this event and participate"), [
|
||||
{value: "PUBLIC", text: t("Visible everywhere on the web (public)")},
|
||||
{value: "UNLISTED", text: t("Only accessible through link (private)")}
|
||||
]);
|
||||
this.visibility.showIn(this.root.getElementsByTagName("fieldset")[3]);
|
||||
this.status = new WidgetFormSelect("edit-status", "status", t("Event status"), [
|
||||
{value: "", text: t("Tentative: Will be confirmed later")},
|
||||
{value: "", text: t("Confirmed: Will happen")},
|
||||
{value: "", text: t("Cancelled: Won't happen")},
|
||||
]);
|
||||
this.status.showIn(this.root.getElementsByTagName("fieldset")[3]);
|
||||
}
|
||||
|
||||
createPreview() {
|
||||
this.preview = new WidgetEventDetail(this.event);
|
||||
return this.preview;
|
||||
}
|
||||
#titleChanged(event) {
|
||||
this.event.data.title = event.target.value;
|
||||
this.preview?.update("title");
|
||||
}
|
||||
#descriptionChanged(event) {
|
||||
this.event.data.description = event.target.value;
|
||||
this.preview?.update("description");
|
||||
}
|
||||
#tagsChanged(event) {
|
||||
var tags = event.target.value.split(" ").filter(tag => tag.trim() !== "");
|
||||
tags = tags.map(t => { return {slug: t, title: t}});
|
||||
this.event.data.tags = tags;
|
||||
this.preview?.update("tags");
|
||||
}
|
||||
#beginsOnChanged(event) {
|
||||
var start = event.target.value;
|
||||
console.info(start);
|
||||
this.event.data.beginsOn = start;
|
||||
this.preview?.update("beginsOn");
|
||||
}
|
||||
#showBeginsOnChanged(event) {
|
||||
this.event.data.showBeginsOn = event.target.checked;
|
||||
this.preview?.update("beginsOn");
|
||||
}
|
||||
}
|
||||
100
src/widgets/group_view.js
Normal file
100
src/widgets/group_view.js
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* Fenestraĵo por groupkapo met rubando kaj generalaj informoj, kiel loko kaj uroloj.
|
||||
*/
|
||||
class WidgetGroupBanner extends Widget {
|
||||
constructor(group) {
|
||||
super();
|
||||
this.group = group;
|
||||
this.root = document.createElement("header");
|
||||
this.root.classList.add("group-banner");
|
||||
var template = `
|
||||
<img class="group-banner-background" src="{{banner-url}}" alt="{{Banner}}">
|
||||
<div>
|
||||
<img class="avatar group-banner-avatar" src="{{avatar-url}}" alt="{{Avatar}}">
|
||||
<h1>{{name}}</h1>
|
||||
<address class="group-banner-handle"><a href="{{instance-url}}" title="{{instance-url-title}}">{{handle}}</a></address>
|
||||
<address class="group-banner-location">{{location}}</address>
|
||||
</div>
|
||||
<menu>{{ics-url}} {{atom-url}}</menu>
|
||||
`;
|
||||
var icsButton = new WidgetButtonNav(t("ICS/WebCal Feed"), this.group.getIcsUrl());
|
||||
var atomButton = new WidgetButtonNav(t("RSS/Atom Feed"), this.group.getAtomUrl());
|
||||
this.root.innerHTML = template
|
||||
.replace("{{name}}", this.group.data.name)
|
||||
.replace("{{handle}}", this.group.getHandle())
|
||||
.replace("{{instance-url}}", this.group.data.url)
|
||||
.replace("{{instance-url-title}}", t("Voir sur l'instance d'origine"))
|
||||
.replace("{{location}}", this.group.getLocationString())
|
||||
.replace("{{Banner}}", ta("Banner"))
|
||||
.replace("{{banner-url}}", this.group.getBannerUrl())
|
||||
.replace("{{Avatar}}", ta("Avatar"))
|
||||
.replace("{{avatar-url}}", this.group.getAvatarUrl())
|
||||
.replace("{{ics-url}}", icsButton.root.outerHTML)
|
||||
.replace("{{atom-url}}", atomButton.root.outerHTML);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fenestraĵo por groupinformoj, kiel priskribo kaj sekvaj eventojloko.
|
||||
*/
|
||||
class WidgetGroupDetail extends Widget {
|
||||
constructor(group, role) {
|
||||
super();
|
||||
this.group = group;
|
||||
this.role = role;
|
||||
this.root = document.createElement("section");
|
||||
this.root.classList.add("group-detail");
|
||||
var template = `
|
||||
<h2>{{about}}</h2>
|
||||
<p>{{summary}}</p>
|
||||
<h2>{{upcoming events}}</h2>
|
||||
<nav class="group-detail-events"></nav>
|
||||
<ul class="l-nobullet l-centering"></ul>
|
||||
`;
|
||||
this.root.innerHTML = template
|
||||
.replace("{{about}}", t("About"))
|
||||
.replace("{{upcoming events}}", t("Upcoming events"))
|
||||
.replace("{{summary}}", this.group.data.summary);
|
||||
var events = [];
|
||||
var eventList = this.root.getElementsByTagName("ul")[0];
|
||||
this.group.getEvents().forEach(evt => {
|
||||
var evtItem = new WidgetEventListItem(evt, 3);
|
||||
evtItem.showIn(eventList);
|
||||
});
|
||||
if (this.group.canManageEvents(this.role)) {
|
||||
var btnAddEvent = new WidgetButtonNav(t("+ Create an event"), getAddEventUrl(this.group));
|
||||
btnAddEvent.showIn(this.root.getElementsByClassName("group-detail-events")[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fenestraĵo por grupresumo destinita por montri multajn grupojn.
|
||||
*/
|
||||
class WidgetGroupListItem extends Widget {
|
||||
constructor(group, titleLevel, role) {
|
||||
super();
|
||||
this.group = group;
|
||||
this.root = document.createElement("li");
|
||||
this.root.classList.add("group-listitem");
|
||||
var template = `
|
||||
<img class="avatar group-listitem-avatar" src="{{avatar-url}}" alt="{{Avatar}}">
|
||||
<div class="group-listitem-content">
|
||||
<h{{level}}>{{name}}</h{{level}}>
|
||||
<address class="group-listitem-handle">{{handle}}</address>
|
||||
<div class="group-listitem-role">{{role}}</div>
|
||||
</div>
|
||||
<nav>{{button-more}}</nav>
|
||||
`;
|
||||
var groupHandle = getHandle(this.group.host, this.group.getUsername())
|
||||
var more = new WidgetButtonNav(t("View more"), getGroupUrl(groupHandle));
|
||||
this.root.innerHTML = template
|
||||
.replaceAll("{{level}}", titleLevel)
|
||||
.replace("{{name}}", this.group.data.name)
|
||||
.replace("{{handle}}", groupHandle)
|
||||
.replace("{{role}}", trole(role))
|
||||
.replace("{{Avatar}}", ta("Avatar"))
|
||||
.replace("{{avatar-url}}", escAttr(this.group.getAvatarUrl()))
|
||||
.replace("{{button-more}}", more.root.outerHTML);
|
||||
}
|
||||
}
|
||||
9
src/widgets/tag_tpl.js
Normal file
9
src/widgets/tag_tpl.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
function tag_list(tags) {
|
||||
var list = "<ul class=\"l-nobullet taglist\">";
|
||||
tags.forEach(t => {
|
||||
list += "<li class=\"tag\">{{tag}}</li>".replace("{{tag}}", t.title);
|
||||
});
|
||||
list += "</ul>";
|
||||
return list;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue