迁移回来了
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
image:
|
||||
style:
|
||||
background: "#2a9d8f"
|
||||
color: "#fff"
|
||||
---
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
description:
|
||||
date: {{ .Date }}
|
||||
image:
|
||||
math:
|
||||
license:
|
||||
hidden: false
|
||||
comments: true
|
||||
draft: true
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
description:
|
||||
image:
|
||||
---
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-archive" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<rect x="3" y="4" width="18" height="4" rx="2" />
|
||||
<path d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10" />
|
||||
<line x1="10" y1="12" x2="14" y2="12" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 432 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-back" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M9 11l-4 4l4 4m-4 -4h11a4 4 0 0 0 0 -8h-1" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 338 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<polyline points="15 6 9 12 15 18" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 323 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-github" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 603 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-twitter" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c-.002 -.249 1.51 -2.772 1.818 -4.013z" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 638 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<line x1="5" y1="9" x2="19" y2="9" />
|
||||
<line x1="5" y1="15" x2="19" y2="15" />
|
||||
<line x1="11" y1="4" x2="7" y2="20" />
|
||||
<line x1="17" y1="4" x2="13" y2="20" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 440 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clock" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<polyline points="12 7 12 12 15 15" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 352 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copyright" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M14.5 9a3.5 4 0 1 0 0 6" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 354 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-calendar-time" width="56" height="56" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M11.795 21h-6.795a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v4" />
|
||||
<circle cx="18" cy="18" r="4" />
|
||||
<path d="M15 3v4" />
|
||||
<path d="M7 3v4" />
|
||||
<path d="M3 11h16" />
|
||||
<path d="M18 16.496v1.504l1 1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 508 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<line x1="5" y1="9" x2="19" y2="9" />
|
||||
<line x1="5" y1="15" x2="19" y2="15" />
|
||||
<line x1="11" y1="4" x2="7" y2="20" />
|
||||
<line x1="17" y1="4" x2="13" y2="20" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 440 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-home" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<polyline points="5 12 3 12 12 3 21 12 19 12" />
|
||||
<path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" />
|
||||
<path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 441 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-infinity" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M9.828 9.172a4 4 0 1 0 0 5.656 a10 10 0 0 0 2.172 -2.828a10 10 0 0 1 2.172 -2.828 a4 4 0 1 1 0 5.656a10 10 0 0 1 -2.172 -2.828a10 10 0 0 0 -2.172 -2.828" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 447 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-language" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 5h7" />
|
||||
<path d="M9 3v2c0 4.418 -2.239 8 -5 8" />
|
||||
<path d="M5 9c-.003 2.144 2.952 3.908 6.7 4" />
|
||||
<path d="M12 20l4 -9l4 9" />
|
||||
<path d="M19.1 18h-6.2" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 467 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5" />
|
||||
<path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 418 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-messages" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M21 14l-3 -3h-7a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1h9a1 1 0 0 1 1 1v10" />
|
||||
<path d="M14 15v2a1 1 0 0 1 -1 1h-7l-3 3v-10a1 1 0 0 1 1 -1h2" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 431 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-rss" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="5" cy="19" r="1" />
|
||||
<path d="M4 4a16 16 0 0 1 16 16" />
|
||||
<path d="M4 11a9 9 0 0 1 9 9" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 381 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="10" cy="10" r="7" />
|
||||
<line x1="21" y1="21" x2="15" y2="15" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 355 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-tag" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M11 3L20 12a1.5 1.5 0 0 1 0 2L14 20a1.5 1.5 0 0 1 -2 0L3 11v-4a4 4 0 0 1 4 -4h4" />
|
||||
<circle cx="9" cy="9" r="2" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 402 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-toggle-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="8" cy="12" r="2" />
|
||||
<rect x="2" y="6" width="20" height="12" rx="6" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 369 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-toggle-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="16" cy="12" r="2" />
|
||||
<rect x="2" y="6" width="20" height="12" rx="6" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 371 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
<path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" />
|
||||
</svg>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 366 B |
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"lib": ["es2020", "dom"],
|
||||
"jsx": "preserve"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
$breakpoints: (
|
||||
sm: 640px,
|
||||
md: 768px,
|
||||
lg: 1024px,
|
||||
xl: 1280px,
|
||||
2xl: 1536px,
|
||||
);
|
||||
|
||||
@mixin respond($breakpoint) {
|
||||
@if not map-has-key($breakpoints, $breakpoint) {
|
||||
@warn "'#{$breakpoint}' is not a valid breakpoint";
|
||||
} @else {
|
||||
@media (min-width: map-get($breakpoints, $breakpoint)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// /* ========================================
|
||||
// 霞鹜文楷 (LXGW WenKai) 字体配置
|
||||
// 许可证: SIL Open Font License 1.1
|
||||
// 允许自由分发和使用
|
||||
// ======================================== */
|
||||
|
||||
// /* Light 字重 - 用于代码块或强调 */
|
||||
// @font-face {
|
||||
// font-family: 'LXGW WenKai';
|
||||
// src: url('/fonts/lxgw-wenkai/LXGWWenKai-Light.ttf') format('truetype');
|
||||
// font-weight: 300;
|
||||
// font-style: normal;
|
||||
// font-display: swap;
|
||||
// }
|
||||
|
||||
// /* Regular 字重 - 正文使用 */
|
||||
// @font-face {
|
||||
// font-family: 'LXGW WenKai';
|
||||
// src: url('/fonts/lxgw-wenkai/LXGWWenKai-Regular.ttf') format('truetype');
|
||||
// font-weight: 400;
|
||||
// font-style: normal;
|
||||
// font-display: swap;
|
||||
// }
|
||||
|
||||
// /* Medium 字重 - 标题使用 */
|
||||
// @font-face {
|
||||
// font-family: 'LXGW WenKai';
|
||||
// src: url('/fonts/lxgw-wenkai/LXGWWenKai-Medium.ttf') format('truetype');
|
||||
// font-weight: 500;
|
||||
// font-style: normal;
|
||||
// font-display: swap;
|
||||
// }
|
||||
|
||||
// /* 斜体版本(霞鹜文楷没有专门的斜体,用浏览器模拟) */
|
||||
// @font-face {
|
||||
// font-family: 'LXGW WenKai';
|
||||
// src: url('/fonts/lxgw-wenkai/LXGWWenKai-Regular.ttf') format('truetype');
|
||||
// font-weight: 400;
|
||||
// font-style: italic;
|
||||
// font-display: swap;
|
||||
// }
|
||||
|
||||
// /* ========================================
|
||||
// 全局字体设置
|
||||
// ======================================== */
|
||||
|
||||
// /* 正文字体 */
|
||||
// body {
|
||||
// font-family: 'LXGW WenKai',
|
||||
// -apple-system,
|
||||
// BlinkMacSystemFont,
|
||||
// 'Segoe UI',
|
||||
// Roboto,
|
||||
// 'Helvetica Neue',
|
||||
// 'Noto Sans SC',
|
||||
// 'Microsoft YaHei',
|
||||
// sans-serif;
|
||||
// }
|
||||
|
||||
// /* 标题字体 - 使用 Medium 字重更醒目 */
|
||||
// h1, h2, h3, h4, h5, h6 {
|
||||
// font-family: 'LXGW WenKai',
|
||||
// -apple-system,
|
||||
// BlinkMacSystemFont,
|
||||
// 'Segoe UI',
|
||||
// Roboto,
|
||||
// 'Helvetica Neue',
|
||||
// 'Noto Sans SC',
|
||||
// 'Microsoft YaHei',
|
||||
// sans-serif;
|
||||
// font-weight: 500; /* 使用 Medium 字重 */
|
||||
// }
|
||||
|
||||
// /* 代码块 - 等宽字体,不用霞鹜文楷 */
|
||||
// code, pre, kbd, samp {
|
||||
// font-family: 'JetBrains Mono',
|
||||
// 'SF Mono',
|
||||
// 'Monaco',
|
||||
// 'Cascadia Code',
|
||||
// 'Fira Code',
|
||||
// monospace;
|
||||
// }
|
||||
|
||||
// /* 文章正文保持舒适的字重和大小 */
|
||||
// article {
|
||||
// font-weight: 400;
|
||||
// line-height: 1.7;
|
||||
// }
|
||||
|
||||
// /* 粗体强调 */
|
||||
// strong, b {
|
||||
// font-weight: 500; /* Medium 比 Regular 粗一点,适合强调 */
|
||||
// }
|
||||
|
||||
// /* 可选:文章首字下沉效果 */
|
||||
// // article .article-content > p:first-of-type:first-letter {
|
||||
// // font-size: 3em;
|
||||
// // font-weight: 500;
|
||||
// // float: left;
|
||||
// // line-height: 0.8;
|
||||
// // margin-right: 0.1em;
|
||||
// // }
|
||||
@@ -0,0 +1,349 @@
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the `main` element consistently in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57-
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers.
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE 10+.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10.
|
||||
* 2. Remove the padding in IE 10.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Misc
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10+.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--accent-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--accent-color-darker);
|
||||
}
|
||||
|
||||
&.link {
|
||||
box-shadow: 0px -2px 0px rgba(var(--link-background-color), var(--link-background-opacity)) inset;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px calc(-1rem * var(--article-line-height)) 0px rgba(var(--link-background-color), var(--link-background-opacity-hover)) inset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
text-transform: uppercase;
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
font-size: 1.6rem;
|
||||
font-weight: bold;
|
||||
color: var(--body-text-color);
|
||||
|
||||
a {
|
||||
color: var(--body-text-color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
.container {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
.left-sidebar {
|
||||
order: -3;
|
||||
max-width: var(--left-sidebar-max-width);
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
order: -1;
|
||||
max-width: var(--right-sidebar-max-width);
|
||||
|
||||
/// Display right sidebar when min-width: lg
|
||||
@include respond(lg) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&.extended {
|
||||
@include respond(md) {
|
||||
max-width: 1024px;
|
||||
--left-sidebar-max-width: 25%;
|
||||
--right-sidebar-max-width: 30%;
|
||||
}
|
||||
|
||||
@include respond(lg) {
|
||||
max-width: 1280px;
|
||||
--left-sidebar-max-width: 20%;
|
||||
--right-sidebar-max-width: 30%;
|
||||
}
|
||||
|
||||
@include respond(xl) {
|
||||
max-width: 1536px;
|
||||
--left-sidebar-max-width: 15%;
|
||||
--right-sidebar-max-width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
&.compact {
|
||||
@include respond(md) {
|
||||
--left-sidebar-max-width: 25%;
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
@include respond(lg) {
|
||||
max-width: 1024px;
|
||||
--left-sidebar-max-width: 20%;
|
||||
}
|
||||
|
||||
@include respond(xl) {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.on-phone--column {
|
||||
flex-direction: column;
|
||||
@include respond(md) {
|
||||
flex-direction: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
main.main {
|
||||
order: -2;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--section-separation);
|
||||
|
||||
@include respond(md) {
|
||||
padding-top: var(--main-top-padding);
|
||||
}
|
||||
}
|
||||
|
||||
.main-container {
|
||||
min-height: 100vh;
|
||||
align-items: flex-start;
|
||||
padding: 0 15px;
|
||||
gap: var(--section-separation);
|
||||
padding-top: var(--main-top-padding);
|
||||
|
||||
@include respond(md) {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/* Default article style */
|
||||
.article-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--section-separation);
|
||||
|
||||
article {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--card-background);
|
||||
box-shadow: var(--shadow-l1);
|
||||
border-radius: var(--card-border-radius);
|
||||
overflow: hidden;
|
||||
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-l2);
|
||||
}
|
||||
|
||||
.article-image {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
|
||||
@include respond(md) {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
@include respond(xl) {
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through length($defaultTagBackgrounds) {
|
||||
&:nth-child(#{length($defaultTagBackgrounds)}n + #{$i}) {
|
||||
.article-category a {
|
||||
background: nth($defaultTagBackgrounds, $i);
|
||||
color: nth($defaultTagColors, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.article-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: var(--card-padding);
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
font-family: var(--article-font-family);
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--card-text-color-main);
|
||||
font-size: 2.2rem;
|
||||
|
||||
@include respond(xl) {
|
||||
font-size: 2.4rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--card-text-color-main);
|
||||
|
||||
&:hover {
|
||||
color: var(--card-text-color-main);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.article-subtitle {
|
||||
font-weight: normal;
|
||||
color: var(--card-text-color-secondary);
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
font-size: 1.75rem;
|
||||
@include respond(xl) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.article-title-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.article-time,
|
||||
.article-translations {
|
||||
display: flex;
|
||||
color: var(--card-text-color-tertiary);
|
||||
gap: 15px;
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
stroke-width: 1.33;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
time,
|
||||
a {
|
||||
font-size: 1.4rem;
|
||||
color: var(--card-text-color-tertiary);
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.article-time {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.article-translations {
|
||||
& > div {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.article-category,
|
||||
.article-tags {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
a {
|
||||
color: var(--accent-color-text);
|
||||
background-color: var(--accent-color);
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--tag-border-radius);
|
||||
display: inline-block;
|
||||
font-size: 1.4rem;
|
||||
transition: background-color 0.5s ease;
|
||||
|
||||
&:hover {
|
||||
color: var(--accent-color-text);
|
||||
background-color: var(--accent-color-darker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Compact style article list */
|
||||
.article-list--compact {
|
||||
border-radius: var(--card-border-radius);
|
||||
box-shadow: var(--shadow-l1);
|
||||
background-color: var(--card-background);
|
||||
--image-size: 50px;
|
||||
|
||||
@include respond(md) {
|
||||
--image-size: 60px;
|
||||
}
|
||||
|
||||
article {
|
||||
& > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--small-card-padding);
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
&:not(:last-of-type) {
|
||||
border-bottom: 1.5px solid var(--card-separator-color);
|
||||
}
|
||||
|
||||
.article-details {
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
min-height: var(--image-size);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
|
||||
@include respond(md) {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.article-image {
|
||||
img {
|
||||
width: var(--image-size);
|
||||
height: var(--image-size);
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.article-time {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.article-preview {
|
||||
font-size: 1.4rem;
|
||||
color: var(--card-text-color-tertiary);
|
||||
margin-top: 10px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Tile style article list */
|
||||
.article-list--tile {
|
||||
article {
|
||||
border-radius: var(--card-border-radius);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 350px;
|
||||
width: 250px;
|
||||
box-shadow: var(--shadow-l1);
|
||||
transition: box-shadow 0.3s ease;
|
||||
background-color: var(--card-background);
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-l2);
|
||||
}
|
||||
|
||||
&.has-image {
|
||||
.article-details {
|
||||
background-color: rgba(#000, 0.25);
|
||||
}
|
||||
|
||||
.article-title {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.article-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.article-details {
|
||||
border-radius: var(--card-border-radius);
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
z-index: 2;
|
||||
padding: 15px;
|
||||
|
||||
@include respond(sm) {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.article-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 500;
|
||||
color: var(--card-text-color-main);
|
||||
|
||||
@include respond(sm) {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
html {
|
||||
font-size: 62.5%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--body-background);
|
||||
margin: 0;
|
||||
font-family: var(--base-font-family);
|
||||
font-size: 1.6rem;
|
||||
text-autospace: ideograph-alpha ideograph-numeric punctuation insert;
|
||||
text-spacing-trim: trim-start allow-end;
|
||||
-ms-text-autospace: ideograph-alpha;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* scrollbar styles for Firefox */
|
||||
* {
|
||||
scrollbar-width: auto;
|
||||
scrollbar-color: var(--scrollbar-thumb) transparent;
|
||||
}
|
||||
/**/
|
||||
|
||||
/* scrollbar styles for Chromium */
|
||||
::-webkit-scrollbar {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scrollbar-thumb);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
/**/
|
||||
@@ -0,0 +1,394 @@
|
||||
.disqus-container {
|
||||
background-color: var(--card-background);
|
||||
border-radius: var(--card-border-radius);
|
||||
box-shadow: var(--shadow-l1);
|
||||
padding: var(--card-padding);
|
||||
}
|
||||
|
||||
#dsqjs * {
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
#dsqjs a {
|
||||
text-decoration: none;
|
||||
color: #076dd0
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-hide {
|
||||
display: none!important
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: .5
|
||||
}
|
||||
|
||||
#dsqjs #dsqjs-msg {
|
||||
text-align: center;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px
|
||||
}
|
||||
|
||||
#dsqjs #dsqjs-msg .dsqjs-msg-btn {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-bullet {
|
||||
line-height: 1.4;
|
||||
margin: 0 2px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-bullet:after {
|
||||
color: #c2c6cc;
|
||||
content: "·";
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-clearfix:after,#dsqjs .dsqjs-clearfix:before {
|
||||
display: table;
|
||||
content: "";
|
||||
line-height: 0;
|
||||
clear: both
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-nav {
|
||||
position: relative;
|
||||
margin: 0 0 20px;
|
||||
border-bottom: 2px solid #e7e9ee
|
||||
}
|
||||
|
||||
#dsqjs ol,#dsqjs ul {
|
||||
list-style: none;
|
||||
list-style-type: none
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-no-comment {
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
color: #2a2e2e;
|
||||
margin-bottom: 6px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-nav-tab {
|
||||
float: left;
|
||||
text-transform: capitalize;
|
||||
font-size: 15px;
|
||||
padding: 12px 8px;
|
||||
color: #656c7a;
|
||||
display: block;
|
||||
margin: 0 15px 0 0;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
transition: all .2s ease-in-out
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-nav-tab:last-child {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-tab-active {
|
||||
color: #2a2e2e
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-tab-active>span:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
height: 2px;
|
||||
background-color: #076dd0!important;
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
right: 0
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list .dsqjs-post-item {
|
||||
position: relative;
|
||||
margin-bottom: 16px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list .dsqjs-post-avatar {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
background: #dbdfe4;
|
||||
padding: 0;
|
||||
display: block;
|
||||
border-radius: 4px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list .dsqjs-post-avatar img {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: block;
|
||||
border-radius: 4px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list .dsqjs-post-header {
|
||||
line-height: 1;
|
||||
font-size: 14px;
|
||||
margin-bottom: 3px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-post-author {
|
||||
color: #656c7a;
|
||||
font-weight: 700
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-admin-badge {
|
||||
color: #fff;
|
||||
background: #687a86;
|
||||
padding: 1px 3px;
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: 1px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-meta {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
color: #656c7a
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-body {
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
color: #2a2e2e
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-body code {
|
||||
padding: .2em .4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
background: #f5f5f5;
|
||||
color: inherit;
|
||||
border-radius: 3px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-body pre {
|
||||
padding: .5em;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
border-radius: 3px;
|
||||
background: #f5f5f5;
|
||||
margin: .5em 0
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-body blockquote {
|
||||
padding: 0 .8em;
|
||||
margin: .5em 0;
|
||||
color: #6a737d;
|
||||
border-left: .25em solid #dfe2e5
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-body p:last-child {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list.dsqjs-children>li {
|
||||
margin-left: 30px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list.dsqjs-children .dsqjs-post-avatar img {
|
||||
width: 38px;
|
||||
height: 38px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-load-more {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 11px 14px;
|
||||
margin: 0 0 24px;
|
||||
background: #687a86;
|
||||
color: #fff;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-load-more:hover {
|
||||
opacity: .8
|
||||
}
|
||||
|
||||
#dsqjs footer {
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
padding-top: 10px;
|
||||
padding-right: 10px;
|
||||
border-top: 2px solid #e7e9ee;
|
||||
margin-top: 12px;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
color: #555
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-disqus-logo {
|
||||
background-image: url(https://c.disquscdn.com/next/embed/assets/img/sprite.654110a9206fd22f08cca0798e34a65e.png);
|
||||
background-repeat: no-repeat;
|
||||
display: inline-block;
|
||||
background-size: 86px 40.5px;
|
||||
height: 16.5px;
|
||||
width: 86px;
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-order {
|
||||
display: flex;
|
||||
float: right;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 12px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-order-radio {
|
||||
display: none
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label {
|
||||
color: #fff;
|
||||
background-color: #888
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-order-label {
|
||||
display: block;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
border-radius: 2px;
|
||||
padding: 0 5px;
|
||||
background-color: #dcdcdc;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
#dsqjs p.dsqjs-has-more {
|
||||
margin-bottom: 24px;
|
||||
margin-left: 48px;
|
||||
font-size: 13px;
|
||||
line-height: 15px
|
||||
}
|
||||
|
||||
#dsqjs p.dsqjs-has-more a.dsqjs-has-more-btn {
|
||||
color: #656c7a;
|
||||
text-decoration: underline;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
#dsqjs .dsqjs-post-list.dsqjs-children>li {
|
||||
margin-left:48px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list .dsqjs-post-avatar {
|
||||
margin-right: 12px
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-list .dsqjs-post-item {
|
||||
margin-bottom: 20px
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
#dsqjs .dsqjs-post-list.dsqjs-children>li {
|
||||
margin-left:60px
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-scheme="light"] {
|
||||
#dsqjs .dsqjs-disqus-logo {
|
||||
background-position: 0 -7px;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-scheme="dark"] {
|
||||
#dsqjs {
|
||||
--t-s: rgba(255,255,255,0.9);
|
||||
--alt: #3e4b5e;
|
||||
--link-hover: #47a2e0;
|
||||
--hover-bg: #3e4b5e;
|
||||
--tag: #3e4b5e;
|
||||
--border: #435266;
|
||||
--pre: #3c495b;
|
||||
--c-bg: #2f3947;
|
||||
--code: #c3c7cb;
|
||||
--kbd: #4e5f77;
|
||||
--hl: #abb2bf;
|
||||
--hlc: #808895;
|
||||
--hlk: #c678dd;
|
||||
--hln: #e06c75;
|
||||
--hll: #56b6c2;
|
||||
--hls: #98c379;
|
||||
--hlt: #e6c07b;
|
||||
--hlv: #d19a66;
|
||||
--bg: #181c27;
|
||||
--main: #252d38;
|
||||
--t: rgba(255,255,255,0.86);
|
||||
--t-l: rgba(255,255,255,0.66);
|
||||
--logo: #fff;
|
||||
--link: #38a3fd;
|
||||
--title: rgba(255,255,255,0.92);
|
||||
--fab: #364151;
|
||||
--shadow: none;
|
||||
}
|
||||
|
||||
#disqus_thread {
|
||||
color: var(--body-text-color)
|
||||
}
|
||||
|
||||
#dsqjs #dsqjs-msg {
|
||||
color: var(--t)
|
||||
}
|
||||
|
||||
#dsqjs a {
|
||||
color:var(--link)
|
||||
}
|
||||
|
||||
#dsqjs a:focus,#dsqjs a:hover {
|
||||
color: var(--link-hover)
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-disqus-logo {
|
||||
background-position: 0 -24px;
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-nav,#dsqjs footer {
|
||||
border-color: var(--hlc)
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-load-more,#dsqjs .dsqjs-load-more:hover,#dsqjs .dsqjs-nav-tab,#dsqjs .dsqjs-no-comment,#dsqjs .dsqjs-post-content {
|
||||
color: var(--t)
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-order-label {
|
||||
background-color: var(--hlc)
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label {
|
||||
background-color: var(--kbd)
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-tab-active>span:after {
|
||||
background-color: #2e9fff
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-footer,#dsqjs .dsqjs-meta {
|
||||
color: var(--t-l)
|
||||
}
|
||||
|
||||
#dsqjs .dsqjs-post-body blockquote {
|
||||
border-color: var(--border)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
footer.site-footer {
|
||||
padding: 20px 0 var(--section-separation) 0;
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.75;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 3px;
|
||||
width: 50px;
|
||||
background: var(--body-text-color);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
color: var(--accent-color);
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.powerby {
|
||||
color: var(--body-text-color);
|
||||
font-weight: normal;
|
||||
font-size: 1.2rem;
|
||||
|
||||
a {
|
||||
color: var(--body-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
/* Background */
|
||||
.chroma {
|
||||
color: $color;
|
||||
background-color: $background-color;
|
||||
}
|
||||
|
||||
/* Other */
|
||||
.chroma .x {
|
||||
}
|
||||
|
||||
/* Error */
|
||||
.chroma .err {
|
||||
color: $error-color;
|
||||
}
|
||||
|
||||
/* LineTableTD */
|
||||
.chroma .lntd {
|
||||
vertical-align: top;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* LineTable */
|
||||
.chroma .lntable {
|
||||
border-spacing: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
display: block;
|
||||
|
||||
> tbody {
|
||||
display: block;
|
||||
width: 100%;
|
||||
> tr {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
> td:last-child {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* LineHighlight */
|
||||
.chroma .hl {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: #ffffcc;
|
||||
}
|
||||
|
||||
/* LineNumbersTable */
|
||||
.chroma .lnt {
|
||||
margin-right: 0.4em;
|
||||
padding: 0 0.4em 0 0.4em;
|
||||
color: #7f7f7f;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* LineNumbers */
|
||||
.chroma .ln {
|
||||
margin-right: 0.4em;
|
||||
padding: 0 0.4em 0 0.4em;
|
||||
color: #7f7f7f;
|
||||
}
|
||||
|
||||
/* Keyword */
|
||||
.chroma .k {
|
||||
color: $keyword-color;
|
||||
}
|
||||
|
||||
/* KeywordConstant */
|
||||
.chroma .kc {
|
||||
color: $keyword-color;
|
||||
}
|
||||
|
||||
/* KeywordDeclaration */
|
||||
.chroma .kd {
|
||||
color: $keyword-color;
|
||||
}
|
||||
|
||||
/* KeywordNamespace */
|
||||
.chroma .kn {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
/* KeywordPseudo */
|
||||
.chroma .kp {
|
||||
color: $keyword-color;
|
||||
}
|
||||
|
||||
/* KeywordReserved */
|
||||
.chroma .kr {
|
||||
color: $keyword-color;
|
||||
}
|
||||
|
||||
/* KeywordType */
|
||||
.chroma .kt {
|
||||
color: $keyword-color;
|
||||
}
|
||||
|
||||
/* Name */
|
||||
.chroma .n {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameAttribute */
|
||||
.chroma .na {
|
||||
color: $name-color;
|
||||
}
|
||||
|
||||
/* NameBuiltin */
|
||||
.chroma .nb {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameBuiltinPseudo */
|
||||
.chroma .bp {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameClass */
|
||||
.chroma .nc {
|
||||
color: $name-color;
|
||||
}
|
||||
|
||||
/* NameConstant */
|
||||
.chroma .no {
|
||||
color: $keyword-color;
|
||||
}
|
||||
|
||||
/* NameDecorator */
|
||||
.chroma .nd {
|
||||
color: $name-color;
|
||||
}
|
||||
|
||||
/* NameEntity */
|
||||
.chroma .ni {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameException */
|
||||
.chroma .ne {
|
||||
color: $name-color;
|
||||
}
|
||||
|
||||
/* NameFunction */
|
||||
.chroma .nf {
|
||||
color: $name-color;
|
||||
}
|
||||
|
||||
/* NameFunctionMagic */
|
||||
.chroma .fm {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameLabel */
|
||||
.chroma .nl {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameNamespace */
|
||||
.chroma .nn {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameOther */
|
||||
.chroma .nx {
|
||||
color: $name-color;
|
||||
}
|
||||
|
||||
/* NameProperty */
|
||||
.chroma .py {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameTag */
|
||||
.chroma .nt {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
/* NameVariable */
|
||||
.chroma .nv {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameVariableClass */
|
||||
.chroma .vc {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameVariableGlobal */
|
||||
.chroma .vg {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameVariableInstance */
|
||||
.chroma .vi {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* NameVariableMagic */
|
||||
.chroma .vm {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* Literal */
|
||||
.chroma .l {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
/* LiteralDate */
|
||||
.chroma .ld {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralString */
|
||||
.chroma .s {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringAffix */
|
||||
.chroma .sa {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringBacktick */
|
||||
.chroma .sb {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringChar */
|
||||
.chroma .sc {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringDelimiter */
|
||||
.chroma .dl {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringDoc */
|
||||
.chroma .sd {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringDouble */
|
||||
.chroma .s2 {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringEscape */
|
||||
.chroma .se {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
/* LiteralStringHeredoc */
|
||||
.chroma .sh {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringInterpol */
|
||||
.chroma .si {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringOther */
|
||||
.chroma .sx {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringRegex */
|
||||
.chroma .sr {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringSingle */
|
||||
.chroma .s1 {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralStringSymbol */
|
||||
.chroma .ss {
|
||||
color: $literal-color;
|
||||
}
|
||||
|
||||
/* LiteralNumber */
|
||||
.chroma .m {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
/* LiteralNumberBin */
|
||||
.chroma .mb {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
/* LiteralNumberFloat */
|
||||
.chroma .mf {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
/* LiteralNumberHex */
|
||||
.chroma .mh {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
/* LiteralNumberInteger */
|
||||
.chroma .mi {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
/* LiteralNumberIntegerLong */
|
||||
.chroma .il {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
/* LiteralNumberOct */
|
||||
.chroma .mo {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
/* Operator */
|
||||
.chroma .o {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
/* OperatorWord */
|
||||
.chroma .ow {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
/* Punctuation */
|
||||
.chroma .p {
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
/* Comment */
|
||||
.chroma .c {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
/* CommentHashbang */
|
||||
.chroma .ch {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
/* CommentMultiline */
|
||||
.chroma .cm {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
/* CommentSingle */
|
||||
.chroma .c1 {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
/* CommentSpecial */
|
||||
.chroma .cs {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
/* CommentPreproc */
|
||||
.chroma .cp {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
/* CommentPreprocFile */
|
||||
.chroma .cpf {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
/* Generic */
|
||||
.chroma .g {
|
||||
}
|
||||
|
||||
/* GenericDeleted */
|
||||
.chroma .gd {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
/* GenericEmph */
|
||||
.chroma .ge {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* GenericError */
|
||||
.chroma .gr {
|
||||
}
|
||||
|
||||
/* GenericHeading */
|
||||
.chroma .gh {
|
||||
}
|
||||
|
||||
/* GenericInserted */
|
||||
.chroma .gi {
|
||||
color: $name-color;
|
||||
}
|
||||
|
||||
/* GenericOutput */
|
||||
.chroma .go {
|
||||
}
|
||||
|
||||
/* GenericPrompt */
|
||||
.chroma .gp {
|
||||
}
|
||||
|
||||
/* GenericStrong */
|
||||
.chroma .gs {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* GenericSubheading */
|
||||
.chroma .gu {
|
||||
color: #75715e;
|
||||
}
|
||||
|
||||
/* GenericTraceback */
|
||||
.chroma .gt {
|
||||
}
|
||||
|
||||
/* GenericUnderline */
|
||||
.chroma .gl {
|
||||
}
|
||||
|
||||
/* TextWhitespace */
|
||||
.chroma .w {
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Style: monokai
|
||||
* https://xyproto.github.io/splash/docs/monokai.html
|
||||
*/
|
||||
|
||||
$color: #f8f8f2;
|
||||
$background-color: #272822;
|
||||
$error-color: #bb0064;
|
||||
$keyword-color: #66d9ef;
|
||||
$text-color: $color;
|
||||
$name-color: #a6e22e;
|
||||
$literal-color: #e6db74;
|
||||
|
||||
@import "common.scss";
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Style: monokailight
|
||||
* https://xyproto.github.io/splash/docs/monokailight.html
|
||||
*/
|
||||
|
||||
$color: #272822;
|
||||
$background-color: #fafafa;
|
||||
$error-color: #960050;
|
||||
$keyword-color: #00a8c8;
|
||||
$text-color: #111111;
|
||||
$name-color: #75af00;
|
||||
$literal-color: #d88200;
|
||||
|
||||
@import "common.scss";
|
||||
@@ -0,0 +1,6 @@
|
||||
.not-found-card {
|
||||
background-color: var(--card-background);
|
||||
box-shadow: var(--shadow-l1);
|
||||
border-radius: var(--card-border-radius);
|
||||
padding: var(--card-padding);
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
.article-page {
|
||||
&.hide-sidebar-sm .left-sidebar {
|
||||
display: none;
|
||||
|
||||
@include respond(md) {
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.main-article {
|
||||
background: var(--card-background);
|
||||
border-radius: var(--card-border-radius);
|
||||
box-shadow: var(--shadow-l1);
|
||||
overflow: hidden;
|
||||
|
||||
.article-header {
|
||||
.article-image {
|
||||
img {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
max-height: 50vh;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.article-details {
|
||||
padding: var(--card-padding);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.article-content {
|
||||
margin: var(--card-padding) 0;
|
||||
color: var(--card-text-color-main);
|
||||
|
||||
.footnotes {
|
||||
font-family: var(--base-font-family);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.article-footer {
|
||||
margin: var(--card-padding);
|
||||
margin-top: 0;
|
||||
|
||||
section:not(:first-child) {
|
||||
margin-top: var(--card-padding);
|
||||
}
|
||||
|
||||
section {
|
||||
color: var(--card-text-color-tertiary);
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.4rem;
|
||||
gap: 15px;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
stroke-width: 1.33;
|
||||
}
|
||||
}
|
||||
|
||||
.article-tags {
|
||||
text-transform: unset;
|
||||
}
|
||||
|
||||
.article-copyright,
|
||||
.article-lastmod {
|
||||
a {
|
||||
color: var(--body-text-color);
|
||||
}
|
||||
|
||||
a.link {
|
||||
box-shadow: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.widget--toc {
|
||||
background-color: var(--card-background);
|
||||
border-radius: var(--card-border-radius);
|
||||
box-shadow: var(--shadow-l1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--card-text-color-main);
|
||||
overflow: hidden;
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: var(--card-separator-color);
|
||||
}
|
||||
|
||||
#TableOfContents {
|
||||
overflow-x: auto;
|
||||
max-height: 75vh;
|
||||
|
||||
ol,
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-type: none;
|
||||
counter-reset: item;
|
||||
|
||||
li a:first-of-type::before {
|
||||
counter-increment: item;
|
||||
content: counters(item, ".") ". ";
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
& > ul {
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 15px 0 15px 20px;
|
||||
padding: 5px;
|
||||
|
||||
& > ol,
|
||||
& > ul {
|
||||
margin-top: 10px;
|
||||
padding-left: 10px;
|
||||
margin-bottom: -5px;
|
||||
|
||||
& > li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
li.active-class > a {
|
||||
border-left: var(--heading-border-size) solid var(--accent-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul li.active-class > a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@function repeat($str, $n) {
|
||||
$result: "";
|
||||
@for $_ from 0 to $n {
|
||||
$result: $result + $str;
|
||||
}
|
||||
@return $result;
|
||||
}
|
||||
|
||||
// Support up to 6 levels of indentation for lists in ToCs
|
||||
@for $i from 0 to 5 {
|
||||
& > ul #{repeat("> li > ul", $i)} > li.active-class > a {
|
||||
$n: 25 + $i * 35;
|
||||
margin-left: calc(-#{$n}px - 1em);
|
||||
padding-left: calc(#{$n}px + 1em - var(--heading-border-size));
|
||||
}
|
||||
|
||||
& > ol #{repeat("> li > ol", $i)} > li.active-class > a {
|
||||
$n: 9 + $i * 35;
|
||||
margin-left: calc(-#{$n}px - 1em);
|
||||
padding-left: calc(#{$n}px + 1em - var(--heading-border-size));
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.related-content {
|
||||
overflow-x: auto;
|
||||
padding-bottom: 15px;
|
||||
|
||||
& > .flex {
|
||||
float: left;
|
||||
}
|
||||
|
||||
article {
|
||||
margin-right: 15px;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
|
||||
.article-title {
|
||||
font-size: 1.8rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&.has-image {
|
||||
.article-details {
|
||||
padding: 20px;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.75) 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.article-content {
|
||||
font-family: var(--article-font-family);
|
||||
font-size: var(--article-font-size);
|
||||
padding: 0 var(--card-padding);
|
||||
line-height: var(--article-line-height);
|
||||
|
||||
& > p {
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-inline-start: calc((var(--card-padding)) * -1);
|
||||
padding-inline-start: calc(var(--card-padding) - var(--heading-border-size));
|
||||
border-inline-start: var(--heading-border-size) solid var(--accent-color);
|
||||
position: relative;
|
||||
|
||||
a.header-anchor {
|
||||
transition: opacity 0.3s ease;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: var(--card-padding);
|
||||
text-align: center;
|
||||
color: var(--accent-color);
|
||||
|
||||
&:before {
|
||||
content: "#";
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
a.header-anchor {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
figure {
|
||||
text-align: center;
|
||||
|
||||
figcaption {
|
||||
font-size: 1.4rem;
|
||||
color: var(--card-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
position: relative;
|
||||
margin: 1.5em 0;
|
||||
border-inline-start: var(--blockquote-border-size) solid var(--card-separator-color);
|
||||
padding: 15px calc(var(--card-padding) - var(--blockquote-border-size));
|
||||
background-color: var(--blockquote-background-color);
|
||||
|
||||
.cite {
|
||||
display: block;
|
||||
text-align: right;
|
||||
font-size: 0.75em;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
width: 100px;
|
||||
margin: 40px auto;
|
||||
background: var(--card-text-color-tertiary);
|
||||
height: 2px;
|
||||
border: 0;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
code {
|
||||
color: var(--code-text-color);
|
||||
background-color: var(--code-background-color);
|
||||
padding: 2px 4px;
|
||||
border-radius: var(--tag-border-radius);
|
||||
font-family: var(--code-font-family);
|
||||
}
|
||||
|
||||
a,
|
||||
code {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.gallery {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin: 1.5em 0;
|
||||
gap: 10px;
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
background-color: var(--pre-background-color);
|
||||
color: var(--pre-text-color);
|
||||
font-family: var(--code-font-family);
|
||||
line-height: 1.428571429;
|
||||
word-break: break-all;
|
||||
padding: var(--card-padding);
|
||||
// keep Codeblocks LTR
|
||||
[dir="rtl"] & {
|
||||
direction: ltr;
|
||||
}
|
||||
code {
|
||||
color: unset;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: var(--pre-background-color);
|
||||
padding: var(--card-padding);
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.copyCodeButton {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
// keep Codeblocks LTR
|
||||
[dir="rtl"] & {
|
||||
direction: ltr;
|
||||
}
|
||||
pre {
|
||||
margin: initial;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.copyCodeButton {
|
||||
position: absolute;
|
||||
top: calc(var(--card-padding));
|
||||
right: calc(var(--card-padding));
|
||||
background: var(--card-background);
|
||||
border: none;
|
||||
box-shadow: var(--shadow-l2);
|
||||
border-radius: var(--tag-border-radius);
|
||||
padding: 8px 16px;
|
||||
color: var(--card-text-color-main);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
padding: 0 var(--card-padding);
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
margin-bottom: 1.5em;
|
||||
font-size: 0.96em;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
text-align: left;
|
||||
padding: 4px 8px 4px 10px;
|
||||
border: 1px solid var(--table-border-color);
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: var(--tr-even-background-color);
|
||||
}
|
||||
|
||||
.twitter-tweet {
|
||||
color: var(--card-text-color-main);
|
||||
}
|
||||
|
||||
.video-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding-bottom: 56.25%;
|
||||
overflow: hidden;
|
||||
|
||||
& > iframe,
|
||||
& > video {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.gitlab-embed-snippets {
|
||||
margin: 0 !important;
|
||||
|
||||
.file-holder.snippet-file-content {
|
||||
margin-block-end: 0 !important;
|
||||
margin-block-start: 0 !important;
|
||||
margin-left: calc((var(--card-padding)) * -1) !important;
|
||||
margin-right: calc((var(--card-padding)) * -1) !important;
|
||||
padding: 0 var(--card-padding) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/// Negative margins
|
||||
blockquote,
|
||||
figure,
|
||||
.highlight,
|
||||
pre,
|
||||
.gallery,
|
||||
.video-wrapper,
|
||||
.table-wrapper,
|
||||
.s_video_simple {
|
||||
margin-left: calc((var(--card-padding)) * -1);
|
||||
margin-right: calc((var(--card-padding)) * -1);
|
||||
width: calc(100% + var(--card-padding) * 2);
|
||||
}
|
||||
|
||||
/// Make long KaTeX equations scrollable in the x-axis
|
||||
.katex-display > .katex {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
kbd {
|
||||
border: 1px solid var(--kbd-border-color);
|
||||
font-weight: bold;
|
||||
font-size: 0.9em;
|
||||
line-height: 1;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
.section-card {
|
||||
border-radius: var(--card-border-radius);
|
||||
background-color: var(--card-background);
|
||||
padding: var(--small-card-padding);
|
||||
box-shadow: var(--shadow-l1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
--separation: 15px;
|
||||
|
||||
.section-term {
|
||||
font-size: 2.2rem;
|
||||
margin: 0;
|
||||
color: var(--card-text-color-main);
|
||||
}
|
||||
|
||||
.section-description {
|
||||
font-weight: normal;
|
||||
color: var(--card-text-color-secondary);
|
||||
font-size: 1.6rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section-details {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.section-image {
|
||||
img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.section-count {
|
||||
color: var(--card-text-color-tertiary);
|
||||
font-size: 1.4rem;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.subsection-list {
|
||||
overflow-x: auto;
|
||||
|
||||
.article-list--tile {
|
||||
display: flex;
|
||||
padding-bottom: 15px;
|
||||
|
||||
article {
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
margin-right: 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.article-title {
|
||||
margin: 0;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.article-details {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
.search-form {
|
||||
position: relative;
|
||||
--button-size: 80px;
|
||||
|
||||
&.widget {
|
||||
--button-size: 60px;
|
||||
|
||||
label {
|
||||
font-size: 1.3rem;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 1.5rem;
|
||||
padding: 30px 20px 15px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
inset-inline-start: 20px;
|
||||
font-size: 1.4rem;
|
||||
color: var(--card-text-color-tertiary);
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 40px 20px 20px;
|
||||
border-radius: var(--card-border-radius);
|
||||
background-color: var(--card-background);
|
||||
box-shadow: var(--shadow-l1);
|
||||
color: var(--card-text-color-main);
|
||||
width: 100%;
|
||||
border: 0;
|
||||
-webkit-appearance: none;
|
||||
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
font-size: 1.8rem;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
box-shadow: var(--shadow-l2);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
position: absolute;
|
||||
inset-inline-end: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: var(--button-size);
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
|
||||
padding: 0 10px;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
|
||||
svg {
|
||||
stroke-width: 2;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
color: var(--card-text-color-secondary);
|
||||
stroke-width: 1.33;
|
||||
transition: all 0.3s ease;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/*!
|
||||
* Hamburgers
|
||||
* @description Tasty CSS-animated hamburgers
|
||||
* @author Jonathan Suh @jonsuh
|
||||
* @site https://jonsuh.com/hamburgers
|
||||
* @link https://github.com/jonsuh/hamburgers
|
||||
*/
|
||||
|
||||
.hamburger {
|
||||
padding-top: 10px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
transition-property: opacity, filter;
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: linear;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
text-transform: none;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
.hamburger:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.hamburger.is-active:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.hamburger.is-active .hamburger-inner,
|
||||
.hamburger.is-active .hamburger-inner::before,
|
||||
.hamburger.is-active .hamburger-inner::after {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.hamburger-box {
|
||||
width: 30px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hamburger-inner {
|
||||
display: block;
|
||||
top: 50%;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.hamburger-inner,
|
||||
.hamburger-inner::before,
|
||||
.hamburger-inner::after {
|
||||
width: 30px;
|
||||
height: 2px;
|
||||
background-color: var(--card-text-color-main);
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
transition-property: transform;
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
.hamburger-inner::before,
|
||||
.hamburger-inner::after {
|
||||
content: "";
|
||||
display: block;
|
||||
}
|
||||
.hamburger-inner::before {
|
||||
top: -10px;
|
||||
}
|
||||
.hamburger-inner::after {
|
||||
bottom: -10px;
|
||||
}
|
||||
|
||||
.hamburger--spin .hamburger-inner {
|
||||
transition-duration: 0.22s;
|
||||
transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||||
}
|
||||
.hamburger--spin .hamburger-inner::before {
|
||||
transition: top 0.1s 0.25s ease-in, opacity 0.1s ease-in;
|
||||
}
|
||||
.hamburger--spin .hamburger-inner::after {
|
||||
transition: bottom 0.1s 0.25s ease-in, transform 0.22s cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||||
}
|
||||
|
||||
.hamburger--spin.is-active .hamburger-inner {
|
||||
transform: rotate(225deg);
|
||||
transition-delay: 0.12s;
|
||||
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
.hamburger--spin.is-active .hamburger-inner::before {
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
transition: top 0.1s ease-out, opacity 0.1s 0.12s ease-out;
|
||||
}
|
||||
.hamburger--spin.is-active .hamburger-inner::after {
|
||||
bottom: 0;
|
||||
transform: rotate(-90deg);
|
||||
transition: bottom 0.1s ease-out, transform 0.22s 0.12s cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
|
||||
#toggle-menu {
|
||||
background: none;
|
||||
border: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
|
||||
[dir="rtl"] & {
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
@include respond(md) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
outline: none;
|
||||
|
||||
&.is-active {
|
||||
.hamburger-inner,
|
||||
.hamburger-inner::before,
|
||||
.hamburger-inner::after {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Menu style */
|
||||
#main-menu {
|
||||
list-style: none;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
font-size: 1.4rem;
|
||||
background-color: var(--card-background);
|
||||
|
||||
box-shadow: var(--shadow-l1);
|
||||
display: none;
|
||||
margin: 0 calc(var(--container-padding) * -1);
|
||||
|
||||
padding: 30px 30px;
|
||||
|
||||
@include respond(xl) {
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
&, .menu-bottom-section ol {
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
|
||||
@include respond(xl) {
|
||||
gap: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
&.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@include respond(md) {
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
padding: 0;
|
||||
|
||||
@include respond(md) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
svg {
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.33;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--body-text-color);
|
||||
gap: var(--menu-icon-separation);
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&.current {
|
||||
a {
|
||||
color: var(--accent-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&.menu-bottom-section {
|
||||
margin-top: auto;
|
||||
|
||||
ol {
|
||||
display: flex;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-social {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
stroke: var(--body-text-color);
|
||||
stroke-width: 1.33;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
.pagination {
|
||||
display: flex;
|
||||
background-color: var(--card-background);
|
||||
box-shadow: var(--shadow-l1);
|
||||
border-radius: var(--card-border-radius);
|
||||
overflow: hidden;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.page-link {
|
||||
padding: 16px 32px;
|
||||
display: inline-flex;
|
||||
|
||||
&.current {
|
||||
font-weight: bold;
|
||||
background-color: var(--card-background-selected);
|
||||
color: var(--card-text-color-main);
|
||||
}
|
||||
|
||||
color: var(--card-text-color-secondary);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
.sidebar {
|
||||
&.sticky {
|
||||
@include respond(md) {
|
||||
position: sticky;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.left-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
align-self: stretch;
|
||||
gap: var(--sidebar-element-separation);
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
--sidebar-avatar-size: 100px;
|
||||
--sidebar-element-separation: 20px;
|
||||
--emoji-size: 40px;
|
||||
--emoji-font-size: 20px;
|
||||
|
||||
@include respond(md) {
|
||||
width: auto;
|
||||
padding-top: var(--main-top-padding);
|
||||
padding-bottom: var(--main-top-padding);
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
@include respond(2xl) {
|
||||
--sidebar-avatar-size: 120px;
|
||||
--sidebar-element-separation: 25px;
|
||||
--emoji-size: 40px;
|
||||
}
|
||||
|
||||
&.sticky {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.compact {
|
||||
--sidebar-avatar-size: 80px;
|
||||
--emoji-size: 30px;
|
||||
--emoji-font-size: 15px;
|
||||
|
||||
header {
|
||||
@include respond(lg) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.site-meta {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
font-size: 1.4rem;
|
||||
|
||||
@include respond(2xl) {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.site-description {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-sidebar {
|
||||
width: 100%;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: var(--widget-separation);
|
||||
|
||||
&.sticky {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
@include respond(lg) {
|
||||
padding-top: var(--main-top-padding);
|
||||
padding-bottom: var(--main-top-padding);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar header {
|
||||
z-index: 1;
|
||||
transition: box-shadow 0.5s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sidebar-element-separation);
|
||||
|
||||
@include respond(md) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.site-avatar {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
width: var(--sidebar-avatar-size);
|
||||
height: var(--sidebar-avatar-size);
|
||||
flex-shrink: 0;
|
||||
|
||||
.site-logo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 100%;
|
||||
box-shadow: var(--shadow-l1);
|
||||
}
|
||||
|
||||
.emoji {
|
||||
position: absolute;
|
||||
width: var(--emoji-size);
|
||||
height: var(--emoji-size);
|
||||
line-height: var(--emoji-size);
|
||||
border-radius: 100%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
font-size: var(--emoji-font-size);
|
||||
background-color: var(--card-background);
|
||||
box-shadow: var(--shadow-l2);
|
||||
}
|
||||
}
|
||||
|
||||
.site-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
color: var(--accent-color);
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
|
||||
@include respond(2xl) {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.site-description {
|
||||
color: var(--body-text-color);
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
font-size: 1.4rem;
|
||||
|
||||
@include respond(2xl) {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-scheme="dark"] {
|
||||
#dark-mode-toggle {
|
||||
color: var(--accent-color);
|
||||
font-weight: 700;
|
||||
|
||||
.icon-tabler-toggle-left {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-tabler-toggle-right {
|
||||
display: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#dark-mode-toggle {
|
||||
margin-top: auto;
|
||||
color: var(--body-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
gap: var(--menu-icon-separation);
|
||||
|
||||
.icon-tabler-toggle-right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#i18n-switch {
|
||||
color: var(--body-text-color);
|
||||
display: inline-flex;
|
||||
align-content: center;
|
||||
gap: var(--menu-icon-separation);
|
||||
|
||||
select {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: var(--body-text-color);
|
||||
|
||||
option {
|
||||
color: var(--card-text-color-main);
|
||||
background-color: var(--card-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
.widget {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.widget-icon {
|
||||
svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
stroke-width: 1.6;
|
||||
color: var(--body-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Tag cloud widget */
|
||||
.tagCloud {
|
||||
.tagCloud-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
|
||||
a {
|
||||
background: var(--card-background);
|
||||
box-shadow: var(--shadow-l1);
|
||||
border-radius: var(--tag-border-radius);
|
||||
padding: 8px 20px;
|
||||
color: var(--card-text-color-main);
|
||||
font-size: 1.4rem;
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-l2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Archives widget */
|
||||
.widget.archives {
|
||||
.widget-archive--list {
|
||||
border-radius: var(--card-border-radius);
|
||||
box-shadow: var(--shadow-l1);
|
||||
background-color: var(--card-background);
|
||||
}
|
||||
|
||||
.archives-year {
|
||||
&:not(:last-of-type) {
|
||||
border-bottom: 1.5px solid var(--card-separator-color);
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 1.4rem;
|
||||
padding: 18px 25px;
|
||||
display: flex;
|
||||
|
||||
span.year {
|
||||
flex: 1;
|
||||
color: var(--card-text-color-main);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
span.count {
|
||||
color: var(--card-text-color-tertiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* Hugo Theme Stack
|
||||
*
|
||||
* @author: Jimmy Cai
|
||||
* @website: https://jimmycai.com
|
||||
* @link: https://github.com/CaiJimmy/hugo-theme-stack
|
||||
*/
|
||||
|
||||
@import "breakpoints.scss";
|
||||
@import "variables.scss";
|
||||
@import "grid.scss";
|
||||
|
||||
@import "external/normalize.scss";
|
||||
|
||||
@import "partials/menu.scss";
|
||||
@import "partials/article.scss";
|
||||
@import "partials/widgets.scss";
|
||||
@import "partials/footer.scss";
|
||||
@import "partials/pagination.scss";
|
||||
@import "partials/sidebar.scss";
|
||||
@import "partials/base.scss";
|
||||
@import "partials/layout/article.scss";
|
||||
@import "partials/layout/list.scss";
|
||||
@import "partials/layout/404.scss";
|
||||
@import "partials/layout/search.scss";
|
||||
|
||||
@import "general.scss";
|
||||
@import "custom.scss";
|
||||
@@ -0,0 +1,167 @@
|
||||
$defaultTagBackgrounds: #8ea885, #df7988, #0177b8, #ffb900, #6b69d6;
|
||||
$defaultTagColors: #fff, #fff, #fff, #fff, #fff;
|
||||
|
||||
/*
|
||||
* Global style
|
||||
*/
|
||||
:root {
|
||||
--main-top-padding: 35px;
|
||||
|
||||
@include respond(xl) {
|
||||
--main-top-padding: 50px;
|
||||
}
|
||||
|
||||
--body-background: #f5f5fa;
|
||||
|
||||
--accent-color: #34495e;
|
||||
--accent-color-darker: #2c3e50;
|
||||
--accent-color-text: #fff;
|
||||
--body-text-color: #707070;
|
||||
|
||||
--tag-border-radius: 4px;
|
||||
|
||||
--section-separation: 40px;
|
||||
|
||||
--scrollbar-thumb: hsl(0, 0%, 85%);
|
||||
--scrollbar-track: var(--body-background);
|
||||
|
||||
&[data-scheme="dark"] {
|
||||
--body-background: #303030;
|
||||
--accent-color: #ecf0f1;
|
||||
--accent-color-darker: #bdc3c7;
|
||||
--accent-color-text: #000;
|
||||
--body-text-color: rgba(255, 255, 255, 0.7);
|
||||
--scrollbar-thumb: hsl(0, 0%, 40%);
|
||||
--scrollbar-track: var(--body-background);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global font family
|
||||
*/
|
||||
:root {
|
||||
--sys-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Droid Sans", "Helvetica Neue";
|
||||
--zh-font-family: "PingFang SC", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei";
|
||||
|
||||
--base-font-family: "Lato", var(--sys-font-family), var(--zh-font-family), sans-serif;
|
||||
--code-font-family: Menlo, Monaco, Consolas, "Courier New", var(--zh-font-family), monospace;
|
||||
}
|
||||
|
||||
/*
|
||||
* Card style
|
||||
*/
|
||||
:root {
|
||||
--card-background: #fff;
|
||||
--card-background-selected: #eaeaea;
|
||||
|
||||
--card-text-color-main: #000;
|
||||
--card-text-color-secondary: #747474;
|
||||
--card-text-color-tertiary: #767676;
|
||||
--card-separator-color: rgba(218, 218, 218, 0.5);
|
||||
|
||||
--card-border-radius: 10px;
|
||||
|
||||
--card-padding: 20px;
|
||||
|
||||
@include respond(md) {
|
||||
--card-padding: 25px;
|
||||
}
|
||||
|
||||
@include respond(xl) {
|
||||
--card-padding: 30px;
|
||||
}
|
||||
|
||||
--small-card-padding: 25px 20px;
|
||||
|
||||
@include respond(md) {
|
||||
--small-card-padding: 25px;
|
||||
}
|
||||
|
||||
&[data-scheme="dark"] {
|
||||
--card-background: #424242;
|
||||
--card-background-selected: rgba(255, 255, 255, 0.16);
|
||||
--card-text-color-main: rgba(255, 255, 255, 0.9);
|
||||
--card-text-color-secondary: rgba(255, 255, 255, 0.7);
|
||||
--card-text-color-tertiary: rgba(255, 255, 255, 0.5);
|
||||
--card-separator-color: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Article content font settings
|
||||
*/
|
||||
:root {
|
||||
--article-font-family: var(--base-font-family);
|
||||
--article-font-size: 1.6rem;
|
||||
|
||||
@include respond(md) {
|
||||
--article-font-size: 1.7rem;
|
||||
}
|
||||
|
||||
--article-line-height: 1.85;
|
||||
}
|
||||
|
||||
/*
|
||||
* Article content style
|
||||
*/
|
||||
:root {
|
||||
--blockquote-border-size: 4px;
|
||||
--blockquote-background-color: rgb(248 248 248);
|
||||
|
||||
--heading-border-size: 4px;
|
||||
|
||||
--link-background-color: 189, 195, 199;
|
||||
--link-background-opacity: 0.5;
|
||||
--link-background-opacity-hover: 0.7;
|
||||
|
||||
--pre-background-color: #272822;
|
||||
--pre-text-color: #f8f8f2;
|
||||
|
||||
--code-background-color: rgba(0, 0, 0, 0.12);
|
||||
--code-text-color: #808080;
|
||||
|
||||
--table-border-color: #dadada;
|
||||
--tr-even-background-color: #efefee;
|
||||
|
||||
--kbd-border-color: #dadada;
|
||||
|
||||
&[data-scheme="dark"] {
|
||||
--code-background-color: #272822;
|
||||
--code-text-color: rgba(255, 255, 255, 0.9);
|
||||
|
||||
--table-border-color: #717171;
|
||||
--tr-even-background-color: #545454;
|
||||
|
||||
--blockquote-background-color: rgb(75 75 75);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Shadow style
|
||||
* Thanks to https://www.figma.com/community/plugin/744987207861965946/Shadow-picker
|
||||
*/
|
||||
:root {
|
||||
--shadow-l1: 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 0px 2px rgba(0, 0, 0, 0.06), 0px 0px 1px rgba(0, 0, 0, 0.04);
|
||||
--shadow-l2: 0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
|
||||
--shadow-l3: 0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
|
||||
--shadow-l4: 0px 24px 32px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04),
|
||||
0px 0px 1px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
[data-scheme="light"] {
|
||||
--pre-text-color: #272822;
|
||||
--pre-background-color: #fafafa;
|
||||
@import "partials/highlight/light.scss";
|
||||
}
|
||||
|
||||
[data-scheme="dark"] {
|
||||
--pre-text-color: #f8f8f2;
|
||||
--pre-background-color: #272822;
|
||||
@import "partials/highlight/dark.scss";
|
||||
}
|
||||
|
||||
:root {
|
||||
--menu-icon-separation: 40px;
|
||||
--container-padding: 15px;
|
||||
--widget-separation: var(--section-separation);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
interface colorScheme {
|
||||
hash: string, /// Regenerate color scheme when the image hash is changed
|
||||
DarkMuted: {
|
||||
hex: string,
|
||||
rgb: Number[],
|
||||
bodyTextColor: string
|
||||
},
|
||||
Vibrant: {
|
||||
hex: string,
|
||||
rgb: Number[],
|
||||
bodyTextColor: string
|
||||
}
|
||||
}
|
||||
|
||||
let colorsCache: { [key: string]: colorScheme } = {};
|
||||
|
||||
if (localStorage.hasOwnProperty('StackColorsCache')) {
|
||||
try {
|
||||
colorsCache = JSON.parse(localStorage.getItem('StackColorsCache'));
|
||||
}
|
||||
catch (e) {
|
||||
colorsCache = {};
|
||||
}
|
||||
}
|
||||
|
||||
async function getColor(key: string, hash: string, imageURL: string) {
|
||||
if (!key) {
|
||||
/**
|
||||
* If no key is provided, do not cache the result
|
||||
*/
|
||||
return await Vibrant.from(imageURL).getPalette();
|
||||
}
|
||||
|
||||
if (!colorsCache.hasOwnProperty(key) || colorsCache[key].hash !== hash) {
|
||||
/**
|
||||
* If key is provided, but not found in cache, or the hash mismatches => Regenerate color scheme
|
||||
*/
|
||||
const palette = await Vibrant.from(imageURL).getPalette();
|
||||
|
||||
colorsCache[key] = {
|
||||
hash: hash,
|
||||
Vibrant: {
|
||||
hex: palette.Vibrant.hex,
|
||||
rgb: palette.Vibrant.rgb,
|
||||
bodyTextColor: palette.Vibrant.bodyTextColor
|
||||
},
|
||||
DarkMuted: {
|
||||
hex: palette.DarkMuted.hex,
|
||||
rgb: palette.DarkMuted.rgb,
|
||||
bodyTextColor: palette.DarkMuted.bodyTextColor
|
||||
}
|
||||
}
|
||||
|
||||
/* Save the result in localStorage */
|
||||
localStorage.setItem('StackColorsCache', JSON.stringify(colorsCache));
|
||||
}
|
||||
|
||||
return colorsCache[key];
|
||||
}
|
||||
|
||||
export {
|
||||
getColor
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
type colorScheme = 'light' | 'dark' | 'auto';
|
||||
|
||||
class StackColorScheme {
|
||||
private localStorageKey = 'StackColorScheme';
|
||||
private currentScheme: colorScheme;
|
||||
private systemPreferScheme: colorScheme;
|
||||
|
||||
constructor(toggleEl: HTMLElement) {
|
||||
this.bindMatchMedia();
|
||||
this.currentScheme = this.getSavedScheme();
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches === true)
|
||||
this.systemPreferScheme = 'dark'
|
||||
else
|
||||
this.systemPreferScheme = 'light';
|
||||
|
||||
this.dispatchEvent(document.documentElement.dataset.scheme as colorScheme);
|
||||
|
||||
if (toggleEl)
|
||||
this.bindClick(toggleEl);
|
||||
|
||||
if (document.body.style.transition == '')
|
||||
document.body.style.setProperty('transition', 'background-color .3s ease');
|
||||
}
|
||||
|
||||
private saveScheme() {
|
||||
localStorage.setItem(this.localStorageKey, this.currentScheme);
|
||||
}
|
||||
|
||||
private bindClick(toggleEl: HTMLElement) {
|
||||
toggleEl.addEventListener('click', (e) => {
|
||||
if (this.isDark()) {
|
||||
/// Disable dark mode
|
||||
this.currentScheme = 'light';
|
||||
}
|
||||
else {
|
||||
this.currentScheme = 'dark';
|
||||
}
|
||||
|
||||
this.setBodyClass();
|
||||
|
||||
if (this.currentScheme == this.systemPreferScheme) {
|
||||
/// Set to auto
|
||||
this.currentScheme = 'auto';
|
||||
}
|
||||
|
||||
this.saveScheme();
|
||||
})
|
||||
}
|
||||
|
||||
private isDark() {
|
||||
return (this.currentScheme == 'dark' || this.currentScheme == 'auto' && this.systemPreferScheme == 'dark');
|
||||
}
|
||||
|
||||
private dispatchEvent(colorScheme: colorScheme) {
|
||||
const event = new CustomEvent('onColorSchemeChange', {
|
||||
detail: colorScheme
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
private setBodyClass() {
|
||||
if (this.isDark()) {
|
||||
document.documentElement.dataset.scheme = 'dark';
|
||||
}
|
||||
else {
|
||||
document.documentElement.dataset.scheme = 'light';
|
||||
}
|
||||
|
||||
this.dispatchEvent(document.documentElement.dataset.scheme as colorScheme);
|
||||
}
|
||||
|
||||
private getSavedScheme(): colorScheme {
|
||||
const savedScheme = localStorage.getItem(this.localStorageKey);
|
||||
|
||||
if (savedScheme == 'light' || savedScheme == 'dark' || savedScheme == 'auto') return savedScheme;
|
||||
else return 'auto';
|
||||
}
|
||||
|
||||
private bindMatchMedia() {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||||
if (e.matches) {
|
||||
this.systemPreferScheme = 'dark';
|
||||
}
|
||||
else {
|
||||
this.systemPreferScheme = 'light';
|
||||
}
|
||||
this.setBodyClass();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default StackColorScheme;
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* createElement
|
||||
* Edited from:
|
||||
* @link https://stackoverflow.com/a/42405694
|
||||
*/
|
||||
function createElement(tag, attrs, children) {
|
||||
var element = document.createElement(tag);
|
||||
|
||||
for (let name in attrs) {
|
||||
if (name && attrs.hasOwnProperty(name)) {
|
||||
let value = attrs[name];
|
||||
|
||||
if (name == "dangerouslySetInnerHTML") {
|
||||
element.innerHTML = value.__html;
|
||||
}
|
||||
else if (value === true) {
|
||||
element.setAttribute(name, name);
|
||||
} else if (value !== false && value != null) {
|
||||
element.setAttribute(name, value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 2; i < arguments.length; i++) {
|
||||
let child = arguments[i];
|
||||
if (child) {
|
||||
element.appendChild(
|
||||
child.nodeType == null ?
|
||||
document.createTextNode(child.toString()) : child);
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
export default createElement;
|
||||
@@ -0,0 +1,186 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
PhotoSwipe: any;
|
||||
PhotoSwipeUI_Default: any
|
||||
}
|
||||
}
|
||||
|
||||
interface PhotoSwipeItem {
|
||||
w: number;
|
||||
h: number;
|
||||
src: string;
|
||||
msrc: string;
|
||||
title?: string;
|
||||
el: HTMLElement;
|
||||
}
|
||||
|
||||
class StackGallery {
|
||||
private galleryUID: number;
|
||||
private items: PhotoSwipeItem[] = [];
|
||||
|
||||
constructor(container: HTMLElement, galleryUID = 1) {
|
||||
if (window.PhotoSwipe == undefined || window.PhotoSwipeUI_Default == undefined) {
|
||||
console.error("PhotoSwipe lib not loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.galleryUID = galleryUID;
|
||||
|
||||
StackGallery.createGallery(container);
|
||||
this.loadItems(container);
|
||||
this.bindClick();
|
||||
}
|
||||
|
||||
private loadItems(container: HTMLElement) {
|
||||
this.items = [];
|
||||
|
||||
const figures = container.querySelectorAll('figure.gallery-image');
|
||||
|
||||
for (const el of figures) {
|
||||
const figcaption = el.querySelector('figcaption'),
|
||||
img = el.querySelector('img');
|
||||
|
||||
let aux: PhotoSwipeItem = {
|
||||
w: parseInt(img.getAttribute('width')),
|
||||
h: parseInt(img.getAttribute('height')),
|
||||
src: img.src,
|
||||
msrc: img.getAttribute('data-thumb') || img.src,
|
||||
el: el
|
||||
}
|
||||
|
||||
if (figcaption) {
|
||||
aux.title = figcaption.innerHTML;
|
||||
}
|
||||
|
||||
this.items.push(aux);
|
||||
}
|
||||
}
|
||||
|
||||
public static createGallery(container: HTMLElement) {
|
||||
/// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook
|
||||
/// because it can not detect whether image is being wrapped by a link or not
|
||||
/// and it lead to a invalid HTML construction (<a><figure><img></figure></a>)
|
||||
|
||||
const images = container.querySelectorAll('img.gallery-image');
|
||||
for (const img of Array.from(images)) {
|
||||
/// Images are wrapped with figure tag if the paragraph has only images without texts
|
||||
/// This is done to allow inline images within paragraphs
|
||||
const paragraph = img.closest('p');
|
||||
|
||||
if (!paragraph || !container.contains(paragraph)) continue;
|
||||
|
||||
if (paragraph.textContent.trim() == '') {
|
||||
/// Once we insert figcaption, this check no longer works
|
||||
/// So we add a class to paragraph to mark it
|
||||
paragraph.classList.add('no-text');
|
||||
}
|
||||
|
||||
let isNewLineImage = paragraph.classList.contains('no-text');
|
||||
if (!isNewLineImage) continue;
|
||||
|
||||
const hasLink = img.parentElement.tagName == 'A';
|
||||
|
||||
let el: HTMLElement = img;
|
||||
/// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes
|
||||
const figure = document.createElement('figure');
|
||||
figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1');
|
||||
figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0');
|
||||
if (hasLink) {
|
||||
/// Wrap <a> if it exists
|
||||
el = img.parentElement;
|
||||
}
|
||||
el.parentElement.insertBefore(figure, el);
|
||||
figure.appendChild(el);
|
||||
|
||||
/// Add figcaption if it exists
|
||||
if (img.hasAttribute('alt')) {
|
||||
const figcaption = document.createElement('figcaption');
|
||||
figcaption.innerText = img.getAttribute('alt');
|
||||
figure.appendChild(figcaption);
|
||||
}
|
||||
|
||||
/// Wrap img tag with <a> tag if image was not wrapped by <a> tag
|
||||
if (!hasLink) {
|
||||
figure.className = 'gallery-image';
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = img.src;
|
||||
a.setAttribute('target', '_blank');
|
||||
img.parentNode.insertBefore(a, img);
|
||||
a.appendChild(img);
|
||||
}
|
||||
}
|
||||
|
||||
const figuresEl = container.querySelectorAll('figure.gallery-image');
|
||||
|
||||
let currentGallery = [];
|
||||
|
||||
for (const figure of figuresEl) {
|
||||
if (!currentGallery.length) {
|
||||
/// First iteration
|
||||
currentGallery = [figure];
|
||||
}
|
||||
else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) {
|
||||
/// Adjacent figures
|
||||
currentGallery.push(figure);
|
||||
}
|
||||
else if (currentGallery.length) {
|
||||
/// End gallery
|
||||
StackGallery.wrap(currentGallery);
|
||||
currentGallery = [figure];
|
||||
}
|
||||
}
|
||||
|
||||
if (currentGallery.length > 0) {
|
||||
StackGallery.wrap(currentGallery);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap adjacent figure tags with div.gallery
|
||||
* @param figures
|
||||
*/
|
||||
public static wrap(figures: HTMLElement[]) {
|
||||
const galleryContainer = document.createElement('div');
|
||||
galleryContainer.className = 'gallery';
|
||||
|
||||
const parentNode = figures[0].parentNode,
|
||||
first = figures[0];
|
||||
|
||||
parentNode.insertBefore(galleryContainer, first)
|
||||
|
||||
for (const figure of figures) {
|
||||
galleryContainer.appendChild(figure);
|
||||
}
|
||||
}
|
||||
|
||||
public open(index: number) {
|
||||
const pswp = document.querySelector('.pswp') as HTMLDivElement;
|
||||
const ps = new window.PhotoSwipe(pswp, window.PhotoSwipeUI_Default, this.items, {
|
||||
index: index,
|
||||
galleryUID: this.galleryUID,
|
||||
getThumbBoundsFn: (index) => {
|
||||
const thumbnail = this.items[index].el.getElementsByTagName('img')[0],
|
||||
pageYScroll = window.pageYOffset || document.documentElement.scrollTop,
|
||||
rect = thumbnail.getBoundingClientRect();
|
||||
|
||||
return { x: rect.left, y: rect.top + pageYScroll, w: rect.width };
|
||||
}
|
||||
});
|
||||
|
||||
ps.init();
|
||||
}
|
||||
|
||||
private bindClick() {
|
||||
for (const [index, item] of this.items.entries()) {
|
||||
const a = item.el.querySelector('a');
|
||||
|
||||
a.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.open(index);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default StackGallery;
|
||||
@@ -0,0 +1,112 @@
|
||||
/*!
|
||||
* Hugo Theme Stack
|
||||
*
|
||||
* @author: Jimmy Cai
|
||||
* @website: https://jimmycai.com
|
||||
* @link: https://github.com/CaiJimmy/hugo-theme-stack
|
||||
*/
|
||||
import StackGallery from "ts/gallery";
|
||||
import { getColor } from 'ts/color';
|
||||
import menu from 'ts/menu';
|
||||
import createElement from 'ts/createElement';
|
||||
import StackColorScheme from 'ts/colorScheme';
|
||||
import { setupScrollspy } from 'ts/scrollspy';
|
||||
import { setupSmoothAnchors } from "ts/smoothAnchors";
|
||||
|
||||
let Stack = {
|
||||
init: () => {
|
||||
/**
|
||||
* Bind menu event
|
||||
*/
|
||||
menu();
|
||||
|
||||
const articleContent = document.querySelector('.article-content') as HTMLElement;
|
||||
if (articleContent) {
|
||||
new StackGallery(articleContent);
|
||||
setupSmoothAnchors();
|
||||
setupScrollspy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add linear gradient background to tile style article
|
||||
*/
|
||||
const articleTile = document.querySelector('.article-list--tile');
|
||||
if (articleTile) {
|
||||
let observer = new IntersectionObserver(async (entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if (!entry.isIntersecting) return;
|
||||
observer.unobserve(entry.target);
|
||||
|
||||
const articles = entry.target.querySelectorAll('article.has-image');
|
||||
articles.forEach(async articles => {
|
||||
const image = articles.querySelector('img'),
|
||||
imageURL = image.src,
|
||||
key = image.getAttribute('data-key'),
|
||||
hash = image.getAttribute('data-hash'),
|
||||
articleDetails: HTMLDivElement = articles.querySelector('.article-details');
|
||||
|
||||
const colors = await getColor(key, hash, imageURL);
|
||||
|
||||
articleDetails.style.background = `
|
||||
linear-gradient(0deg,
|
||||
rgba(${colors.DarkMuted.rgb[0]}, ${colors.DarkMuted.rgb[1]}, ${colors.DarkMuted.rgb[2]}, 0.5) 0%,
|
||||
rgba(${colors.Vibrant.rgb[0]}, ${colors.Vibrant.rgb[1]}, ${colors.Vibrant.rgb[2]}, 0.75) 100%)`;
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
observer.observe(articleTile)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add copy button to code block
|
||||
*/
|
||||
const highlights = document.querySelectorAll('.article-content div.highlight');
|
||||
const copyText = `点击复制`,
|
||||
copiedText = `已复制!`;
|
||||
|
||||
highlights.forEach(highlight => {
|
||||
const copyButton = document.createElement('button');
|
||||
copyButton.innerHTML = copyText;
|
||||
copyButton.classList.add('copyCodeButton');
|
||||
highlight.appendChild(copyButton);
|
||||
|
||||
const codeBlock = highlight.querySelector('code[data-lang]');
|
||||
if (!codeBlock) return;
|
||||
|
||||
copyButton.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(codeBlock.textContent)
|
||||
.then(() => {
|
||||
copyButton.textContent = copiedText;
|
||||
|
||||
setTimeout(() => {
|
||||
copyButton.textContent = copyText;
|
||||
}, 1000);
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err)
|
||||
console.log('Something went wrong', err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
new StackColorScheme(document.getElementById('dark-mode-toggle'));
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(function () {
|
||||
Stack.init();
|
||||
}, 0);
|
||||
})
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
createElement: any;
|
||||
Stack: any
|
||||
}
|
||||
}
|
||||
|
||||
window.Stack = Stack;
|
||||
window.createElement = createElement;
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Slide up/down
|
||||
* Code from https://dev.to/bmsvieira/vanilla-js-slidedown-up-4dkn
|
||||
* @param target
|
||||
* @param duration
|
||||
*/
|
||||
let slideUp = (target: HTMLElement, duration = 500) => {
|
||||
target.classList.add('transiting');
|
||||
target.style.transitionProperty = 'height, margin, padding';
|
||||
target.style.transitionDuration = duration + 'ms';
|
||||
///target.style.boxSizing = 'border-box';
|
||||
target.style.height = target.offsetHeight + 'px';
|
||||
target.offsetHeight;
|
||||
target.style.overflow = 'hidden';
|
||||
target.style.height = "0";
|
||||
target.style.paddingTop = "0";
|
||||
target.style.paddingBottom = "0";
|
||||
target.style.marginTop = "0";
|
||||
target.style.marginBottom = "0";
|
||||
window.setTimeout(() => {
|
||||
target.classList.remove('show')
|
||||
target.style.removeProperty('height');
|
||||
target.style.removeProperty('padding-top');
|
||||
target.style.removeProperty('padding-bottom');
|
||||
target.style.removeProperty('margin-top');
|
||||
target.style.removeProperty('margin-bottom');
|
||||
target.style.removeProperty('overflow');
|
||||
target.style.removeProperty('transition-duration');
|
||||
target.style.removeProperty('transition-property');
|
||||
target.classList.remove('transiting');
|
||||
}, duration);
|
||||
}
|
||||
|
||||
let slideDown = (target: HTMLElement, duration = 500) => {
|
||||
target.classList.add('transiting');
|
||||
target.style.removeProperty('display');
|
||||
|
||||
target.classList.add('show');
|
||||
|
||||
let height = target.offsetHeight;
|
||||
target.style.overflow = 'hidden';
|
||||
target.style.height = "0";
|
||||
target.style.paddingTop = "0";
|
||||
target.style.paddingBottom = "0";
|
||||
target.style.marginTop = "0";
|
||||
target.style.marginBottom = "0";
|
||||
target.offsetHeight;
|
||||
///target.style.boxSizing = 'border-box';
|
||||
target.style.transitionProperty = "height, margin, padding";
|
||||
target.style.transitionDuration = duration + 'ms';
|
||||
target.style.height = height + 'px';
|
||||
target.style.removeProperty('padding-top');
|
||||
target.style.removeProperty('padding-bottom');
|
||||
target.style.removeProperty('margin-top');
|
||||
target.style.removeProperty('margin-bottom');
|
||||
window.setTimeout(() => {
|
||||
target.style.removeProperty('height');
|
||||
target.style.removeProperty('overflow');
|
||||
target.style.removeProperty('transition-duration');
|
||||
target.style.removeProperty('transition-property');
|
||||
target.classList.remove('transiting');
|
||||
}, duration);
|
||||
}
|
||||
|
||||
let slideToggle = (target, duration = 500) => {
|
||||
if (window.getComputedStyle(target).display === 'none') {
|
||||
return slideDown(target, duration);
|
||||
} else {
|
||||
return slideUp(target, duration);
|
||||
}
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const toggleMenu = document.getElementById('toggle-menu');
|
||||
if (toggleMenu) {
|
||||
toggleMenu.addEventListener('click', () => {
|
||||
if (document.getElementById('main-menu').classList.contains('transiting')) return;
|
||||
document.body.classList.toggle('show-menu');
|
||||
slideToggle(document.getElementById('main-menu'), 300);
|
||||
toggleMenu.classList.toggle('is-active');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Implements a scroll spy system for the ToC, displaying the current section with an indicator and scrolling to it when needed.
|
||||
|
||||
// Inspired from https://gomakethings.com/debouncing-your-javascript-events/
|
||||
function debounced(func: Function) {
|
||||
let timeout;
|
||||
return () => {
|
||||
if (timeout) {
|
||||
window.cancelAnimationFrame(timeout);
|
||||
}
|
||||
|
||||
timeout = window.requestAnimationFrame(() => func());
|
||||
}
|
||||
}
|
||||
|
||||
const headersQuery = ".article-content h1[id], .article-content h2[id], .article-content h3[id], .article-content h4[id], .article-content h5[id], .article-content h6[id]";
|
||||
const tocQuery = "#TableOfContents";
|
||||
const navigationQuery = "#TableOfContents li";
|
||||
const activeClass = "active-class";
|
||||
|
||||
function scrollToTocElement(tocElement: HTMLElement, scrollableNavigation: HTMLElement) {
|
||||
let textHeight = tocElement.querySelector("a").offsetHeight;
|
||||
let scrollTop = tocElement.offsetTop - scrollableNavigation.offsetHeight / 2 + textHeight / 2 - scrollableNavigation.offsetTop;
|
||||
if (scrollTop < 0) {
|
||||
scrollTop = 0;
|
||||
}
|
||||
scrollableNavigation.scrollTo({ top: scrollTop, behavior: "smooth" });
|
||||
}
|
||||
|
||||
type IdToElementMap = { [key: string]: HTMLElement };
|
||||
|
||||
function buildIdToNavigationElementMap(navigation: NodeListOf<Element>): IdToElementMap {
|
||||
const sectionLinkRef: IdToElementMap = {};
|
||||
navigation.forEach((navigationElement: HTMLElement) => {
|
||||
const link = navigationElement.querySelector("a");
|
||||
if (link) {
|
||||
const href = link.getAttribute("href");
|
||||
if (href.startsWith("#")) {
|
||||
sectionLinkRef[href.slice(1)] = navigationElement;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return sectionLinkRef;
|
||||
}
|
||||
|
||||
function computeOffsets(headers: NodeListOf<Element>) {
|
||||
let sectionsOffsets = [];
|
||||
headers.forEach((header: HTMLElement) => { sectionsOffsets.push({ id: header.id, offset: header.offsetTop }) });
|
||||
sectionsOffsets.sort((a, b) => a.offset - b.offset);
|
||||
return sectionsOffsets;
|
||||
}
|
||||
|
||||
function setupScrollspy() {
|
||||
let headers = document.querySelectorAll(headersQuery);
|
||||
if (!headers) {
|
||||
console.warn("No header matched query", headers);
|
||||
return;
|
||||
}
|
||||
|
||||
let scrollableNavigation = document.querySelector(tocQuery) as HTMLElement | undefined;
|
||||
if (!scrollableNavigation) {
|
||||
console.warn("No toc matched query", tocQuery);
|
||||
return;
|
||||
}
|
||||
|
||||
let navigation = document.querySelectorAll(navigationQuery);
|
||||
if (!navigation) {
|
||||
console.warn("No navigation matched query", navigationQuery);
|
||||
return;
|
||||
}
|
||||
|
||||
let sectionsOffsets = computeOffsets(headers);
|
||||
|
||||
// We need to avoid scrolling when the user is actively interacting with the ToC. Otherwise, if the user clicks on a link in the ToC,
|
||||
// we would scroll their view, which is not optimal usability-wise.
|
||||
let tocHovered: boolean = false;
|
||||
scrollableNavigation.addEventListener("mouseenter", debounced(() => tocHovered = true));
|
||||
scrollableNavigation.addEventListener("mouseleave", debounced(() => tocHovered = false));
|
||||
|
||||
let activeSectionLink: Element;
|
||||
|
||||
let idToNavigationElement: IdToElementMap = buildIdToNavigationElementMap(navigation);
|
||||
|
||||
function scrollHandler() {
|
||||
let scrollPosition = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
|
||||
let newActiveSection: HTMLElement | undefined;
|
||||
|
||||
// Find the section that is currently active.
|
||||
// It is possible for no section to be active, so newActiveSection may be undefined.
|
||||
sectionsOffsets.forEach((section) => {
|
||||
if (scrollPosition >= section.offset - 20) {
|
||||
newActiveSection = document.getElementById(section.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Find the link for the active section. Once again, there are a few edge cases:
|
||||
// - No active section = no link => undefined
|
||||
// - No active section but the link does not exist in toc (e.g. because it is outside of the applicable ToC levels) => undefined
|
||||
let newActiveSectionLink: HTMLElement | undefined
|
||||
if (newActiveSection) {
|
||||
newActiveSectionLink = idToNavigationElement[newActiveSection.id];
|
||||
}
|
||||
|
||||
if (newActiveSection && !newActiveSectionLink) {
|
||||
// The active section does not have a link in the ToC, so we can't scroll to it.
|
||||
console.debug("No link found for section", newActiveSection);
|
||||
} else if (newActiveSectionLink !== activeSectionLink) {
|
||||
if (activeSectionLink)
|
||||
activeSectionLink.classList.remove(activeClass);
|
||||
if (newActiveSectionLink) {
|
||||
newActiveSectionLink.classList.add(activeClass);
|
||||
if (!tocHovered) {
|
||||
// Scroll so that newActiveSectionLink is in the middle of scrollableNavigation, except when it's from a manual click (hence the tocHovered check)
|
||||
scrollToTocElement(newActiveSectionLink, scrollableNavigation);
|
||||
}
|
||||
}
|
||||
activeSectionLink = newActiveSectionLink;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", debounced(scrollHandler));
|
||||
|
||||
// Resizing may cause the offset values to change: recompute them.
|
||||
function resizeHandler() {
|
||||
sectionsOffsets = computeOffsets(headers);
|
||||
scrollHandler();
|
||||
}
|
||||
|
||||
// Use ResizeObserver to detect changes in the size of .article-content
|
||||
const articleContent = document.querySelector(".article-content");
|
||||
if (articleContent) {
|
||||
const resizeObserver = new ResizeObserver(debounced(resizeHandler));
|
||||
resizeObserver.observe(articleContent);
|
||||
}
|
||||
|
||||
window.addEventListener("resize", debounced(resizeHandler));
|
||||
}
|
||||
|
||||
export { setupScrollspy };
|
||||
@@ -0,0 +1,333 @@
|
||||
interface pageData {
|
||||
title: string,
|
||||
date: string,
|
||||
permalink: string,
|
||||
content: string,
|
||||
image?: string,
|
||||
preview: string,
|
||||
matchCount: number
|
||||
}
|
||||
|
||||
interface match {
|
||||
start: number,
|
||||
end: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML tags as HTML entities
|
||||
* Edited from:
|
||||
* @link https://stackoverflow.com/a/5499821
|
||||
*/
|
||||
const tagsToReplace = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
'…': '…'
|
||||
};
|
||||
|
||||
function replaceTag(tag) {
|
||||
return tagsToReplace[tag] || tag;
|
||||
}
|
||||
|
||||
function replaceHTMLEnt(str) {
|
||||
return str.replace(/[&<>"]/g, replaceTag);
|
||||
}
|
||||
|
||||
function escapeRegExp(string) {
|
||||
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
class Search {
|
||||
private data: pageData[];
|
||||
private form: HTMLFormElement;
|
||||
private input: HTMLInputElement;
|
||||
private list: HTMLDivElement;
|
||||
private resultTitle: HTMLHeadElement;
|
||||
private resultTitleTemplate: string;
|
||||
|
||||
constructor({ form, input, list, resultTitle, resultTitleTemplate }) {
|
||||
this.form = form;
|
||||
this.input = input;
|
||||
this.list = list;
|
||||
this.resultTitle = resultTitle;
|
||||
this.resultTitleTemplate = resultTitleTemplate;
|
||||
|
||||
/// Check if there's already value in the search input
|
||||
if (this.input.value.trim() !== '') {
|
||||
this.doSearch(this.input.value.split(' '));
|
||||
}
|
||||
else {
|
||||
this.handleQueryString();
|
||||
}
|
||||
|
||||
this.bindQueryStringChange();
|
||||
this.bindSearchForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes search matches
|
||||
* @param str original text
|
||||
* @param matches array of matches
|
||||
* @param ellipsis whether to add ellipsis to the end of each match
|
||||
* @param charLimit max length of preview string
|
||||
* @param offset how many characters before and after the match to include in preview
|
||||
* @returns preview string
|
||||
*/
|
||||
private static processMatches(str: string, matches: match[], ellipsis: boolean = true, charLimit = 140, offset = 20): string {
|
||||
matches.sort((a, b) => {
|
||||
return a.start - b.start;
|
||||
});
|
||||
|
||||
let i = 0,
|
||||
lastIndex = 0,
|
||||
charCount = 0;
|
||||
|
||||
const resultArray: string[] = [];
|
||||
|
||||
while (i < matches.length) {
|
||||
const item = matches[i];
|
||||
|
||||
/// item.start >= lastIndex (equal only for the first iteration)
|
||||
/// because of the while loop that comes after, iterating over variable j
|
||||
|
||||
if (ellipsis && item.start - offset > lastIndex) {
|
||||
resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, lastIndex + offset))} [...] `);
|
||||
resultArray.push(`${replaceHTMLEnt(str.substring(item.start - offset, item.start))}`);
|
||||
charCount += offset * 2;
|
||||
}
|
||||
else {
|
||||
/// If the match is too close to the end of last match, don't add ellipsis
|
||||
resultArray.push(replaceHTMLEnt(str.substring(lastIndex, item.start)));
|
||||
charCount += item.start - lastIndex;
|
||||
}
|
||||
|
||||
let j = i + 1,
|
||||
end = item.end;
|
||||
|
||||
/// Include as many matches as possible
|
||||
/// [item.start, end] is the range of the match
|
||||
while (j < matches.length && matches[j].start <= end) {
|
||||
end = Math.max(matches[j].end, end);
|
||||
++j;
|
||||
}
|
||||
|
||||
resultArray.push(`<mark>${replaceHTMLEnt(str.substring(item.start, end))}</mark>`);
|
||||
charCount += end - item.start;
|
||||
|
||||
i = j;
|
||||
lastIndex = end;
|
||||
|
||||
if (ellipsis && charCount > charLimit) break;
|
||||
}
|
||||
|
||||
/// Add the rest of the string
|
||||
if (lastIndex < str.length) {
|
||||
let end = str.length;
|
||||
if (ellipsis) end = Math.min(end, lastIndex + offset);
|
||||
|
||||
resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, end))}`);
|
||||
|
||||
if (ellipsis && end != str.length) {
|
||||
resultArray.push(` [...]`);
|
||||
}
|
||||
}
|
||||
|
||||
return resultArray.join('');
|
||||
}
|
||||
|
||||
private async searchKeywords(keywords: string[]) {
|
||||
const rawData = await this.getData();
|
||||
const results: pageData[] = [];
|
||||
|
||||
const regex = new RegExp(keywords.filter((v, index, arr) => {
|
||||
arr[index] = escapeRegExp(v);
|
||||
return v.trim() !== '';
|
||||
}).join('|'), 'gi');
|
||||
|
||||
for (const item of rawData) {
|
||||
const titleMatches: match[] = [],
|
||||
contentMatches: match[] = [];
|
||||
|
||||
let result = {
|
||||
...item,
|
||||
preview: '',
|
||||
matchCount: 0
|
||||
}
|
||||
|
||||
const contentMatchAll = item.content.matchAll(regex);
|
||||
for (const match of Array.from(contentMatchAll)) {
|
||||
contentMatches.push({
|
||||
start: match.index,
|
||||
end: match.index + match[0].length
|
||||
});
|
||||
}
|
||||
|
||||
const titleMatchAll = item.title.matchAll(regex);
|
||||
for (const match of Array.from(titleMatchAll)) {
|
||||
titleMatches.push({
|
||||
start: match.index,
|
||||
end: match.index + match[0].length
|
||||
});
|
||||
}
|
||||
|
||||
if (titleMatches.length > 0) result.title = Search.processMatches(result.title, titleMatches, false);
|
||||
if (contentMatches.length > 0) {
|
||||
result.preview = Search.processMatches(result.content, contentMatches);
|
||||
}
|
||||
else {
|
||||
/// If there are no matches in the content, use the first 140 characters as preview
|
||||
result.preview = replaceHTMLEnt(result.content.substring(0, 140));
|
||||
}
|
||||
|
||||
result.matchCount = titleMatches.length + contentMatches.length;
|
||||
if (result.matchCount > 0) results.push(result);
|
||||
}
|
||||
|
||||
/// Result with more matches appears first
|
||||
return results.sort((a, b) => {
|
||||
return b.matchCount - a.matchCount;
|
||||
});
|
||||
}
|
||||
|
||||
private async doSearch(keywords: string[]) {
|
||||
const startTime = performance.now();
|
||||
|
||||
const results = await this.searchKeywords(keywords);
|
||||
this.clear();
|
||||
|
||||
for (const item of results) {
|
||||
this.list.append(Search.render(item));
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
|
||||
this.resultTitle.innerText = this.generateResultTitle(results.length, ((endTime - startTime) / 1000).toPrecision(1));
|
||||
}
|
||||
|
||||
private generateResultTitle(resultLen, time) {
|
||||
return this.resultTitleTemplate.replace("#PAGES_COUNT", resultLen).replace("#TIME_SECONDS", time);
|
||||
}
|
||||
|
||||
public async getData() {
|
||||
if (!this.data) {
|
||||
/// Not fetched yet
|
||||
const jsonURL = this.form.dataset.json;
|
||||
this.data = await fetch(jsonURL).then(res => res.json());
|
||||
const parser = new DOMParser();
|
||||
|
||||
for (const item of this.data) {
|
||||
item.content = parser.parseFromString(item.content, 'text/html').body.innerText;
|
||||
}
|
||||
}
|
||||
|
||||
return this.data;
|
||||
}
|
||||
|
||||
private bindSearchForm() {
|
||||
let lastSearch = '';
|
||||
|
||||
const eventHandler = (e) => {
|
||||
e.preventDefault();
|
||||
const keywords = this.input.value.trim();
|
||||
|
||||
Search.updateQueryString(keywords, true);
|
||||
|
||||
if (keywords === '') {
|
||||
lastSearch = '';
|
||||
return this.clear();
|
||||
}
|
||||
|
||||
if (lastSearch === keywords) return;
|
||||
lastSearch = keywords;
|
||||
|
||||
this.doSearch(keywords.split(' '));
|
||||
}
|
||||
|
||||
this.input.addEventListener('input', eventHandler);
|
||||
this.input.addEventListener('compositionend', eventHandler);
|
||||
}
|
||||
|
||||
private clear() {
|
||||
this.list.innerHTML = '';
|
||||
this.resultTitle.innerText = '';
|
||||
}
|
||||
|
||||
private bindQueryStringChange() {
|
||||
window.addEventListener('popstate', (e) => {
|
||||
this.handleQueryString()
|
||||
})
|
||||
}
|
||||
|
||||
private handleQueryString() {
|
||||
const pageURL = new URL(window.location.toString());
|
||||
const keywords = pageURL.searchParams.get('keyword');
|
||||
this.input.value = keywords;
|
||||
|
||||
if (keywords) {
|
||||
this.doSearch(keywords.split(' '));
|
||||
}
|
||||
else {
|
||||
this.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private static updateQueryString(keywords: string, replaceState = false) {
|
||||
const pageURL = new URL(window.location.toString());
|
||||
|
||||
if (keywords === '') {
|
||||
pageURL.searchParams.delete('keyword')
|
||||
}
|
||||
else {
|
||||
pageURL.searchParams.set('keyword', keywords);
|
||||
}
|
||||
|
||||
if (replaceState) {
|
||||
window.history.replaceState('', '', pageURL.toString());
|
||||
}
|
||||
else {
|
||||
window.history.pushState('', '', pageURL.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static render(item: pageData) {
|
||||
return <article>
|
||||
<a href={item.permalink}>
|
||||
<div class="article-details">
|
||||
<h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2>
|
||||
<section class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></section>
|
||||
</div>
|
||||
{item.image &&
|
||||
<div class="article-image">
|
||||
<img src={item.image} loading="lazy" />
|
||||
</div>
|
||||
}
|
||||
</a>
|
||||
</article>;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
searchResultTitleTemplate: string;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(function () {
|
||||
const searchForm = document.querySelector('.search-form') as HTMLFormElement,
|
||||
searchInput = searchForm.querySelector('input') as HTMLInputElement,
|
||||
searchResultList = document.querySelector('.search-result--list') as HTMLDivElement,
|
||||
searchResultTitle = document.querySelector('.search-result--title') as HTMLHeadingElement;
|
||||
|
||||
new Search({
|
||||
form: searchForm,
|
||||
input: searchInput,
|
||||
list: searchResultList,
|
||||
resultTitle: searchResultTitle,
|
||||
resultTitleTemplate: window.searchResultTitleTemplate
|
||||
});
|
||||
}, 0);
|
||||
})
|
||||
|
||||
export default Search;
|
||||
@@ -0,0 +1,37 @@
|
||||
// Implements smooth scrolling when clicking on an anchor link.
|
||||
// This is required instead of using modern CSS because Chromium does not currently support scrolling
|
||||
// one element with scrollTo while another element is scrolled because of a click on a link. This would
|
||||
// thus not work with the ToC scrollspy and e.g. footnotes.
|
||||
|
||||
// Here are additional links about this issue:
|
||||
// - https://stackoverflow.com/questions/49318497/google-chrome-simultaneously-smooth-scrollintoview-with-more-elements-doesn
|
||||
// - https://stackoverflow.com/questions/57214373/scrollintoview-using-smooth-function-on-multiple-elements-in-chrome
|
||||
// - https://bugs.chromium.org/p/chromium/issues/detail?id=833617
|
||||
// - https://bugs.chromium.org/p/chromium/issues/detail?id=1043933
|
||||
// - https://bugs.chromium.org/p/chromium/issues/detail?id=1121151
|
||||
|
||||
const anchorLinksQuery = "a[href]";
|
||||
|
||||
function setupSmoothAnchors() {
|
||||
document.querySelectorAll(anchorLinksQuery).forEach(aElement => {
|
||||
let href = aElement.getAttribute("href");
|
||||
if (!href.startsWith("#")) {
|
||||
return;
|
||||
}
|
||||
aElement.addEventListener("click", clickEvent => {
|
||||
clickEvent.preventDefault();
|
||||
|
||||
const targetId = decodeURI(aElement.getAttribute("href").substring(1)),
|
||||
target = document.getElementById(targetId) as HTMLElement,
|
||||
offset = target.getBoundingClientRect().top - document.documentElement.getBoundingClientRect().top;
|
||||
|
||||
window.history.pushState({}, "", aElement.getAttribute("href"));
|
||||
scrollTo({
|
||||
top: offset,
|
||||
behavior: "smooth"
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export { setupSmoothAnchors };
|
||||
@@ -0,0 +1,38 @@
|
||||
Vibrant:
|
||||
- src: /js/vibrant.min.js
|
||||
integrity: sha256-awcR2jno4kI5X0zL8ex0vi2z+KMkF24hUW8WePSA9HM=
|
||||
type: script
|
||||
|
||||
PhotoSwipe:
|
||||
- src: /js/photoswipe.min.js
|
||||
integrity: sha256-ePwmChbbvXbsO02lbM3HoHbSHTHFAeChekF1xKJdleo=
|
||||
type: script
|
||||
defer: true
|
||||
|
||||
- src: /js/photoswipe-ui-default.min.js
|
||||
integrity: sha256-UKkzOn/w1mBxRmLLGrSeyB4e1xbrp4xylgAWb3M42pU=
|
||||
type: script
|
||||
defer: true
|
||||
|
||||
- src: /css/default-skin.css
|
||||
integrity: sha256-c0uckgykQ9v5k+IqViZOZKc47Jn7KQil4/MP3ySA3F8=
|
||||
type: style
|
||||
|
||||
- src: /css/photoswipe.css
|
||||
integrity: sha256-SBLU4vv6CA6lHsZ1XyTdhyjJxCjPif/TRkjnsyGAGnE=
|
||||
type: style
|
||||
|
||||
KaTeX:
|
||||
- src: /css/katex.css
|
||||
integrity: sha256-hNzwPIKcTN4VMW22XyQxpfkD5+s8GbarVVr6wz6G02M=
|
||||
type: style
|
||||
|
||||
- src: /js/katex.min.js
|
||||
integrity: sha384-XjKyOOlGwcjNTAIQHIpgOno0Hl1YQqzUOEleOLALmuqehneUG+vnGctmUb0ZY0l8
|
||||
type: script
|
||||
defer: true
|
||||
|
||||
- src: /js/auto-render.min.js
|
||||
integrity: sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05
|
||||
type: script
|
||||
defer: true
|
||||
@@ -0,0 +1,149 @@
|
||||
module:
|
||||
hugoVersion:
|
||||
extended: true
|
||||
min: "0.87.0"
|
||||
|
||||
params:
|
||||
mainSections:
|
||||
- post
|
||||
featuredImageField: image
|
||||
rssFullContent: true
|
||||
favicon:
|
||||
|
||||
footer:
|
||||
since:
|
||||
customText:
|
||||
|
||||
dateFormat:
|
||||
published: Jan 02, 2006
|
||||
lastUpdated: Jan 02, 2006 15:04 MST
|
||||
|
||||
sidebar:
|
||||
compact: false
|
||||
emoji:
|
||||
subtitle:
|
||||
avatar:
|
||||
enabled: true
|
||||
local: true
|
||||
src: img/avatar.png
|
||||
|
||||
article:
|
||||
headingAnchor: false
|
||||
math: false
|
||||
toc: true
|
||||
readingTime: true
|
||||
license:
|
||||
enabled: false
|
||||
default: Licensed under CC BY-NC-SA 4.0
|
||||
|
||||
comments:
|
||||
enabled: false
|
||||
provider: disqus
|
||||
|
||||
disqusjs:
|
||||
shortname:
|
||||
apiUrl:
|
||||
apiKey:
|
||||
admin:
|
||||
adminLabel:
|
||||
|
||||
utterances:
|
||||
repo:
|
||||
issueTerm: pathname
|
||||
label:
|
||||
|
||||
beaudar:
|
||||
repo:
|
||||
issueTerm: pathname
|
||||
label:
|
||||
theme:
|
||||
|
||||
remark42:
|
||||
host:
|
||||
site:
|
||||
locale:
|
||||
|
||||
vssue:
|
||||
platform:
|
||||
owner:
|
||||
repo:
|
||||
clientId:
|
||||
clientSecret:
|
||||
autoCreateIssue: false
|
||||
|
||||
# Waline client configuration see: https://waline.js.org/en/reference/client/props.html
|
||||
waline:
|
||||
serverURL:
|
||||
lang:
|
||||
visitor:
|
||||
avatar:
|
||||
emoji:
|
||||
- https://cdn.jsdelivr.net/gh/walinejs/emojis/weibo
|
||||
requiredMeta:
|
||||
- nick
|
||||
- mail
|
||||
locale:
|
||||
admin: Admin
|
||||
placeholder:
|
||||
|
||||
twikoo:
|
||||
envId:
|
||||
region:
|
||||
path:
|
||||
lang:
|
||||
|
||||
giscus:
|
||||
repo:
|
||||
repoID:
|
||||
category:
|
||||
categoryID:
|
||||
mapping:
|
||||
strict:
|
||||
lightTheme:
|
||||
darkTheme:
|
||||
reactionsEnabled: 1
|
||||
emitMetadata: 0
|
||||
inputPosition:
|
||||
lang:
|
||||
|
||||
gitalk:
|
||||
owner:
|
||||
admin:
|
||||
repo:
|
||||
clientID:
|
||||
clientSecret:
|
||||
|
||||
cusdis:
|
||||
host:
|
||||
id:
|
||||
|
||||
widgets:
|
||||
homepage: []
|
||||
page: []
|
||||
|
||||
opengraph:
|
||||
twitter:
|
||||
# Your Twitter username
|
||||
site:
|
||||
|
||||
# Available values: summary, summary_large_image
|
||||
card: summary_large_image
|
||||
|
||||
defaultImage:
|
||||
opengraph:
|
||||
enabled: false
|
||||
local: false
|
||||
src:
|
||||
|
||||
colorScheme:
|
||||
# Display toggle
|
||||
toggle: true
|
||||
|
||||
# Available values: auto, light, dark
|
||||
default: auto
|
||||
|
||||
imageProcessing:
|
||||
cover:
|
||||
enabled: true
|
||||
content:
|
||||
enabled: true
|
||||
@@ -0,0 +1,73 @@
|
||||
toggleMenu:
|
||||
other: Toggle Menu
|
||||
|
||||
darkMode:
|
||||
other: Dark Mode
|
||||
|
||||
list:
|
||||
page:
|
||||
one: "{{ .Count }} page"
|
||||
other: "{{ .Count }} pages"
|
||||
|
||||
section:
|
||||
other: Section
|
||||
|
||||
subsection:
|
||||
one: Subsection
|
||||
other: Subsections
|
||||
|
||||
article:
|
||||
back:
|
||||
other: Back
|
||||
|
||||
tableOfContents:
|
||||
other: Table of contents
|
||||
|
||||
relatedContent:
|
||||
other: Related content
|
||||
|
||||
lastUpdatedOn:
|
||||
other: Last updated on
|
||||
|
||||
readingTime:
|
||||
one: "{{ .Count }} minute read"
|
||||
other: "{{ .Count }} minute read"
|
||||
|
||||
notFound:
|
||||
title:
|
||||
other: Not Found
|
||||
|
||||
subtitle:
|
||||
other: This page does not exist
|
||||
|
||||
widget:
|
||||
archives:
|
||||
title:
|
||||
other: Archives
|
||||
|
||||
more:
|
||||
other: More
|
||||
|
||||
tagCloud:
|
||||
title:
|
||||
other: Tags
|
||||
categoriesCloud:
|
||||
title:
|
||||
other: Categories
|
||||
|
||||
search:
|
||||
title:
|
||||
other: Search
|
||||
|
||||
placeholder:
|
||||
other: Type something...
|
||||
|
||||
resultTitle:
|
||||
other: "#PAGES_COUNT pages (#TIME_SECONDS seconds)"
|
||||
|
||||
footer:
|
||||
builtWith:
|
||||
other: Built with {{ .Generator }}
|
||||
|
||||
designedBy:
|
||||
other: Theme {{ .Theme }} designed by {{ .DesignedBy }}
|
||||
@@ -0,0 +1,77 @@
|
||||
toggleMenu:
|
||||
other: 切换菜单
|
||||
|
||||
darkMode:
|
||||
other: 暗色模式
|
||||
|
||||
list:
|
||||
page: "{{ .Count }} 个页面"
|
||||
|
||||
section: 章节
|
||||
|
||||
subsection: 子章节
|
||||
|
||||
article:
|
||||
back:
|
||||
other: 返回
|
||||
|
||||
tableOfContents:
|
||||
other: 目录
|
||||
|
||||
relatedContent:
|
||||
other: 相关文章
|
||||
|
||||
lastUpdatedOn:
|
||||
other: 最后更新于
|
||||
|
||||
readingTime:
|
||||
other: "阅读时长: {{ .Count }} 分钟"
|
||||
|
||||
notFound:
|
||||
title:
|
||||
other: 404 错误
|
||||
subtitle:
|
||||
other: 页面不存在
|
||||
|
||||
widget:
|
||||
archives:
|
||||
title:
|
||||
other: 归档
|
||||
|
||||
more:
|
||||
other: 更多
|
||||
|
||||
tagCloud:
|
||||
title:
|
||||
other: 标签
|
||||
|
||||
categoriesCloud:
|
||||
title:
|
||||
other: 分类
|
||||
roomsCloud:
|
||||
title:
|
||||
other: 房间
|
||||
|
||||
search:
|
||||
title:
|
||||
other: 搜索
|
||||
|
||||
placeholder:
|
||||
other: 输入关键词...
|
||||
|
||||
resultTitle:
|
||||
other: "#PAGES_COUNT 个结果 (用时 #TIME_SECONDS 秒)"
|
||||
|
||||
footer:
|
||||
builtWith:
|
||||
other: 使用 {{ .Generator }} 构建
|
||||
|
||||
designedBy:
|
||||
other: 主题 {{ .Theme }} 由 {{ .DesignedBy }} 设计
|
||||
|
||||
room:
|
||||
translation: "房间"
|
||||
category:
|
||||
translation: "分类"
|
||||
tag:
|
||||
translation: "标签"
|
||||
@@ -0,0 +1,45 @@
|
||||
{{ define "main" }}
|
||||
<div class="not-found-card">
|
||||
<h1 class="article-title">{{ T "notFound.title" }}</h1>
|
||||
<h2 class="article-subtitle">{{ T "notFound.subtitle" }}</h2>
|
||||
</div>
|
||||
|
||||
{{- $query := first 1 (where .Site.Pages "Layout" "==" "search") -}}
|
||||
{{- $searchPage := index $query 0 -}}
|
||||
|
||||
{{- with $searchPage -}}
|
||||
<form action="{{ $searchPage.RelPermalink }}" class="search-form widget" {{ with .OutputFormats.Get "json" -}}data-json="{{ .Permalink }}" {{- end }}>
|
||||
<p>
|
||||
<label>{{ T "search.title" }}</label>
|
||||
<input id="searchInput" name="keyword" required placeholder="{{ T `search.placeholder` }}" />
|
||||
|
||||
<button title="{{ T `search.title` }}">
|
||||
{{ partial "helper/icon" "search" }}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<div class="search-result">
|
||||
<h3 class="search-result--title section-title"></h3>
|
||||
<div class="search-result--list article-list--compact"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.searchResultTitleTemplate = "{{ T `search.resultTitle` }}"
|
||||
</script>
|
||||
|
||||
{{- $opts := dict "minify" hugo.IsProduction "JSXFactory" "createElement" -}}
|
||||
{{- $searchScript := resources.Get "ts/search.tsx" | js.Build $opts -}}
|
||||
<script type="text/javascript" src="{{ $searchScript.RelPermalink }}" defer></script>
|
||||
|
||||
<script>
|
||||
const wrongUrl = new URL(window.location.href);
|
||||
|
||||
/// Get the search keyword from the wrong URL by removing all slashes and dashes
|
||||
const searchKeyword = decodeURIComponent(wrongUrl.pathname).split(/[/|-]/).join(' ').trim();
|
||||
|
||||
document.getElementById('searchInput').setAttribute('value', searchKeyword);
|
||||
</script>
|
||||
{{- end -}}
|
||||
{{ partialCached "footer/footer" . }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,6 @@
|
||||
<h{{ .Level }} id="{{ .Anchor }}">
|
||||
{{- if site.Params.Article.HeadingAnchor -}}
|
||||
<a href="#{{ .Anchor }}" class="header-anchor"></a>
|
||||
{{- end -}}
|
||||
{{ .Text | safeHTML }}
|
||||
</h{{ .Level }}>
|
||||
@@ -0,0 +1,41 @@
|
||||
{{- $image := .Page.Resources.GetMatch (printf "%s" (.Destination | safeURL)) -}}
|
||||
{{- $Permalink := .Destination | relURL | safeURL -}}
|
||||
{{- $alt := .PlainText | safeHTML -}}
|
||||
{{- $Width := 0 -}}
|
||||
{{- $Height := 0 -}}
|
||||
{{- $Srcset := "" -}}
|
||||
|
||||
{{/* SVG and external images won't work with gallery layout, because their width and height attributes are unknown */}}
|
||||
{{- $galleryImage := false -}}
|
||||
|
||||
{{- if $image -}}
|
||||
{{- $notSVG := ne (path.Ext .Destination) ".svg" -}}
|
||||
{{- $Permalink = $image.RelPermalink -}}
|
||||
|
||||
{{- if $notSVG -}}
|
||||
{{- $Width = $image.Width -}}
|
||||
{{- $Height = $image.Height -}}
|
||||
{{- $galleryImage = true -}}
|
||||
|
||||
{{- if (default true .Page.Site.Params.imageProcessing.content.enabled) -}}
|
||||
{{- $small := $image.Resize `480x` -}}
|
||||
{{- $big := $image.Resize `1024x` -}}
|
||||
{{- $Srcset = printf `%s 480w, %s 1024w` $small.RelPermalink $big.RelPermalink -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<img src="{{ $Permalink }}"
|
||||
{{ with $Width }}width="{{ . }}"{{ end }}
|
||||
{{ with $Height }}height="{{ . }}"{{ end }}
|
||||
{{ with $Srcset }}srcset="{{ . }}"{{ end }}
|
||||
loading="lazy"
|
||||
{{ with $alt }}
|
||||
alt="{{ . }}"
|
||||
{{ end }}
|
||||
{{ if $galleryImage }}
|
||||
class="gallery-image"
|
||||
data-flex-grow="{{ div (mul $image.Width 100) $image.Height }}"
|
||||
data-flex-basis="{{ div (mul $image.Width 240) $image.Height }}px"
|
||||
{{ end }}
|
||||
>
|
||||
@@ -0,0 +1,3 @@
|
||||
<a class="link" href="{{ .Destination | safeURL }}" {{ with .Title}} title="{{ . }}"
|
||||
{{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener"
|
||||
{{ end }}>{{ .Text | safeHTML }}</a>
|
||||
@@ -0,0 +1,35 @@
|
||||
{{ define "body-class" }}template-archives{{ end }}
|
||||
{{ define "main" }}
|
||||
<header>
|
||||
{{- $taxonomy := $.Site.GetPage "taxonomyTerm" "categories" -}}
|
||||
{{- $terms := $taxonomy.Pages -}}
|
||||
{{ if $terms }}
|
||||
<h2 class="section-title">{{ T "widget.categoriesCloud.title" }}</h2>
|
||||
<div class="subsection-list">
|
||||
<div class="article-list--tile">
|
||||
{{ range $terms }}
|
||||
{{ partial "article-list/tile" (dict "context" . "size" "250x150" "Type" "taxonomy") }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</header>
|
||||
|
||||
{{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }}
|
||||
{{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }}
|
||||
{{ $filtered := ($pages | intersect $notHidden) }}
|
||||
|
||||
{{ range $filtered.GroupByDate "2006" }}
|
||||
{{ $id := lower (replace .Key " " "-") }}
|
||||
<div class="archives-group" id="{{ $id }}">
|
||||
<h2 class="archives-date section-title"><a href="{{ $.RelPermalink }}#{{ $id }}">{{ .Key }}</a></h2>
|
||||
<div class="article-list--compact">
|
||||
{{ range .Pages }}
|
||||
{{ partial "article-list/compact" . }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ partialCached "footer/footer" . }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Site.LanguageCode }}" dir="{{ default `ltr` .Language.LanguageDirection }}">
|
||||
<head>
|
||||
{{- partial "head/head.html" . -}}
|
||||
{{- block "head" . -}}{{ end }}
|
||||
</head>
|
||||
<body class="{{ block `body-class` . }}{{ end }}">
|
||||
{{- partial "head/colorScheme" . -}}
|
||||
|
||||
{{/* The container is wider when there's any activated widget */}}
|
||||
{{- $hasWidget := false -}}
|
||||
{{- range .Site.Params.widgets -}}
|
||||
{{- if gt (len .) 0 -}}
|
||||
{{- $hasWidget = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<div class="container main-container flex on-phone--column {{ if $hasWidget }}extended{{ else }}compact{{ end }}">
|
||||
{{- block "left-sidebar" . -}}
|
||||
{{ partial "sidebar/left.html" . }}
|
||||
{{- end -}}
|
||||
{{- block "right-sidebar" . -}}{{ end }}
|
||||
<main class="main full-width">
|
||||
{{- block "main" . }}{{- end }}
|
||||
</main>
|
||||
</div>
|
||||
{{ partial "footer/include.html" . }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
{{ define "main" }}
|
||||
{{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }}
|
||||
{{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }}
|
||||
{{ $filtered := ($pages | intersect $notHidden) }}
|
||||
{{ $pag := .Paginate ($filtered) }}
|
||||
|
||||
<section class="article-list">
|
||||
{{ range $index, $element := $pag.Pages }}
|
||||
{{ partial "article-list/default" . }}
|
||||
{{ end }}
|
||||
</section>
|
||||
|
||||
{{- partial "pagination.html" . -}}
|
||||
{{- partial "footer/footer" . -}}
|
||||
{{ end }}
|
||||
|
||||
{{ define "right-sidebar" }}
|
||||
{{ partial "sidebar/right.html" (dict "Context" . "Scope" "homepage") }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,85 @@
|
||||
{{ define "main" }}
|
||||
<header>
|
||||
<h3 class="section-title">
|
||||
{{ if eq .Parent (.GetPage "/") }}
|
||||
{{ T "list.section" }}
|
||||
{{ else }}
|
||||
{{ .Parent.Title }}
|
||||
{{ end }}
|
||||
</h3>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="section-details">
|
||||
<h3 class="section-count">{{ T "list.page" (len .Pages) }}</h3>
|
||||
<h1 class="section-term">{{ .Title }}</h1>
|
||||
{{ with .Params.description }}
|
||||
<h2 class="section-description">{{ . }}</h2>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{- $image := partialCached "helper/image" (dict "Context" . "Type" "section") .RelPermalink "section" -}}
|
||||
{{ if $image.exists }}
|
||||
<div class="section-image">
|
||||
{{ if $image.resource }}
|
||||
{{- $Permalink := $image.resource.RelPermalink -}}
|
||||
{{- $Width := $image.resource.Width -}}
|
||||
{{- $Height := $image.resource.Height -}}
|
||||
|
||||
{{- if (default true .Page.Site.Params.imageProcessing.cover.enabled) -}}
|
||||
{{- $thumbnail := $image.resource.Fill "120x120" -}}
|
||||
{{- $Permalink = $thumbnail.RelPermalink -}}
|
||||
{{- $Width = $thumbnail.Width -}}
|
||||
{{- $Height = $thumbnail.Height -}}
|
||||
{{- end -}}
|
||||
|
||||
<img src="{{ $Permalink }}"
|
||||
width="{{ $Width }}"
|
||||
height="{{ $Height }}"
|
||||
loading="lazy">
|
||||
{{ else }}
|
||||
<img src="{{ $image.permalink }}" loading="lazy" />
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{- $subsections := .Sections -}}
|
||||
{{- $pages := .Pages | complement $subsections -}}
|
||||
|
||||
{{- if eq (len $pages) 0 -}}
|
||||
{{/* If there are no normal pages, display subsections in list style, with pagination */}}
|
||||
{{/* This happens with taxonomies like categories or tags */}}
|
||||
{{- $pages = $subsections -}}
|
||||
{{- $subsections = slice -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- with $subsections -}}
|
||||
<aside>
|
||||
<h2 class="section-title">{{ T "list.subsection" (len $subsections) }}</h2>
|
||||
<div class="subsection-list">
|
||||
<div class="article-list--tile">
|
||||
{{ range . }}
|
||||
{{ partial "article-list/tile" (dict "context" . "size" "250x150" "Type" "section") }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
{{- end -}}
|
||||
|
||||
{{/* List only pages that are not a subsection */}}
|
||||
{{ $paginator := .Paginate $pages }}
|
||||
<section class="article-list--compact">
|
||||
{{ range $paginator.Pages }}
|
||||
{{ partial "article-list/compact" . }}
|
||||
{{ end }}
|
||||
</section>
|
||||
|
||||
{{- partial "pagination.html" . -}}
|
||||
|
||||
{{ partialCached "footer/footer" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "right-sidebar" }}
|
||||
{{ partial "sidebar/right.html" (dict "Context" . "Scope" "homepage") }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,33 @@
|
||||
{{ define "body-class" }}template-search{{ end }}
|
||||
{{ define "head" }}
|
||||
{{- with .OutputFormats.Get "json" -}}
|
||||
<link rel="preload" href="{{ .RelPermalink }}" as="fetch" crossorigin="anonymous">
|
||||
{{- end -}}
|
||||
{{ end }}
|
||||
{{ define "main" }}
|
||||
<form action="{{ .RelPermalink }}" class="search-form"{{ with .OutputFormats.Get "json" -}} data-json="{{ .RelPermalink }}"{{- end }}>
|
||||
<p>
|
||||
<label>{{ T "search.title" }}</label>
|
||||
<input name="keyword" placeholder="{{ T `search.placeholder` }}" />
|
||||
</p>
|
||||
|
||||
<button title="{{ T `search.title` }}">
|
||||
{{ partial "helper/icon" "search" }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="search-result">
|
||||
<h3 class="search-result--title section-title"></h3>
|
||||
<div class="search-result--list article-list--compact"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.searchResultTitleTemplate = "{{ T `search.resultTitle` }}"
|
||||
</script>
|
||||
|
||||
{{- $opts := dict "minify" hugo.IsProduction "JSXFactory" "createElement" -}}
|
||||
{{- $searchScript := resources.Get "ts/search.tsx" | js.Build $opts -}}
|
||||
<script type="text/javascript" src="{{ $searchScript.RelPermalink }}" defer></script>
|
||||
|
||||
{{ partialCached "footer/footer" . }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,26 @@
|
||||
{{- $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections -}}
|
||||
{{- $notHidden := where .Site.RegularPages "Params.hidden" "!=" true -}}
|
||||
{{- $filtered := ($pages | intersect $notHidden) -}}
|
||||
|
||||
{{- $result := slice -}}
|
||||
|
||||
{{- range $filtered -}}
|
||||
{{- $data := dict "title" .Title "date" .Date "permalink" .Permalink "content" (.Plain) -}}
|
||||
|
||||
{{- $image := partialCached "helper/image" (dict "Context" . "Type" "articleList") .RelPermalink "articleList" -}}
|
||||
{{- if $image.exists -}}
|
||||
{{- $imagePermalink := "" -}}
|
||||
{{- if and $image.resource (default true .Page.Site.Params.imageProcessing.cover.enabled) -}}
|
||||
{{- $thumbnail := $image.resource.Fill "120x120" -}}
|
||||
{{- $imagePermalink = (absURL $thumbnail.Permalink) -}}
|
||||
{{- else -}}
|
||||
{{- $imagePermalink = $image.permalink -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $data = merge $data (dict "image" (absURL $imagePermalink)) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $result = $result | append $data -}}
|
||||
{{- end -}}
|
||||
|
||||
{{ jsonify $result }}
|
||||
@@ -0,0 +1,40 @@
|
||||
<article>
|
||||
<a href="{{ .RelPermalink }}">
|
||||
<div class="article-details">
|
||||
<h2 class="article-title">
|
||||
{{- .Title -}}
|
||||
</h2>
|
||||
<footer class="article-time">
|
||||
<time datetime='{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}'>
|
||||
{{- .Date | time.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
|
||||
</time>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
{{- $image := partialCached "helper/image" (dict "Context" . "Type" "articleList") .RelPermalink "articleList" -}}
|
||||
{{ if $image.exists }}
|
||||
<div class="article-image">
|
||||
{{ if $image.resource }}
|
||||
{{- $Permalink := $image.resource.RelPermalink -}}
|
||||
{{- $Width := $image.resource.Width -}}
|
||||
{{- $Height := $image.resource.Height -}}
|
||||
|
||||
{{- if (default true .Page.Site.Params.imageProcessing.cover.enabled) -}}
|
||||
{{- $thumbnail := $image.resource.Fill "120x120" -}}
|
||||
{{- $Permalink = $thumbnail.RelPermalink -}}
|
||||
{{- $Width = $thumbnail.Width -}}
|
||||
{{- $Height = $thumbnail.Height -}}
|
||||
{{- end -}}
|
||||
|
||||
<img src="{{ $Permalink }}"
|
||||
width="{{ $Width }}"
|
||||
height="{{ $Height }}"
|
||||
alt="{{ .Title }}"
|
||||
loading="lazy">
|
||||
{{ else }}
|
||||
<img src="{{ $image.permalink }}" loading="lazy" alt="Featured image of post {{ .Title }}" />
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</a>
|
||||
</article>
|
||||
@@ -0,0 +1,4 @@
|
||||
{{ $image := partialCached "helper/image" (dict "Context" . "Type" "articleList") .RelPermalink "articleList" }}
|
||||
<article class="{{ if $image.exists }}has-image{{ end }}">
|
||||
{{ partial "article/components/header" . }}
|
||||
</article>
|
||||
@@ -0,0 +1,39 @@
|
||||
{{ $image := partialCached "helper/image" (dict "Context" .context "Type" .Type) .context.RelPermalink .Type }}
|
||||
<article class="{{ if $image.exists }}has-image{{ end }}">
|
||||
<a href="{{ .context.RelPermalink }}">
|
||||
|
||||
{{ if $image.exists }}
|
||||
<div class="article-image">
|
||||
{{ if $image.resource }}
|
||||
{{- $imageRaw := $image.resource | resources.Fingerprint "md5" -}}
|
||||
{{- $Permalink := $imageRaw.RelPermalink -}}
|
||||
{{- $Width := $imageRaw.Width -}}
|
||||
{{- $Height := $imageRaw.Height -}}
|
||||
|
||||
{{- if .context.Site.Params.imageProcessing.cover.enabled -}}
|
||||
{{- $thumbnail := $imageRaw.Fill .size -}}
|
||||
{{- $Permalink = $thumbnail.RelPermalink -}}
|
||||
{{- $Width = $thumbnail.Width -}}
|
||||
{{- $Height = $thumbnail.Height -}}
|
||||
{{- end -}}
|
||||
|
||||
<img src="{{ $Permalink }}"
|
||||
width="{{ $Width }}"
|
||||
height="{{ $Height }}"
|
||||
loading="lazy"
|
||||
alt="Featured image of post {{ .context.Title }}"
|
||||
{{ with .context.Slug }}data-key="{{ . }}" {{ end }}
|
||||
data-hash="{{ $imageRaw.Data.Integrity }}">
|
||||
{{ else }}
|
||||
<img src="{{ $image.permalink }}" loading="lazy" data-key="{{ .context.Slug }}" data-hash="{{ $image.permalink }}"/>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="article-details">
|
||||
<h2 class="article-title">
|
||||
{{- .context.Title -}}
|
||||
</h2>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
@@ -0,0 +1,11 @@
|
||||
<article class="{{ if .Params.image }}has-image {{ end }}main-article">
|
||||
{{ partial "article/components/header" . }}
|
||||
|
||||
{{ partial "article/components/content" . }}
|
||||
|
||||
{{ partial "article/components/footer" . }}
|
||||
|
||||
{{ if or .Params.math .Site.Params.article.math }}
|
||||
{{ partialCached "article/components/math.html" . }}
|
||||
{{ end }}
|
||||
</article>
|
||||
@@ -0,0 +1,5 @@
|
||||
<section class="article-content">
|
||||
<!-- Refer to https://discourse.gohugo.io/t/responsive-tables-in-markdown/10639/5 -->
|
||||
{{ $wrappedTable := printf "<div class=\"table-wrapper\">${1}</div>" }}
|
||||
{{ .Content | replaceRE "(<table>(?:.|\n)+?</table>)" $wrappedTable | safeHTML }}
|
||||
</section>
|
||||
@@ -0,0 +1,61 @@
|
||||
<div class="article-details">
|
||||
{{ if .Params.categories }}
|
||||
<header class="article-category">
|
||||
{{ range (.GetTerms "categories") }}
|
||||
<a href="{{ .RelPermalink }}" {{ with .Params.style }}style="background-color: {{ .background }}; color: {{ .color }};"{{ end }}>
|
||||
{{ .LinkTitle }}
|
||||
</a>
|
||||
{{ end }}
|
||||
</header>
|
||||
{{ end }}
|
||||
|
||||
<div class="article-title-wrapper">
|
||||
<h2 class="article-title">
|
||||
<a href="{{ .RelPermalink }}">
|
||||
{{- .Title -}}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
{{ with .Params.description }}
|
||||
<h3 class="article-subtitle">
|
||||
{{ . }}
|
||||
</h3>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{ $showReadingTime := .Params.readingTime | default (.Site.Params.article.readingTime) }}
|
||||
{{ $showDate := not .Date.IsZero }}
|
||||
{{ $showFooter := or $showDate $showReadingTime }}
|
||||
{{ if $showFooter }}
|
||||
<footer class="article-time">
|
||||
{{ if $showDate }}
|
||||
<div>
|
||||
{{ partial "helper/icon" "date" }}
|
||||
<time class="article-time--published" datetime='{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}'>
|
||||
{{- .Date | time.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
|
||||
</time>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ if $showReadingTime }}
|
||||
<div>
|
||||
{{ partial "helper/icon" "clock" }}
|
||||
<time class="article-time--reading">
|
||||
{{ T "article.readingTime" .ReadingTime }}
|
||||
</time>
|
||||
</div>
|
||||
{{ end }}
|
||||
</footer>
|
||||
{{ end }}
|
||||
|
||||
{{ if .IsTranslated }}
|
||||
<footer class="article-translations">
|
||||
{{ partial "helper/icon" "language" }}
|
||||
<div>
|
||||
{{ range .Translations }}
|
||||
<a href="{{ .Permalink }}" class="link">{{ .Language.LanguageName }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</footer>
|
||||
{{ end }}
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
<footer class="article-footer">
|
||||
{{ partial "article/components/tags" . }}
|
||||
|
||||
{{ if and (.Site.Params.article.license.enabled) (not (eq .Params.license false)) }}
|
||||
<section class="article-copyright">
|
||||
{{ partial "helper/icon" "copyright" }}
|
||||
<span>{{ default .Site.Params.article.license.default .Params.license | markdownify }}</span>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
{{- if ne .Lastmod .Date -}}
|
||||
<section class="article-lastmod">
|
||||
{{ partial "helper/icon" "clock" }}
|
||||
<span>
|
||||
{{ T "article.lastUpdatedOn" }} {{ .Lastmod | time.Format ( or .Site.Params.dateFormat.lastUpdated "Jan 02, 2006 15:04 MST" ) }}
|
||||
</span>
|
||||
</section>
|
||||
{{- end -}}
|
||||
</footer>
|
||||
@@ -0,0 +1,35 @@
|
||||
<header class="article-header">
|
||||
{{- $image := partialCached "helper/image" (dict "Context" . "Type" "article") .RelPermalink "article" -}}
|
||||
{{ if $image.exists }}
|
||||
<div class="article-image">
|
||||
<a href="{{ .RelPermalink }}">
|
||||
{{ if $image.resource }}
|
||||
{{- $Permalink := $image.resource.RelPermalink -}}
|
||||
{{- $Width := $image.resource.Width -}}
|
||||
{{- $Height := $image.resource.Height -}}
|
||||
{{- $Srcset := "" -}}
|
||||
|
||||
{{- if (default true .Page.Site.Params.imageProcessing.cover.enabled) -}}
|
||||
{{- $thumbnail := $image.resource.Resize "800x" -}}
|
||||
{{- $thumbnailRetina := $image.resource.Resize "1600x" -}}
|
||||
{{- $Srcset = printf "%s 800w, %s 1600w" $thumbnail.RelPermalink $thumbnailRetina.RelPermalink -}}
|
||||
{{- $Permalink = $thumbnail.RelPermalink -}}
|
||||
{{- $Width = $thumbnail.Width -}}
|
||||
{{- $Height = $thumbnail.Height -}}
|
||||
{{- end -}}
|
||||
|
||||
<img src="{{ $Permalink }}"
|
||||
{{ with $Srcset }}srcset="{{ . }}"{{ end }}
|
||||
width="{{ $Width }}"
|
||||
height="{{ $Height }}"
|
||||
loading="lazy"
|
||||
alt="Featured image of post {{ .Title }}" />
|
||||
{{ else }}
|
||||
<img src="{{ $image.permalink }}" loading="lazy" alt="Featured image of post {{ .Title }}" />
|
||||
{{ end }}
|
||||
</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ partialCached "article/components/details" . .RelPermalink }}
|
||||
</header>
|
||||
@@ -0,0 +1,30 @@
|
||||
<div class="article-list--compact links">
|
||||
{{ range $i, $link := .Params.links }}
|
||||
<article>
|
||||
<a href="{{ $link.website }}" target="_blank" rel="noopener">
|
||||
<div class="article-details">
|
||||
<h2 class="article-title">
|
||||
{{- $link.title -}}
|
||||
</h2>
|
||||
<footer class="article-time">
|
||||
{{ with $link.description }}
|
||||
{{ . }}
|
||||
{{ else }}
|
||||
{{ $link.website }}
|
||||
{{ end }}
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
{{ with $link.image }}
|
||||
{{ $permalink := . }}
|
||||
{{ with ($.Resources.GetMatch (printf "%s" (. | safeURL))) }}
|
||||
{{ $permalink = .RelPermalink }}
|
||||
{{ end }}
|
||||
<div class="article-image">
|
||||
<img src="{{ $permalink }}" loading="lazy"{{ with $link.alt }} alt="{{ . }}"{{ end }}>
|
||||
</div>
|
||||
{{ end }}
|
||||
</a>
|
||||
</article>
|
||||
{{ end }}
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
{{- partial "helper/external" (dict "Context" . "Namespace" "KaTeX") -}}
|
||||
<script>
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const elementsToRender = [".main-article", ".widget--toc"];
|
||||
|
||||
elementsToRender.forEach(selector => {
|
||||
const element = document.querySelector(selector);
|
||||
if (element) {
|
||||
renderMathInElement(element, {
|
||||
delimiters: [
|
||||
{ left: "$$", right: "$$", display: true },
|
||||
{ left: "$", right: "$", display: false },
|
||||
{ left: "\\(", right: "\\)", display: false },
|
||||
{ left: "\\[", right: "\\]", display: true }
|
||||
],
|
||||
ignoredClasses: ["gist"]
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,68 @@
|
||||
<!-- Root element of PhotoSwipe. Must have class pswp. -->
|
||||
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
|
||||
<!-- Background of PhotoSwipe.
|
||||
It's a separate element as animating opacity is faster than rgba(). -->
|
||||
<div class="pswp__bg"></div>
|
||||
|
||||
<!-- Slides wrapper with overflow:hidden. -->
|
||||
<div class="pswp__scroll-wrap">
|
||||
|
||||
<!-- Container that holds slides.
|
||||
PhotoSwipe keeps only 3 of them in the DOM to save memory.
|
||||
Don't modify these 3 pswp__item elements, data is added later on. -->
|
||||
<div class="pswp__container">
|
||||
<div class="pswp__item"></div>
|
||||
<div class="pswp__item"></div>
|
||||
<div class="pswp__item"></div>
|
||||
</div>
|
||||
|
||||
<!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
|
||||
<div class="pswp__ui pswp__ui--hidden">
|
||||
|
||||
<div class="pswp__top-bar">
|
||||
|
||||
<!-- Controls are self-explanatory. Order can be changed. -->
|
||||
|
||||
<div class="pswp__counter"></div>
|
||||
|
||||
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
|
||||
|
||||
<button class="pswp__button pswp__button--share" title="Share"></button>
|
||||
|
||||
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
|
||||
|
||||
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
|
||||
|
||||
<!-- Preloader demo https://codepen.io/dimsemenov/pen/yyBWoR -->
|
||||
<!-- element will get class pswp__preloader--active when preloader is running -->
|
||||
<div class="pswp__preloader">
|
||||
<div class="pswp__preloader__icn">
|
||||
<div class="pswp__preloader__cut">
|
||||
<div class="pswp__preloader__donut"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
|
||||
<div class="pswp__share-tooltip"></div>
|
||||
</div>
|
||||
|
||||
<button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
|
||||
</button>
|
||||
|
||||
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
|
||||
</button>
|
||||
|
||||
<div class="pswp__caption">
|
||||
<div class="pswp__caption__center"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{- partial "helper/external" (dict "Context" . "Namespace" "PhotoSwipe") -}}
|
||||
@@ -0,0 +1,13 @@
|
||||
{{ $related := (where (.Site.RegularPages.Related .) "Params.hidden" "!=" true) | first 5 }}
|
||||
{{ with $related }}
|
||||
<aside class="related-content--wrapper">
|
||||
<h2 class="section-title">{{ T "article.relatedContent" }}</h2>
|
||||
<div class="related-content">
|
||||
<div class="flex article-list--tile">
|
||||
{{ range . }}
|
||||
{{ partial "article-list/tile" (dict "context" . "size" "250x150" "Type" "articleList") }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,7 @@
|
||||
{{ if .Params.Tags }}
|
||||
<section class="article-tags">
|
||||
{{ range (.GetTerms "tags") }}
|
||||
<a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
|
||||
{{ end }}
|
||||
</section>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,17 @@
|
||||
<!-- Use site subtitle by default -->
|
||||
{{ $description := .Site.Params.sidebar.subtitle }}
|
||||
|
||||
<!-- Seprate description exists -->
|
||||
{{ if .Site.Params.description }}
|
||||
{{ $description = .Site.Params.description }}
|
||||
{{ end }}
|
||||
|
||||
{{ if .Description }}
|
||||
<!-- Page description exists -->
|
||||
{{ $description = .Description }}
|
||||
{{ else if .IsPage }}
|
||||
<!-- Use page summary -->
|
||||
{{ $description = .Summary }}
|
||||
{{ end }}
|
||||
|
||||
{{ return (replaceRE "\n" " " $description | plainify) }}
|
||||
@@ -0,0 +1,45 @@
|
||||
{{- $title := .Title -}}
|
||||
{{- $siteTitle := .Site.Title -}}
|
||||
|
||||
{{- if .IsHome -}}
|
||||
<!-- Homepage, and it's pagination -->
|
||||
|
||||
<!-- Build paginator -->
|
||||
{{ $pages := where .Site.RegularPages "Section" "in" .Site.Params.mainSections }}
|
||||
{{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }}
|
||||
{{ $filtered := ($pages | intersect $notHidden) }}
|
||||
{{ $pag := .Paginate ($filtered) }}
|
||||
|
||||
{{ if .Paginator.HasPrev }}
|
||||
<!-- Paginated. Append page number to title -->
|
||||
{{ $title = printf "%s - %s" .Paginator $siteTitle }}
|
||||
{{ else }}
|
||||
{{ $title = $siteTitle}}
|
||||
{{ end }}
|
||||
{{- else if eq .Kind "term" -}}
|
||||
<!-- Taxonomy page -->
|
||||
|
||||
<!-- Build paginator -->
|
||||
{{ $notHidden := where .Pages "Params.hidden" "!=" true }}
|
||||
{{ $pag := .Paginate ($notHidden) }}
|
||||
|
||||
<!-- {TAXONOMY_TYPE}: {TAXONOMY_TERM} -->
|
||||
{{ $convertedKey := slice .Data.Singular ".translation"}}
|
||||
{{ $keyString := delimit $convertedKey "" }}
|
||||
{{/* warnf "调试信息: .Data.Singular = %s" .Data.Singular */}}
|
||||
{{/* warnf "调试信息: $convertedKey = %v" $convertedKey */}}
|
||||
{{/* warnf "调试信息: $keyString = %s" $keyString */}}
|
||||
{{ $singularName := T $keyString }}
|
||||
{{/* warnf "调试信息: $singularName = %s" $singularName */}}
|
||||
{{ $title = slice $singularName ": " $title }}
|
||||
|
||||
{{ if .Paginator.HasPrev }}
|
||||
<!-- Add page number-->
|
||||
{{ $title = $title | append " - " .Paginator }}
|
||||
{{ end }}
|
||||
|
||||
{{ $title = $title | append " - " $siteTitle }}
|
||||
{{ $title = delimit $title "" }}
|
||||
{{- end -}}
|
||||
|
||||
{{ return $title }}
|
||||
@@ -0,0 +1,12 @@
|
||||
{{- partial "helper/external" (dict "Context" . "Namespace" "Vibrant") -}}
|
||||
|
||||
{{- $opts := dict "minify" hugo.IsProduction -}}
|
||||
{{- $script := resources.Get "ts/main.ts" | js.Build $opts | fingerprint -}}
|
||||
|
||||
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
|
||||
|
||||
{{- with resources.Get "ts/custom.ts" -}}
|
||||
{{/* Place your custom script in HUGO_SITE_FOLDER/assets/ts/custom.ts */}}
|
||||
{{- $customScript := . | js.Build $opts | fingerprint -}}
|
||||
<script type="text/javascript" src="{{ $customScript.RelPermalink }}" defer></script>
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,27 @@
|
||||
{{- $ThemeVersion := "3.32.0" -}}
|
||||
<footer class="site-footer">
|
||||
<section class="copyright">
|
||||
©
|
||||
{{ if and (.Site.Params.footer.since) (ne .Site.Params.footer.since (int (now.Format "2006"))) }}
|
||||
{{ .Site.Params.footer.since }} -
|
||||
{{ end }}
|
||||
{{ now.Format "2006" }} {{ default .Site.Title .Site.Copyright }}
|
||||
</section>
|
||||
|
||||
<section class="powerby">
|
||||
{{ with .Site.Params.footer.customText }}
|
||||
{{ . | safeHTML }} <br/>
|
||||
{{ end }}
|
||||
|
||||
{{- $Generator := `<a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a>` -}}
|
||||
{{- $Theme := printf `<b><a href="https://github.com/CaiJimmy/hugo-theme-stack" target="_blank" rel="noopener" data-version="%s">Stack</a></b>` $ThemeVersion -}}
|
||||
{{- $DesignedBy := `<a href="https://jimmycai.com" target="_blank" rel="noopener">Jimmy</a>` -}}
|
||||
{{- $ICP := `<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener"><u>京ICP备2026002012号</u></a>` -}}
|
||||
{{- $MPS := `<img src="/beian.png" width="15" alt="公安备案"/> <a href="https://beian.mps.gov.cn/#/query/webSearch?code=11010802047341" rel="noopener noreferrer" target="_blank"><u>京公网安备11010802047341号</u></a>` -}}
|
||||
|
||||
{{ T "footer.builtWith" (dict "Generator" $Generator) | safeHTML }} <br />
|
||||
{{ T "footer.designedBy" (dict "Theme" $Theme "DesignedBy" $DesignedBy) | safeHTML }}<br />
|
||||
{{ $ICP | safeHTML }}<br />
|
||||
{{ $MPS | safeHTML }}
|
||||
</section>
|
||||
</footer>
|
||||
@@ -0,0 +1,2 @@
|
||||
{{ partialCached "footer/components/script.html" . }}
|
||||
{{ partial "footer/custom.html" . }}
|
||||
@@ -0,0 +1,39 @@
|
||||
{{- $defaultColorScheme := default "auto" .Site.Params.colorScheme.default -}}
|
||||
{{- if not (default false .Site.Params.colorScheme.toggle) -}}
|
||||
{{/* If toggle is disabled, force default scheme */}}
|
||||
<script>
|
||||
(function() {
|
||||
const colorSchemeKey = 'StackColorScheme';
|
||||
localStorage.setItem(colorSchemeKey, "{{ $defaultColorScheme }}");
|
||||
})();
|
||||
</script>
|
||||
{{- else -}}
|
||||
{{/* Otherwise set to default scheme only if no preference is set by user */}}
|
||||
<script>
|
||||
(function() {
|
||||
const colorSchemeKey = 'StackColorScheme';
|
||||
if(!localStorage.getItem(colorSchemeKey)){
|
||||
localStorage.setItem(colorSchemeKey, "{{ $defaultColorScheme }}");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{{- end -}}
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const colorSchemeKey = 'StackColorScheme';
|
||||
const colorSchemeItem = localStorage.getItem(colorSchemeKey);
|
||||
const supportDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches === true;
|
||||
|
||||
if (colorSchemeItem == 'dark' || colorSchemeItem === 'auto' && supportDarkMode) {
|
||||
/**
|
||||
* Enable dark mode if:
|
||||
* 1. If dark mode is set already (in local storage)
|
||||
* 2. Auto mode & prefere color scheme is dark
|
||||
*/
|
||||
document.documentElement.dataset.scheme = 'dark';
|
||||
} else {
|
||||
document.documentElement.dataset.scheme = 'light';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,26 @@
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||
|
||||
{{- $description := partialCached "data/description" . .RelPermalink -}}
|
||||
<meta name='description' {{ printf "content=%q" $description | safeHTMLAttr }}>
|
||||
{{ with .Params.Keywords }}<meta name="keywords" content="{{ delimit . ", " }}">{{ end }}
|
||||
|
||||
{{- $title := partial "data/title" . -}}
|
||||
<title>{{ $title }}</title>
|
||||
|
||||
<link rel='canonical' href='{{ .Permalink }}'>
|
||||
|
||||
{{- partial "head/style.html" . -}}
|
||||
{{- partial "head/script.html" . -}}
|
||||
{{- partial "head/opengraph/include.html" . -}}
|
||||
|
||||
{{- range .AlternativeOutputFormats -}}
|
||||
<link rel="{{ .Rel }}" type="{{ .MediaType.Type }}" href="{{ .Permalink | safeURL }}">
|
||||
{{- end -}}
|
||||
|
||||
{{ with .Site.Params.favicon }}
|
||||
<link rel="shortcut icon" href="{{ . | relURL }}" />
|
||||
{{ end }}
|
||||
|
||||
{{- partial "google_analytics.html" . -}}
|
||||
{{- partial "head/custom.html" . -}}
|
||||
@@ -0,0 +1,2 @@
|
||||
{{ partial "head/opengraph/provider/base" . }}
|
||||
{{ partial "head/opengraph/provider/twitter" . }}
|
||||
@@ -0,0 +1,43 @@
|
||||
{{- $title := partialCached "data/title" . .RelPermalink -}}
|
||||
{{- $description := partialCached "data/description" . .RelPermalink -}}
|
||||
|
||||
<meta property='og:title' {{ printf "content=%q" $title | safeHTMLAttr }}>
|
||||
<meta property='og:description' {{ printf "content=%q" $description | safeHTMLAttr }}>
|
||||
<meta property='og:url' content='{{ .Permalink }}'>
|
||||
<meta property='og:site_name' content='{{ .Site.Title }}'>
|
||||
<meta property='og:type' content='
|
||||
{{- if .IsPage -}}
|
||||
article
|
||||
{{- else -}}
|
||||
website
|
||||
{{- end -}}
|
||||
'>
|
||||
|
||||
{{- with .Params.locale -}}
|
||||
<meta property='og:locale' content='{{ . }}'>
|
||||
{{- end -}}
|
||||
|
||||
{{- if .IsPage -}}
|
||||
<meta property='article:section' content='{{ .Section | title }}' />
|
||||
{{- range .Params.tags -}}
|
||||
<meta property='article:tag' content='{{ . }}' />
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if .IsPage -}}
|
||||
{{- if not .Date.IsZero -}}
|
||||
<meta property='article:published_time' content='{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}'/>
|
||||
{{- end -}}
|
||||
{{- if not .Lastmod.IsZero -}}
|
||||
<meta property='article:modified_time' content='{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}'/>
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- if not .Site.Lastmod.IsZero -}}
|
||||
<meta property='og:updated_time' content='{{ .Site.Lastmod.Format " 2006-01-02T15:04:05-07:00 " | safeHTML }}'/>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{ $image := partialCached "helper/image" (dict "Context" . "Type" "opengraph") .RelPermalink "opengraph" }}
|
||||
{{- if $image.exists -}}
|
||||
<meta property='og:image' content='{{ absURL $image.permalink }}' />
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,16 @@
|
||||
{{- with .Site.Params.opengraph.twitter.site -}}
|
||||
<meta name="twitter:site" content="@{{ . }}">
|
||||
<meta name="twitter:creator" content="@{{ . }}">
|
||||
{{- end -}}
|
||||
|
||||
{{- $title := partialCached "data/title" . .RelPermalink -}}
|
||||
{{- $description := partialCached "data/description" . .RelPermalink -}}
|
||||
|
||||
<meta name="twitter:title" {{ printf "content=%q" $title | safeHTMLAttr }}>
|
||||
<meta name="twitter:description" {{ printf "content=%q" $description | safeHTMLAttr }}>
|
||||
|
||||
{{- $image := partialCached "helper/image" (dict "Context" . "Type" "opengraph") .RelPermalink "opengraph" -}}
|
||||
{{- if $image.exists -}}
|
||||
<meta name="twitter:card" content="{{ default `summary_large_image` .Site.Params.opengraph.twitter.card }}">
|
||||
<meta name="twitter:image" content='{{ absURL $image.permalink }}' />
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{ $sass := resources.Get "scss/style.scss" }}
|
||||
{{ $style := $sass | toCSS | minify | resources.Fingerprint "sha256" }}
|
||||
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
|
||||
@@ -0,0 +1,29 @@
|
||||
{{- $List := index .Context.Site.Data.external .Namespace -}}
|
||||
{{- with $List -}}
|
||||
{{- range . -}}
|
||||
{{- if eq .type "script" -}}
|
||||
<script
|
||||
src="{{ .src }}"
|
||||
{{- with .integrity -}}
|
||||
integrity="{{ . }}"
|
||||
{{- end -}}
|
||||
crossorigin="anonymous"
|
||||
{{ if .defer }}defer{{ end }}
|
||||
>
|
||||
</script>
|
||||
{{- else if eq .type "style" -}}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ .src }}"
|
||||
{{- with .integrity -}}
|
||||
integrity="{{ . }}"
|
||||
{{- end -}}
|
||||
crossorigin="anonymous"
|
||||
>
|
||||
{{- else -}}
|
||||
{{- errorf "Error: unknown external resource type: %s" .type -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- errorf "Error: external resource '%s' is not found" .Namespace -}}
|
||||
{{- end -}}
|
||||