mirror of
https://github.com/wanglin2/obsidian-simplemindmap.git
synced 2026-07-22 07:11:13 +00:00
0.1.5版本
This commit is contained in:
parent
9b9c037be7
commit
6eccecf6d5
117 changed files with 4959 additions and 5988 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
674
LICENSE
674
LICENSE
|
|
@ -1,674 +0,0 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2025 Obsidian-simple-mind-map.
|
||||
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>.
|
||||
13
README.md
13
README.md
|
|
@ -8,12 +8,16 @@
|
|||
|
||||
mind-map 也提供了独立的思维导图客户端,可以点击[客户端](https://github.com/wanglin2/mind-map/releases)了解更多。
|
||||
|
||||
独立客户端下载(Windows、macOS、Linux):[点击这里](https://github.com/wanglin2/mind-map/releases)
|
||||
|
||||
更新记录:[中文](./docs/changelog_zh.md) | [English](./docs/changelog.md)
|
||||
|
||||
使用帮助:[中文](./docs/help_zh.md) | [English](./docs/help.md)
|
||||
|
||||
图床配置帮助:[中文](./docs/imageHosting_zh.md) | [English](./docs/imageHosting.md)
|
||||
|
||||
# 功能清单
|
||||
|
||||
- 快捷键
|
||||
|
||||
从`0.1.5+`开始,插件不再提供默认的快捷键,全部转为ob的命令,你可以自行设置命令对应的快捷键。该[文档](./ShortcutKeySettingReference.md)列出了原来的快捷键,可供参考。
|
||||
|
||||
- 思维导图本身的功能可从[mind-map](https://github.com/wanglin2/mind-map)项目了解更多,也可以在这里查[看常见问题](./Help_zh.md)。
|
||||
|
||||
- 思维导图文件格式
|
||||
|
|
@ -183,4 +187,3 @@ mind-map 也提供了独立的思维导图客户端,可以点击[客户端](ht
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
|
|
|||
14
README_en.md
14
README_en.md
|
|
@ -8,12 +8,16 @@ The mind map functionality is built upon the [mind-map](https://github.com/wangl
|
|||
|
||||
mind-map also provides a standalone mind map client. Learn more about the [Client](https://github.com/wanglin2/mind-map/releases).
|
||||
|
||||
Download standalone client(Windows、macOS、Linux): [Click here](https://github.com/wanglin2/mind-map/releases)
|
||||
|
||||
Changelog: [中文](./docs/changelog_zh.md) | [English](./docs/changelog.md)
|
||||
|
||||
Help: [中文](./docs/help_zh.md) | [English](./docs/help.md)
|
||||
|
||||
Image Hosting Help: [中文](./docs/imageHosting_zh.md) | [English](./docs/imageHosting.md)
|
||||
|
||||
# Feature List
|
||||
|
||||
- Shortcuts
|
||||
|
||||
Starting from '0.1.5+', the plugin no longer provides default shortcut keys, all are converted to ob commands, and you can set the corresponding shortcut keys for the commands yourself. This [document](./ShortcutKeySettingReference_en.md) lists the original shortcut keys for reference.
|
||||
|
||||
- For mind map features, refer to the [mind-map](https://github.com/wanglin2/mind-map) project. You can also check [Common Issues](./Help.md) here.
|
||||
|
||||
- Mind Map File Format
|
||||
|
|
@ -134,4 +138,4 @@ mind-map also provides a standalone mind map client. Learn more about the [Clien
|
|||
|
||||
# Bugs/Suggestions/Requests
|
||||
|
||||
Submit via [Issues](https://github.com/wanglin2/obsidian-simplemindmap/issues).
|
||||
Submit via [Issues](https://github.com/wanglin2/obsidian-simplemindmap/issues).
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
# 自定义命令快捷键参考
|
||||
|
||||
从0.1.5+开始,插件不再提供默认的快捷键,全部转为ob的命令,你可以自行设置命令对应的快捷键。本文档给出原默认的快捷键,可供参考。
|
||||
|
||||
> 本文档中提到的Ctrl键,在Mac系统中,对应的是Command键
|
||||
>
|
||||
> 快捷键中的`|`表示或、`+`表示与
|
||||
>
|
||||
> Obsidian不支持设置单个键的快捷键,比如单独的Enter、Tab、Del等,建议使用组合键,比如`Ctrl + Enter`等
|
||||
|
||||
## 编辑操作
|
||||
|
||||
- 保存
|
||||
|
||||
`Ctrl + S`
|
||||
|
||||
- 保存并更新图像数据
|
||||
|
||||
`Ctrl + Shfit + S`
|
||||
|
||||
## 节点操作
|
||||
|
||||
- 插入下级节点
|
||||
|
||||
`Tab | Insert`
|
||||
|
||||
- 插入同级节点
|
||||
|
||||
`Enter`
|
||||
|
||||
- 插入父节点
|
||||
|
||||
`Shift + Tab`
|
||||
|
||||
- 上移节点
|
||||
|
||||
`Ctrl + ↑`
|
||||
|
||||
- 下移节点
|
||||
|
||||
`Ctrl + ↓`
|
||||
|
||||
- 插入概要
|
||||
|
||||
`Ctrl + G`
|
||||
|
||||
- 展开/收起节点
|
||||
|
||||
`/`
|
||||
|
||||
- 删除节点
|
||||
|
||||
`Delete | Backspace`
|
||||
|
||||
- 仅删除当前节点
|
||||
|
||||
`Shift + Backspace`
|
||||
|
||||
- 复制节点
|
||||
|
||||
`Ctrl + C`
|
||||
|
||||
- 剪切节点
|
||||
|
||||
`Ctrl + X`
|
||||
|
||||
- 粘贴节点
|
||||
|
||||
`Ctrl + V`
|
||||
|
||||
- 编辑节点
|
||||
|
||||
`F2`
|
||||
|
||||
- 回退
|
||||
|
||||
`Ctrl + Z`
|
||||
|
||||
- 前进
|
||||
|
||||
`Ctrl + Y`
|
||||
|
||||
- 全选
|
||||
|
||||
`Ctrl + A`
|
||||
|
||||
- 一键整理布局
|
||||
|
||||
`Ctrl + R`
|
||||
|
||||
- 搜索和替换
|
||||
|
||||
`Ctrl + F`
|
||||
|
||||
- 切换粗体
|
||||
|
||||
`Control + B`
|
||||
|
||||
- 切换斜体
|
||||
|
||||
`Control + I`
|
||||
|
||||
- 切换下划线
|
||||
|
||||
`Control + U`
|
||||
|
||||
- 切换删除线
|
||||
|
||||
`Control + [`
|
||||
|
||||
- 增大字号
|
||||
|
||||
`Control + Shift + =`
|
||||
|
||||
- 减小字号
|
||||
|
||||
`Control + Shift + -`
|
||||
|
||||
## 画布操作
|
||||
|
||||
- 放大
|
||||
|
||||
`Ctrl + +`
|
||||
|
||||
- 缩小
|
||||
|
||||
`Ctrl + -`
|
||||
|
||||
- 回到根节点
|
||||
|
||||
`Ctrl + Enter`
|
||||
|
||||
- 适应画布
|
||||
|
||||
`Ctrl + Shift + I`
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
# Custom Command Shortcut Reference
|
||||
|
||||
Starting from version 0.1.5+, the plugin no longer provides default shortcuts. All commands have been converted to OB commands, allowing you to set custom shortcuts for each command. This document lists the original default shortcuts for reference.
|
||||
|
||||
> In this document, the Ctrl key corresponds to the Command key on Mac systems.
|
||||
>
|
||||
> The `|` symbol in shortcuts indicates "or," while `+` indicates "and."
|
||||
>
|
||||
> Obsidian does not support setting individual keys as shortcuts, such as `Enter`, `Tab`, `Del`, etc. It is recommended to use composite keys, such as `Ctrl + Enter`.
|
||||
|
||||
## Editing Operations
|
||||
|
||||
- Save
|
||||
|
||||
`Ctrl + S`
|
||||
|
||||
- Save and Update Image Data
|
||||
|
||||
`Ctrl + Shift + S`
|
||||
|
||||
## Node Operations
|
||||
|
||||
- Insert Child Node
|
||||
|
||||
`Tab | Insert`
|
||||
|
||||
- Insert Sibling Node
|
||||
|
||||
`Enter`
|
||||
|
||||
- Insert Parent Node
|
||||
|
||||
`Shift + Tab`
|
||||
|
||||
- Move Node Up
|
||||
|
||||
`Ctrl + ↑`
|
||||
|
||||
- Move Node Down
|
||||
|
||||
`Ctrl + ↓`
|
||||
|
||||
- Insert Summary
|
||||
|
||||
`Ctrl + G`
|
||||
|
||||
- Expand/Collapse Node
|
||||
|
||||
`/`
|
||||
|
||||
- Delete Node
|
||||
|
||||
`Delete | Backspace`
|
||||
|
||||
- Delete Current Node Only
|
||||
|
||||
`Shift + Backspace`
|
||||
|
||||
- Copy Node
|
||||
|
||||
`Ctrl + C`
|
||||
|
||||
- Cut Node
|
||||
|
||||
`Ctrl + X`
|
||||
|
||||
- Paste Node
|
||||
|
||||
`Ctrl + V`
|
||||
|
||||
- Edit Node
|
||||
|
||||
`F2`
|
||||
|
||||
- Undo
|
||||
|
||||
`Ctrl + Z`
|
||||
|
||||
- Redo
|
||||
|
||||
`Ctrl + Y`
|
||||
|
||||
- Select All
|
||||
|
||||
`Ctrl + A`
|
||||
|
||||
- Auto-arrange Layout
|
||||
|
||||
`Ctrl + R`
|
||||
|
||||
- Search and Replace
|
||||
|
||||
`Ctrl + F`
|
||||
|
||||
- Toggle Bold
|
||||
|
||||
`Control + B`
|
||||
|
||||
- Toggle Italic
|
||||
|
||||
`Control + I`
|
||||
|
||||
- Toggle Underline
|
||||
|
||||
`Control + U`
|
||||
|
||||
- Toggle Strikethrough
|
||||
|
||||
`Control + [`
|
||||
|
||||
- Increase Font Size
|
||||
|
||||
`Control + Shift + =`
|
||||
|
||||
- Decrease Font Size
|
||||
|
||||
`Control + Shift + -`
|
||||
|
||||
## Canvas Operations
|
||||
|
||||
- Zoom In
|
||||
|
||||
`Ctrl + +`
|
||||
|
||||
- Zoom Out
|
||||
|
||||
`Ctrl + -`
|
||||
|
||||
- Return to Root Node
|
||||
|
||||
`Ctrl + Enter`
|
||||
|
||||
- Fit to Canvas
|
||||
|
||||
`Ctrl + Shift + I`
|
||||
209
docs/changelog.md
Normal file
209
docs/changelog.md
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
# v0.0.0
|
||||
|
||||
1:
|
||||
- Save files in Markdown format as `.smm.md`. The "More" menu in the top-right corner now supports switching to Markdown editing mode.
|
||||
- Node hyperlinks now support linking to Obsidian (`.md`) files.
|
||||
- Split-view mode now synchronizes the mind map’s core data (basic configurations and settings are not synchronized).
|
||||
- Clicking the save button in the top-right stores the current preview image, which can then be embedded into Markdown documents using `![[.smm.md]]` to display as an image. The image updates in real time when the source file is modified, and double-clicking the image opens the mind map editor.
|
||||
- Node statistics have been added to the bottom status bar.
|
||||
- A basic multilingual setting has been implemented.
|
||||
|
||||
2:
|
||||
- Mind map and SVG data are now compressed and encoded before saving.
|
||||
- Fixed duplicate entries for the **Open as Markdown Document** option in the top-right "More" menu.
|
||||
- Mind map settings are now persisted across sessions.
|
||||
- Added settings for specifying mind map file path, image file path, default structure, default theme, and auto-save interval.
|
||||
- Theme mode can now be set to: Follow Obsidian, Dark, or Light.
|
||||
- Files are automatically saved before closing a tab to prevent data loss.
|
||||
- Uploaded node and background images are stored in the vault; only file paths are saved in the mind map data to reduce file size.
|
||||
- Node images can now be selected directly from images already in the vault.
|
||||
- Embedded previews now use PNG instead of SVG. This significantly reduces saved data size but sacrifices SVG’s high resolution.
|
||||
- Added **Copy as Obsidian Internal Link** to the canvas right-click menu.
|
||||
- Added a "Saving..." indicator next to the save button.
|
||||
- Node text input now supports typing `[[` to trigger a popup listing Obsidian files for inserting internal links.
|
||||
- Obsidian files in the hyperlink dialog now support search.
|
||||
- Notification messages and tooltips have been migrated from Element UI to Obsidian’s native components.
|
||||
- Theme colors now follow Obsidian’s theme; non-Element UI component colors dynamically adapt to Obsidian’s theme.
|
||||
- Added a button in the top-right corner to switch to outline editing mode.
|
||||
|
||||
3:
|
||||
- **Multilingual support**:
|
||||
- Automatically follows Obsidian’s language setting.
|
||||
- Plugin code now fully supports Chinese, English, Vietnamese, and Traditional Chinese.
|
||||
- Image upload dialog for node images now supports dragging images directly from the Obsidian file list.
|
||||
- Pasted images are now automatically saved as files in the vault.
|
||||
- Added **Copy as Obsidian Internal Link** to the node right-click menu; opening the link automatically focuses on that node.
|
||||
- **Internal Links**:
|
||||
- Improved editing of rich-text internal links within nodes: during editing, raw syntax like `[[xxx]]` is shown; after editing, it renders as a clickable link. Supports suffixes like `#`, `^`, and `|`.
|
||||
- Unified handling of internal links in both node hyperlinks and node text; updates are synchronized to the Markdown document.
|
||||
- Fixed ambiguity when inserting internal links to files with identical names in different directories.
|
||||
- Deduplication of internal links.
|
||||
- Support embedding SMM-language code blocks in Markdown documents:
|
||||
- Simple mind map editing is possible within the code block; all keyboard shortcuts and rich-text toolbar functions are available.
|
||||
- Initial height can be set and adjusted by dragging.
|
||||
- Action buttons: Return to root node, Fit to canvas.
|
||||
- Command: Insert a mind map code block into the current document.
|
||||
- File selection popups for node text now support keyboard navigation (arrow keys and Enter).
|
||||
- Fixed abnormal rendering in the minimap.
|
||||
- Fixed errors in presentation mode.
|
||||
- Outline view now supports displaying and adding internal links.
|
||||
- Improved UI styling, layout, and dark mode compatibility.
|
||||
|
||||
4:
|
||||
- When importing `.smm`, `.json`, or `.xmind` files, base64-encoded node images are converted and stored in the vault.
|
||||
- Hyperlink icons now visually distinguish between Obsidian files and web URLs.
|
||||
- When exporting to `.smm`, `.json`, or `.xmind`, images using Obsidian paths are converted to base64.
|
||||
- Added links in settings for submitting issues and downloading the desktop client.
|
||||
- Double-clicking an embedded preview without an image still navigates to the mind map editor.
|
||||
- Hyperlinks now support adding local files; selected files are uploaded to the vault, and the storage path can be customized in settings.
|
||||
- Image compression for uploads is now supported; compression settings (enabled/disabled and parameters) are configurable.
|
||||
- Added setting to control whether double-clicking an `![[]]` embedded preview opens in a new tab.
|
||||
- Added setting to toggle transparent background for embedded previews.
|
||||
- Mind map file storage directory can be set to: vault root, a specified folder, or the same folder as the current file.
|
||||
- Image and general file storage directories support four options: vault root, specified folder, current file’s folder, or a subfolder under the current file’s folder.
|
||||
- Added settings for prefix and datetime format in new mind map filenames.
|
||||
- Added onboarding guidance for first-time plugin users.
|
||||
|
||||
5:
|
||||
- Added setting to extend the list of fonts available in mind maps.
|
||||
- Directory inputs in settings now support selecting vault folders.
|
||||
- Markdown documents can now be previewed as mind maps, with options to switch structure/theme and export as PNG, SVG, or PDF.
|
||||
- Structure options in settings are now localized; single-type structures no longer show redundant index labels.
|
||||
- Added a read-only mode toggle button in the top-right corner.
|
||||
- If a mind map contains image data, embedded previews use PNG; otherwise, SVG is used. SVG previews support transparent backgrounds.
|
||||
- Code block embedding now supports toggling transparent background.
|
||||
- Code block embedding supports switching structure and theme.
|
||||
- Components are now imported on-demand to reduce bundle size and fix interference with Obsidian’s table styles.
|
||||
|
||||
# v0.1.1
|
||||
1. Added **Copy Current Node as Image to Clipboard** to the node right-click menu—can be pasted directly into Obsidian documents.
|
||||
2. Fixed an issue where editing the same file in multiple tabs caused node text to disappear when switching tabs.
|
||||
3. Fixed unresponsive Delete key during text editing when multiple tabs edit the same file.
|
||||
4. Automatically clears active states (e.g., text editing, node activation) when switching tabs.
|
||||
5. Fixed overlap between the bottom toolbar and Obsidian’s status bar.
|
||||
6. Default theme settings now differentiate between light and dark modes.
|
||||
7. Added **Show Bottom Toolbar** toggle in the mind map view’s sidebar settings.
|
||||
8. Intercepted Obsidian’s `Ctrl+S` and `Ctrl+F` shortcuts: `Ctrl+S` now triggers mind map save, and `Ctrl+F` opens the mind map search box.
|
||||
9. Added command to enter presentation mode.
|
||||
10. Fixed failure to auto-update preview images when `![[]]` embeds lacked an initial preview.
|
||||
11. Added a “File deleted” warning when an embedded file is removed.
|
||||
12. Fixed repeated file creation and failures from rapid clicks on the new-file icon due to incorrect directory resolution.
|
||||
13. Fixed ignored width/height settings for embedded previews in reading mode.
|
||||
14. Centered Obsidian whiteboard embeds and limited max dimensions to prevent overflow.
|
||||
15. Supported embedding mind maps as images in Excalidraw (display size issues remain).
|
||||
16. Added **Save and Update Image** command with shortcut `Ctrl+Shift+S`.
|
||||
17. Fixed empty content in the node notes sidebar.
|
||||
18. Holding `Ctrl`/`Command` while clicking a node hyperlink opens it in a new tab.
|
||||
19. Fixed hanging “loading” state when importing invalid Markdown files; added user feedback.
|
||||
|
||||
# v0.1.2
|
||||
1. Added **Enable Search in Obsidian** setting. When enabled, all node text is saved to the file to support Obsidian search; clicking search results auto-focuses the relevant node.
|
||||
2. Added **Check for Updates** setting; checks for new versions on plugin startup.
|
||||
3. Filters out YAML frontmatter when previewing Markdown as a mind map.
|
||||
4. Holding `Shift` enables horizontal panning with the mouse wheel.
|
||||
5. Supports parsing online images in Markdown during import or preview.
|
||||
6. Prioritizes mind map rendering before loading UI elements to improve open speed.
|
||||
7. Preserves user-edited YAML data.
|
||||
8. Supports converting mind map documents to Markdown.
|
||||
9. Supports converting Markdown documents to mind map format.
|
||||
10. Notes images are now uploaded to the vault, consistent with node images.
|
||||
11. Mobile adaptation:
|
||||
- Added right-click menu icon on the left.
|
||||
- Added helper buttons in outline editing.
|
||||
- Disabled Obsidian sidebar during horizontal canvas dragging.
|
||||
- Hidden non-essential buttons.
|
||||
- Disabled onboarding tips.
|
||||
12. Supports dragging files from the vault or local system onto nodes: images are inserted as node images; others become hyperlinks.
|
||||
13. Fixed hidden top-right search box obstructing canvas and node interactions.
|
||||
14. Fixed unintended canvas/toolbox shifts when editing text near canvas edges.
|
||||
15. Added **Save Canvas Position and Zoom** setting.
|
||||
16. Improved Markdown import: if multiple root nodes are detected, a new root wraps them instead of discarding extras.
|
||||
17. Changed UID symbol in **Copy Node as Obsidian Internal Link** from `#` to `#^`.
|
||||
18. When **Enable Search in Obsidian** is on, saved UIDs use `^` instead of `#`.
|
||||
19. Hover previews of `[[ ]]` embeds in reading mode now auto-resize images.
|
||||
|
||||
# v0.1.3
|
||||
1. Fixed `undefined` errors during Markdown import.
|
||||
2. Added **Max Heading Level for Node-to-Markdown Conversion**: nodes above this level become Markdown headings; others become list items.
|
||||
3. Preview image data is now stored as separate files by default:
|
||||
3.1. Added **Store Preview Image as File** setting (enabled by default). When on, SVG preview files are saved in the vault and only paths are stored in the mind map file. When off, base64 data is embedded (increasing file size).
|
||||
3.2. Deleting a mind map file in Obsidian also deletes its preview file. Manual cleanup is needed if deleted via the OS. Auto-deletion may fail in some edge cases.
|
||||
4. Fixed loss of searchable text data when saving in outline mode.
|
||||
5. Optimized layout algorithm for **Logic Structure** diagrams: faster rendering, resolved position conflicts between summary and regular nodes, and fixed missing layout updates after summary node resizing.
|
||||
6. Added confirmation dialogs for mind map ↔ Markdown conversions.
|
||||
7. Import dialog now supports dragging files from the vault or local system.
|
||||
8. File name can now be edited directly in the title bar.
|
||||
|
||||
# v0.1.4
|
||||
1. Fixed ability to drag files onto nodes in read-only mode.
|
||||
2. Node text editing now supports direct internal link input:
|
||||
2.1. Typing `[[` triggers Obsidian file list; navigate with arrow keys, press Enter or click to insert; search supported.
|
||||
2.2. After editing, `[[ ]]` renders as a clickable link; `Ctrl+Click` opens in a new tab.
|
||||
2.3. Supports `#`, `^`, and `|` suffixes in pasted or typed links.
|
||||
3. Simplified new mind map filename format setting into a single field supporting variables.
|
||||
4. Added **Image Paste Naming Format** setting with variable support.
|
||||
5. Added **Open Node Links in New Tab** setting (enabled by default).
|
||||
6. Removed close buttons from all dialogs and disabled closing by clicking outside.
|
||||
7. Fixed canvas background turning into the preview image when double-clicking a node image with transparent background enabled.
|
||||
8. Added "+" button next to the tag input field.
|
||||
9. Added guidance text to the import dialog.
|
||||
10. Added **Bracket** style to line connection options under Basic Styles.
|
||||
11. Summary nodes now support connectors, formulas, and attachments.
|
||||
12. SVG export now supports transparent background.
|
||||
13. Added tooltips to clarify complex settings in the right-side configuration sidebar.
|
||||
|
||||
# v0.1.5
|
||||
**New Features**:
|
||||
1. Added support for free-floating nodes.
|
||||
2. Enhanced internal link editing in node text:
|
||||
- Parses aliases and block references.
|
||||
- Fixed broken link opening.
|
||||
- Typing `【【` auto-corrects to `[[` and triggers file selection.
|
||||
- Creates new files if linked file doesn’t exist.
|
||||
3. Upgraded math formula rendering library for better output.
|
||||
4. Added support for importing `.txt` files.
|
||||
5. Automatic math formula rendering during import of `.mm`, `.xmind`, `.txt`, `.md`, and `.xlsx` files.
|
||||
6. Improved Markdown export:
|
||||
- Preserves and renders bold, italic, and strikethrough.
|
||||
- Preserves icons, tags, hyperlinks, and images.
|
||||
- Wraps summary and note content in code blocks to avoid structural pollution; supports re-import.
|
||||
7. Preserves raw math formula syntax in `.md` and `.txt` exports for reliable re-import.
|
||||
8. Adds `$` delimiters around math formulas in `.xmind` exports for better recognition.
|
||||
9. Node width resizing now snaps to sibling node widths.
|
||||
10. Rainbow lines moved from instance options to theme settings for per-file customization.
|
||||
11. Added node margin controls in the node style sidebar.
|
||||
12. Added **Select All Nodes** to both node and canvas right-click menus.
|
||||
13. Added **Copy as Plain Text** to the node right-click menu.
|
||||
14. Grouped node creation and copy/paste actions into a submenu.
|
||||
15. Added keyboard shortcuts for bold, italic, underline, strikethrough, and font size adjustment on active nodes (may conflict with Obsidian shortcuts).
|
||||
16. Outline sidebar fixes:
|
||||
- Fixed outdated outline after immediate node level changes post-editing.
|
||||
- Fixed outline sync when promoting an already-active node.
|
||||
- `Tab` key now demotes nodes (previously promoted).
|
||||
17. Fullscreen outline editing fixes:
|
||||
- Fixed node text reverting to default after promotion post-editing.
|
||||
- `Tab` key now demotes nodes.
|
||||
18. Added ability (and shortcut) to split text after cursor into a sibling node in outline mode (shortcut may conflict with Obsidian).
|
||||
19. Pasting multi-line text supports preserving or flattening hierarchy.
|
||||
20. With free-drag enabled, holding `Ctrl` ignores hierarchy during dragging.
|
||||
21. Line arrow styling:
|
||||
- **Basic Styles**: set arrow direction.
|
||||
- **Node Styles**: toggle arrow visibility.
|
||||
22. Switched node notes editor from modal to sidebar mode.
|
||||
23. Added support for configuring image hosting services (image beds).
|
||||
|
||||
**Fixes & Optimizations**:
|
||||
1. Fixed failure when converting mind maps to Markdown.
|
||||
2. Fixed duplicate file creation from **New Mind Map** in right-click menu.
|
||||
3. Fixed failed Markdown conversion after duplicating an `.smm` file via **Duplicate** in file explorer.
|
||||
4. Fixed save errors when editing a mind map with no preview file while it’s embedded via `![[]]` in another Markdown file.
|
||||
5. Fixed mismatched preview SVG filenames after renaming mind map files or converting to Markdown.
|
||||
6. Fixed delayed update of `path` metadata after renaming mind map files.
|
||||
7. Fixed background images in SVG exports ignoring stretch settings from Basic Settings.
|
||||
8. Resolved node collision in Directory Organization diagrams.
|
||||
9. Fixed export failures (PNG/SVG/PDF) when node text contained illegal characters.
|
||||
10. Fixed first background image upload failing after uploading a node image.
|
||||
11. Optimized Fishbone diagram code and node outer spacing.
|
||||
12. Improved text visibility in dark theme for level-3+ node text editors (fixed similar background/text colors).
|
||||
13. Added filename length validation and warning for Excel exports.
|
||||
14. Improved dark mode styling in file list dialogs.
|
||||
191
docs/changelog_zh.md
Normal file
191
docs/changelog_zh.md
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
# v0.0.0
|
||||
|
||||
1:
|
||||
保存为md格式的文件.smm.md,右上角更多菜单中支持切换为md格式编辑
|
||||
节点超链接支持添加ob文件
|
||||
拆分模式支持思维导图本身数据的同步,基本配置、设置之类的不能同步
|
||||
点击右上角的保存按钮时会存储当前的预览图像,然后可以通过![[.smm.md]]嵌入md文档显示为图片,原文件修改后会实时同步,双击图片跳转到思维导图编辑
|
||||
节点统计添加到底部状态栏
|
||||
设置简单做了一个多语言设置
|
||||
|
||||
2:
|
||||
思维导图和svg数据压缩编码后进行保存
|
||||
修复右上角更多菜单中【打开为Markdown文档】菜单重复的问题
|
||||
支持持久化思维导图中的设置
|
||||
支持设置思维导图文件路径、图片文件路径、默认结构、默认主题、无操作自动保存时间
|
||||
支持设置主题模式为:跟随ob、深色、浅色
|
||||
标签页关闭前触发保存,避免数据丢失
|
||||
节点图片、背景图片上传后保存到vault中,只存储文件路径,减少数据体积
|
||||
节点图片支持选择vault中的图片
|
||||
嵌入预览的数据由svg格式改为png格式,优点是保存的数据会小很多,缺点是没有svg高清
|
||||
画布右键菜单中增加【复制为ob内链】菜单
|
||||
保存按钮旁边增加保存中的提示
|
||||
节点文本输入支持输入[[弹出选择ob文件的列表,支持插入链接
|
||||
节点超链接弹窗中的ob文件支持搜索
|
||||
提示信息由elementui切换为obsidian
|
||||
tooltip由elementui切换为obsidian
|
||||
主题色改为ob的主题色,非elementui组件的颜色支持随ob的主题色改变
|
||||
右上角新增切换大纲编辑模式的按钮
|
||||
|
||||
3:
|
||||
多语言
|
||||
跟随ob的设置
|
||||
完善插件代码多语言,支持中文、英文、越南语、繁体
|
||||
节点图片上传弹窗支持直接从ob文件列表中拖拽图片
|
||||
直接粘贴的图片也创建为文件添加到vault中
|
||||
节点右键菜单新增【复制到ob内链】菜单,打开该内链时自动定位到该节点
|
||||
内链
|
||||
修改节点富文本内链链接的编辑方式,编辑过程中显示源码[[xxx]],编辑完成后渲染为链接。支持带#、^、|后缀的内链
|
||||
统一处理节点超链接和节点文本中出现的ob内链,更新到md文档中
|
||||
修复节点文本中的内链插入时不同目录的同名文件无法区分的问题
|
||||
内链去重
|
||||
支持在markdown文档嵌入smm语言的代码块:
|
||||
可简单编辑思维导图,快捷键功能都可用,支持富文本工具栏
|
||||
支持设置初始高度,支持拖拽调整高度
|
||||
操作按钮:回到根节点、适应画布
|
||||
命令:插入思维导图代码块到当前文档
|
||||
节点文本选择文件弹窗支持键盘操作,上下方向键,回车键
|
||||
修复小地图图片渲染异常的问题
|
||||
修复演示模式报错的问题
|
||||
大纲支持显示和添加内链
|
||||
完善界面样式、布局、暗黑模式等
|
||||
|
||||
4.
|
||||
导入smm、json、xmind文件转换base64格式的节点图片,存储到vault中
|
||||
节点超链接图标区分ob文件和网页
|
||||
导出为smm、json、xmind文件时转换ob路径的图片为base64
|
||||
设置里增加提交issue和下载客户端的链接信息
|
||||
没有可预览图片双击也可以跳转
|
||||
超链接支持添加本地文件,选择的文件会上传到Vault中,存储路径可以在设置中修改
|
||||
支持压缩上传的图片,是否开启压缩及压缩参数可在设置中修改
|
||||
支持设置![[]]嵌入预览时双击是否新标签页打开
|
||||
增加嵌入预览背景是否透明的设置
|
||||
思维导图文件存储目录支持设置三种方式:仓库根目录、指定文件夹、当前文件所在的文件夹
|
||||
图片文件存储目录、文件存储目录支持设置四种方式:仓库根目录、指定文件夹、当前文件所在的文件夹、当前文件所在文件夹下指定的子文件夹
|
||||
支持设置新建思维导图文件名的前缀和日期时间戳格式
|
||||
增加首次进入插件时的引导提示
|
||||
|
||||
5.
|
||||
设置里支持扩展思维导图可用的字体列表
|
||||
设置里的目录输入支持选择仓库目录
|
||||
支持将md文档预览为思维导图,支持切换结构、主题,可导出为png、svg、pdf
|
||||
设置里的结构选项列表适配多语言、只有一个的结构类型名称不用加索引
|
||||
右上角新增切换为只读模式的图标按钮
|
||||
如果思维导图中存在图片数据,那么嵌入的图形为png格式,否则为svg格式,svg嵌入图像支持背景透明
|
||||
代码块嵌入支持设置背景是否透明
|
||||
代码块嵌入支持切换结构、主题
|
||||
按需引入组件,减小打包体积,修复对ob文档表格样式影响的问题
|
||||
|
||||
# v0.1.1
|
||||
1.节点右键菜单新增【复制当前节点为图片到剪贴板】,可在ob文档里粘贴;
|
||||
2.修复同时打开多个标签页编辑同一个文件时,在其中一个标签页中修改后,切换到其他标签页中会出现节点文字消失的问题;
|
||||
3.修复同时打开多个标签页编辑同一个文件时,会出现文本编辑中删除键不生效的问题;
|
||||
4.切换标签页时自动关闭思维导图界面上的一些状态,比如文本编辑状态、节点激活状态等等;
|
||||
5.修复底部工具栏可能会和ob的状态栏重叠的问题;
|
||||
6.默认主题设置区分浅色和深色模式;
|
||||
7.思维导图视图的侧边栏设置中新增【是否显示底部工具栏】的设置;
|
||||
8.拦截ob的Ctrl+S和Ctrl+F快捷键,修改为Ctrl+S触发思维导图保存,Ctrl+F触发显示思维导图搜索框;
|
||||
9.增加进入演示模式的命令;
|
||||
10.修复md文档![[]]嵌入时没有预览图像的情况下,此时更新图像预览图不会自动更新的问题;
|
||||
11.![[]]嵌入后,如果文件被删除,增加文件已被删除的提示;
|
||||
12.修复因获取文件新建目录错误导致会重复创建文件及快速点击新建图标会出现文件创建失败的问题;
|
||||
13.修复嵌入预览在阅读模式下指定的宽高不生效的问题;
|
||||
14.ob白板嵌入预览时居中显示,限制显示的最大宽高避免溢出;
|
||||
15.支持excalidraw中以图像嵌入思维导图文件,但是显示尺寸目前存在问题;
|
||||
16.新增【保存并更新图像】的命令和快捷键Ctrl+Shift+S;
|
||||
17.修复节点备注侧边栏内容为空的问题;
|
||||
18.按住Ctrl/Command键点击节点超链接,会在新标签页打开超链接;
|
||||
19.修复导入解析不了的md文件时一直处于loading状态的问题,同时增加提示;
|
||||
|
||||
# v0.1.2
|
||||
1.增加设置【是否允许在ob中搜索】。开启后会额外保存所有节点文本的内容到文件中用于搜索。点击搜索结果打开思维导图文件时会自动定位到该节点;
|
||||
2.增加设置【检查新版本】。开启后会在插件启动时检查是否有新版本可用;
|
||||
3.将Markdown文档【预览为思维导图】时过滤掉开头的YAML Frontmatter;
|
||||
4.支持按住Shift键时鼠标滚轮左右移动画布;
|
||||
5.导入Markdown及将Markdown文档【预览为思维导图】时支持解析Markdown中的网络图片;
|
||||
6.优先渲染思维导图,然后加载页面UI,提升打开速度;
|
||||
7.保留用户编辑的YAML数据;
|
||||
8.支持把思维导图文档转换为Markdown文档;
|
||||
9.支持把Markdown文档转换为思维导图文档;
|
||||
10.备注的图片上传到Vault中,和节点图片保持一致;
|
||||
11.适配手机端:1.左侧增加右键菜单图标;2.大纲编辑增加辅助按钮;3.左右拖动画布时禁止ob侧边栏显示;4.隐藏一些非必要的按钮;5.不显示新手引导;
|
||||
12.支持从仓库或电脑本地拖拽文件到节点,如果是图片文件则以节点图片方式插入,否则为超链接;
|
||||
13.修复右上角隐藏的搜索框会挡住画布和节点操作的问题;
|
||||
14.修复在画布右侧或底部边缘编辑文本时,当文本编辑框范围超出边缘后整个画布及右边框、工具栏都会左移或上移的问题;
|
||||
15.增加设置【存储画布位置和缩放数据】;
|
||||
16.优化Markdown的导入和解析:如果解析出多个根节点,不是只返回第一个,而是再创建一个根节点来包裹;
|
||||
17.将【复制节点为ob内链】操作中保存uid的符号由#改为#^;
|
||||
18.开启【支持ob搜索】设置后保存节点uid的符号由#改为^;
|
||||
19.在md中通过[[]]嵌入后在阅读模式下鼠标悬浮预览时图片自适应;
|
||||
|
||||
# v0.1.3
|
||||
1.修复导入md时会出现undefined的问题;
|
||||
2.增加设置【节点文字转markdown标题的最大层级】:小于该层级的节点文字会转为Markdown标题,其余的转为无序列表;
|
||||
3.嵌入预览的图像数据默认改为以独立的文件存储:
|
||||
3.1.增加设置【预览图像数据是否以文件形式存储】,默认开启,即图像数据以svg文件形式存储在仓库中,思维导图文件中只存储路径。关闭则将图像数据以base64存储在文件中,文件体积会增大:
|
||||
3.2.在ob软件中删除思维导图文件时同步删除预览文件,如果是在电脑的文件系统中删除,则需要手动删除。其他一些情况下也可能无法自动删除;
|
||||
4.修复大纲模式下保存会丢失用于搜索的文本数据的问题;
|
||||
5.优化【逻辑结构图】结构的布局算法,加快渲染速度;优化【逻辑结构图】结构的概要节点和普通节点的位置冲突问题;修复概要节点尺寸改变后没有触发布局更新的问题;
|
||||
6.思维导图文件和md文件的互相转换增加确认弹窗;
|
||||
7.导入文件弹窗支持拖拽仓库或电脑本地文件进行导入;
|
||||
8.支持在标题栏修改文件名;
|
||||
|
||||
# v0.1.4
|
||||
1.修复只读模式下可以拖拽文件到节点上的问题;
|
||||
2.节点文本编辑支持直接输入内链:
|
||||
2.1.编辑时连续输入[[会弹出ob文件列表,按键盘的上下键可切换选中的文件,按回车键可插入所选的文件,也可鼠标点击选择,支持搜索;
|
||||
2.2.文本编辑完成后[[]]格式会渲染为超链接样式,点击可打开链接的文件,ctrl+鼠标左键可新标签页打开;
|
||||
2.3.输入或粘贴的内链支持#、^、|三种格式;
|
||||
3.新建思维导图文件命名格式设置修改,从两个设置选项改为一个,支持使用变量;
|
||||
4.增加设置【粘贴图片的命名方式】,支持使用变量;
|
||||
5.增加设置【新标签页打开节点链接】,默认开启;
|
||||
6.所有弹窗隐藏掉右上角的关闭按钮,以及禁止点击弹窗外部关闭弹窗;
|
||||
7.修复画布背景设置为透明时双击节点图片预览时画布背景会变为该图片的问题;
|
||||
8.节点标签添加输入框右侧增加添加按钮;
|
||||
9.导入弹窗增加提示信息;
|
||||
10.基础样式-连线风格增加【括号】样式;
|
||||
11.概要节点允许添加关联线、公式、附件;
|
||||
12.导出为svg文件支持设置背景是否透明;
|
||||
13.思维导图视图右侧设置侧边栏中对部分比较难以理解的设置选项增加提示信息;
|
||||
|
||||
# v0.1.5
|
||||
新增:
|
||||
1.支持创建自由节点;
|
||||
2.节点文本内链编辑优化:1.支持解析别名、区块等;2.修复无法打开链接的问题;3.输入【【支持自动修改为[[并弹出文件选择列表;4.文件不存在则创建;
|
||||
3.升级数学公式渲染库版本,提升渲染效果;
|
||||
4.支持导入txt文件;
|
||||
5.导入mm、xmind、txt、md、xlsx文件支持自动转换并渲染数学公式;
|
||||
6.导出md文件优化:1.支持保留和解析加粗、斜体、删除线样式;2.支持保留和解析图标、标签、超链接、图片内容;3.优化概要和备注内容的保存,通过代码块包裹,导入支持解析,避免污染结构;
|
||||
7.导出md、txt文件保留数学公式源码,便于导入识别;
|
||||
8.导出为xmind文件时数学公式的前后增加$符号,便于导入识别;
|
||||
9.拖拽调整节点宽度支持吸附到兄弟节点宽度;
|
||||
10.彩虹线条由实例化选项改为主题配置中的字段,以支持不同的思维导图文件个性化;
|
||||
11.节点样式侧边栏增加节点外边距的设置;
|
||||
12.节点和画布右键菜单新增一键选择节点菜单;
|
||||
13.节点右键菜单增加复制为纯文本菜单;
|
||||
14.节点右键菜单中的创建节点、复制粘贴系列收起到二级菜单中;
|
||||
15.新增对激活节点使用加粗、斜体、下划线、删除线、增大减小字体的快捷键(部分快捷键可能和ob快捷键冲突,导致不生效);
|
||||
16.侧边栏大纲:1.修复编辑完某个节点再立即上移节点层级时思维导图树更新大纲未更新的问题;2.修复在大纲中点击已激活节点时进行上移操作思维导图树更新大纲未更新的问题;3.大纲Tab快捷键改为下移节点层级;
|
||||
17.全屏大纲编辑:1.修复编辑已存在或新建节点文本后再提升该节点层级时会导致该节点文本恢复为默认文本的问题;2.大纲Tab快捷键改为下移节点层级;
|
||||
18.大纲编辑新增将当前节点光标后面的内容拆分创建为同级节点的能力和快捷键(快捷键可能和ob快捷键冲突,导致不生效);
|
||||
19.粘贴多行文本时支持保留层级和不保留层级两种方式创建节点;
|
||||
20.开启自由拖拽后,支持按住Ctrl键忽略节点层级拖拽;
|
||||
21.连线箭头样式设置:1.【基础样式】中支持设置箭头方向;2.【节点样式】中支持设置显示箭头;
|
||||
22.节点备注编辑器由弹窗模式改为侧边模式;
|
||||
23.支持配置图床;
|
||||
|
||||
修复/优化:
|
||||
1.修复转换思维导图为md文件时会失败的问题;
|
||||
2.修复右键菜单中点击新建思维导图会创建两个文件的问题;
|
||||
3.修复在文件列表中先将smm文件右键【创建副本】,然后在对创建的副本点击转换为markdown文件失败的问题;
|
||||
4.修复在其他md文件中![[]]嵌入思维导图文件后,同时打开且思维导图没有预览文件的情况下,编辑思维导图文件保存时会报错的问题;
|
||||
5.修复修改脑图文件名、将思维导图转换为md文件操作没有同步修改预览svg文件名的问题;
|
||||
6.修复修改脑图文件名时,文件元数据中的path字段没有立即更新的问题;
|
||||
7.修复导出svg文件,背景图片没有按基础设置中设置进行拉伸的问题;
|
||||
8.修复目录组织图节点冲突的问题;
|
||||
9.修复节点文本输入了非法字符时导出图片、svg、pdf会失败的问题;
|
||||
10.修复先在节点图片弹窗中上传图片,然后在基础设置中的背景图片中上传图片第一次会不生效的问题;
|
||||
11.鱼骨图结构代码优化;优化节点外间距;
|
||||
12.优化深色主题下编辑三级及以下节点文本时编辑框背景色和文字颜色相近导致看不清的问题;
|
||||
13.导出弹窗中增加对导出excel文件的名称长度的校验和提示;
|
||||
14.优化文件列表弹窗暗黑模式的样式;
|
||||
|
|
@ -115,4 +115,4 @@
|
|||
|
||||
- **Node Line Style Inheritance?**
|
||||
If enabled, descendant nodes inherit line styles (unless they have custom styles).
|
||||
*Toggle:* Settings → *Node Line Style Inherits from Ancestor*.
|
||||
*Toggle:* Settings → *Node Line Style Inherits from Ancestor*.
|
||||
63
docs/imageHosting.md
Normal file
63
docs/imageHosting.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Image Hosting Feature Description
|
||||
|
||||
Once the image hosting is configured and enabled, all images uploaded during the mind map editing process will be uploaded to the image hosting service. This significantly reduces the size of the mind map source file and speeds up both reading and saving operations.
|
||||
|
||||
Currently, the default integration uses PicGo software to manage the image hosting and images. PicGo is a free, open-source tool for managing image hosting services, supporting various platforms such as Alibaba Cloud and Qiniu Cloud. You can download it here: [GitHub](https://github.com/Molunerfinn/PicGo/releases).
|
||||
|
||||
After configuration and enabling the feature, ensure that the PicGo software is running during the mind map editing process; otherwise, uploads will fail. If you wish to use other image hosting tools, you will need to configure and test them on your own.
|
||||
|
||||
If an image fails to open after a successful upload, it may be due to permission issues. Please check whether the image hosting service has public access enabled.
|
||||
|
||||
## imgHostingResponseField Configuration Description
|
||||
|
||||
The interface response structure configuration is used to extract the image URL after a successful upload. The default setting is `data.result[0]`, where `data` represents the root object of the interface response data and cannot be modified. Other fields should be configured according to the structure of the interface response data. Examples are as follows:
|
||||
|
||||
If the image hosting interface response data structure is:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": ["https://example.com/image.jpg" ]
|
||||
}
|
||||
```
|
||||
|
||||
Then the interface response structure configuration should be: `data.result[0]`.
|
||||
|
||||
If the image hosting interface response data structure is:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"url": "https://example.com/image.jpg"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then the interface response structure configuration should be: `data.data.url`.
|
||||
|
||||
If the image hosting interface response data structure is:
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "https://example.com/image.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
Then the interface response structure configuration should be: `data.url`.
|
||||
|
||||
If the image hosting interface response data structure is:
|
||||
|
||||
```json
|
||||
https://example.com/image.jpg
|
||||
```
|
||||
|
||||
Then the interface response structure configuration should be: `data`.
|
||||
|
||||
If the image hosting interface response data structure is:
|
||||
|
||||
```json
|
||||
["https://example.com/image.jpg"]
|
||||
```
|
||||
|
||||
Then the interface response structure configuration should be: `data[0]`.
|
||||
63
docs/imageHosting_zh.md
Normal file
63
docs/imageHosting_zh.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# 图床功能说明
|
||||
|
||||
设置了图床并开启后,在思维导图编辑过程中上传的所有图片都会上传到图床,可显著减小思维导图源文件体积,加快读取和保存的速度。
|
||||
|
||||
目前默认适配的是PicGo软件来管理图床和图片,PicGo是一个免费、开源的图床管理工具,支持多种图床,如阿里云、七牛云等。可点击这里进行下载:[GitHub](https://github.com/Molunerfinn/PicGo/releases)。
|
||||
|
||||
配置完成并开启使用后,在编辑思维导图的过程中需确保PicGo软件正在运行,否则无法上传。如果你想要使用其他图床工具,需要自行配置和测试。
|
||||
|
||||
上传成功后图片却打开,可能是权限问题,需检查图床是否开启了公开访问权限。
|
||||
|
||||
## imgHostingResponseField配置说明
|
||||
|
||||
接口响应结构配置用于提取上传成功后的图片URL,默认是`data.result[0]`,`data`代表接口响应数据的根对象,不可修改。其他字段需根据接口响应数据的结构进行配置。示例如下:
|
||||
|
||||
如果图床接口响应数据的结构为:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": ["https://example.com/image.jpg" ]
|
||||
}
|
||||
```
|
||||
|
||||
则接口响应结构配置为:`data.result[0]`。
|
||||
|
||||
如果图床接口响应数据的结构为:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"url": "https://example.com/image.jpg"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
则接口响应结构配置为:`data.data.url`。
|
||||
|
||||
如果图床接口响应数据的结构为:
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "https://example.com/image.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
则接口响应结构配置为:`data.url`。
|
||||
|
||||
如果图床接口响应数据的结构为:
|
||||
|
||||
```json
|
||||
https://example.com/image.jpg
|
||||
```
|
||||
|
||||
则接口响应结构配置为:`data`。
|
||||
|
||||
图床接口响应数据的结构为:
|
||||
|
||||
```json
|
||||
["https://example.com/image.jpg"]
|
||||
```
|
||||
|
||||
则接口响应结构配置为:`data[0]`。
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "simple-mind-map",
|
||||
"name": "Simple mind map",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.5",
|
||||
"minAppVersion": "1.8.3",
|
||||
"description": "A relatively powerful mind map.",
|
||||
"author": "wanglin",
|
||||
|
|
|
|||
BIN
plugin/.DS_Store
vendored
BIN
plugin/.DS_Store
vendored
Binary file not shown.
|
|
@ -7,7 +7,8 @@ import {
|
|||
SMM_TAG,
|
||||
OUTLINE_ICON,
|
||||
MINDMAP_ICON,
|
||||
PRINT_ICON
|
||||
PRINT_ICON,
|
||||
getSmmEditViewCount
|
||||
} from './ob/constant.js'
|
||||
import { initApp } from './src/main.js'
|
||||
import {
|
||||
|
|
@ -15,10 +16,15 @@ import {
|
|||
parseMarkdownText,
|
||||
createDefaultText
|
||||
} from './ob/metadataAndMarkdown.js'
|
||||
import { hideTargetMenu, checkVersion } from './ob/utils.js'
|
||||
import {
|
||||
hideTargetMenu,
|
||||
smmFilePathToFileName,
|
||||
formatFileName
|
||||
} from './ob/utils.js'
|
||||
import LZString from 'lz-string'
|
||||
import { base64ToFile } from './src/utils'
|
||||
import ModifyFileName from './ob/ModifyFileName.js'
|
||||
|
||||
// 自定义视图类
|
||||
class SmmEditView extends TextFileView {
|
||||
constructor(leaf, plugin) {
|
||||
super(leaf)
|
||||
|
|
@ -30,28 +36,17 @@ class SmmEditView extends TextFileView {
|
|||
height: 100%;
|
||||
overflow: hidden;
|
||||
`
|
||||
// 字符串类型的文件数据
|
||||
this.mindMapData = ''
|
||||
// 解析后的文件数据
|
||||
this.parsedMindMapData = null
|
||||
// Vue实例
|
||||
this.mindMapAPP = null
|
||||
|
||||
// 当前视图是否处于激活状态
|
||||
this.isActive = true
|
||||
// 监听容器元素的尺寸变化
|
||||
this.resizeObserver = null
|
||||
// 当前文件是否存在未保存的内容
|
||||
this.isUnSave = false
|
||||
// 是否外部修改了当前文件
|
||||
this.isOuterChange = false
|
||||
// 是否不要触发data_change事件,当外部修改数据时进行同步使用
|
||||
this.isNotTriggerDataChange = false
|
||||
// 标签页是否处于隐藏状态
|
||||
this.isHidden = false
|
||||
// 标签页激活时是否需要通知思维导图刷新尺寸
|
||||
this.needResize = false
|
||||
// 按钮元素
|
||||
this.isOutlineEditMode = false
|
||||
this.saveButton = null
|
||||
this.exportButton = null
|
||||
|
|
@ -62,46 +57,39 @@ class SmmEditView extends TextFileView {
|
|||
this.savingTipEl = null
|
||||
this.isReadonlyMode = false
|
||||
this.toggleReadonlyButton = null
|
||||
this.modifyFileName = new ModifyFileName(this)
|
||||
this.popScope = null
|
||||
this.id = getSmmEditViewCount()
|
||||
}
|
||||
|
||||
// 获取视图类型
|
||||
getViewType() {
|
||||
return SMM_VIEW_TYPE
|
||||
}
|
||||
|
||||
// 获取图标名称
|
||||
getIcon() {
|
||||
return 'smm-icon'
|
||||
}
|
||||
|
||||
// 右上角更多菜单扩展
|
||||
onPaneMenu(menu, source) {
|
||||
super.onPaneMenu(menu, source)
|
||||
// 隐藏特定默认菜单项
|
||||
hideTargetMenu(menu, '在新窗口中打开')
|
||||
hideTargetMenu(menu, '移动至新窗口')
|
||||
}
|
||||
|
||||
// 切换到Markdown视图
|
||||
async switchToMarkdownView() {
|
||||
if (!this.file || !this.leaf) return
|
||||
// 获取默认的Markdown视图类型
|
||||
const mdViewType = this.app.viewRegistry.getTypeByExtension('md')
|
||||
// 切换到Markdown视图
|
||||
await this.leaf.setViewState({
|
||||
type: mdViewType,
|
||||
state: {
|
||||
...this.leaf.getViewState().state,
|
||||
isSwitchToMarkdownViewFromSmmView: true
|
||||
},
|
||||
popstate: true // 保持导航历史
|
||||
popstate: true
|
||||
})
|
||||
}
|
||||
|
||||
// 打开视图
|
||||
async onOpen() {
|
||||
// 注册文件修改监听器
|
||||
this.registerEvent(
|
||||
this.app.vault.on('modify', this._handleExternalChange.bind(this))
|
||||
)
|
||||
|
|
@ -112,33 +100,22 @@ class SmmEditView extends TextFileView {
|
|||
this._handleActiveChange.bind(this)
|
||||
)
|
||||
)
|
||||
// 注册窗口大小变化监听
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('resize', this._handleResize.bind(this))
|
||||
)
|
||||
|
||||
// 在视图头部右侧添加工具按钮
|
||||
this._addActionBtns()
|
||||
|
||||
// 监听主题模式改变
|
||||
this._initThemeMode()
|
||||
|
||||
// 检查更新
|
||||
if (this.plugin.settings.openVersionCheck) {
|
||||
checkVersion(version => {
|
||||
if (version) {
|
||||
new Notice(this.plugin._t('tip.pluginNewVersion') + version)
|
||||
}
|
||||
})
|
||||
}
|
||||
this.modifyFileName.setupTitleEditing()
|
||||
this._registerHotkeyOverrides()
|
||||
}
|
||||
|
||||
// 获取视图数据(保存到文件)
|
||||
getViewData() {
|
||||
return this.mindMapData
|
||||
}
|
||||
|
||||
// 解析加载的数据
|
||||
async setViewData(data, isClear) {
|
||||
if (this.isHidden) {
|
||||
return
|
||||
|
|
@ -148,16 +125,25 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
let rawData = data.trim()
|
||||
try {
|
||||
// 空文件处理
|
||||
if (!rawData) {
|
||||
// 文件内容为空
|
||||
throw new Error(this.plugin._t('tip.fileIsEmpty'))
|
||||
} else {
|
||||
this.parsedMindMapData = parseMarkdownText(rawData)
|
||||
const content = this.parsedMindMapData.metadata.content
|
||||
if (content) {
|
||||
this.parsedMindMapData.metadata.content =
|
||||
LZString.decompressFromBase64(content)
|
||||
let passedContent = ''
|
||||
try {
|
||||
// 兼容未编码的老版数据
|
||||
const json = JSON.parse(content)
|
||||
if (json && json.root) {
|
||||
passedContent = content
|
||||
}
|
||||
} catch (error) {}
|
||||
if (!passedContent) {
|
||||
this.parsedMindMapData.metadata.content = LZString.decompressFromBase64(
|
||||
content
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw new Error('文件格式错误')
|
||||
}
|
||||
|
|
@ -186,7 +172,6 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
}
|
||||
|
||||
// 清理观察者
|
||||
_clearObserver() {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect()
|
||||
|
|
@ -194,7 +179,6 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
}
|
||||
|
||||
// 渲染思维导图
|
||||
_renderMindMap() {
|
||||
this.warpEl.empty()
|
||||
const el = this.warpEl.createDiv('smmMindMapEdit')
|
||||
|
|
@ -203,9 +187,7 @@ class SmmEditView extends TextFileView {
|
|||
height: 100%;
|
||||
overflow: hidden;
|
||||
`
|
||||
// 清理旧的观察者
|
||||
this._clearObserver()
|
||||
// 创建新的尺寸观察者
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect
|
||||
|
|
@ -219,9 +201,7 @@ class SmmEditView extends TextFileView {
|
|||
this.resizeObserver.observe(el)
|
||||
}
|
||||
|
||||
// 实际初始化思维导图
|
||||
_initializeMindMap(el) {
|
||||
// 确保没有重复初始化
|
||||
if (this.mindMapAPP) return
|
||||
const filePath = this.file?.path
|
||||
let initLocationNodeId = ''
|
||||
|
|
@ -231,47 +211,64 @@ class SmmEditView extends TextFileView {
|
|||
delete fileToSubpathMap[filePath]
|
||||
}
|
||||
this.mindMapAPP = initApp(el, {
|
||||
// 传递初始数据给思维导图渲染
|
||||
getInitMindMapData: () => {
|
||||
return this.parsedMindMapData.metadata.content
|
||||
},
|
||||
// 获取思维导图初始化位置的节点ID
|
||||
getInitLocationNodeId: () => {
|
||||
return initLocationNodeId
|
||||
},
|
||||
// 从思维导图获取最新数据
|
||||
getMindMapCurrentData: (content, linkData, textData) => {
|
||||
this.parsedMindMapData.metadata.content = content
|
||||
const { metadata, svgdata, linkdata } = this.parsedMindMapData
|
||||
const {
|
||||
metadata,
|
||||
svgdata,
|
||||
svgdataUpdateAt,
|
||||
linkdata
|
||||
} = this.parsedMindMapData
|
||||
this.mindMapData = assembleMarkdownText({
|
||||
metadata: {
|
||||
path: `${filePath}`,
|
||||
path: `${this.file?.path}`,
|
||||
tags: Array.from(new Set([SMM_TAG, ...metadata.tags])),
|
||||
content: LZString.compressToBase64(content),
|
||||
yaml: metadata.yaml
|
||||
},
|
||||
svgdata,
|
||||
svgdataUpdateAt,
|
||||
linkdata: linkData || linkdata || [],
|
||||
textdata: textData || []
|
||||
})
|
||||
},
|
||||
// 思维导图中触发保存
|
||||
saveMindMapData: (_, svgData) => {
|
||||
if (svgData) {
|
||||
this.parsedMindMapData.svgdata = LZString.compressToBase64(svgData)
|
||||
}
|
||||
this.forceSave()
|
||||
saveMindMapData: async (_, svgData) => {
|
||||
try {
|
||||
this._showSavingTip()
|
||||
const { embedImageIsSeparateFile } = this.plugin.settings
|
||||
if (svgData) {
|
||||
if (embedImageIsSeparateFile) {
|
||||
const res = await this._getFileSvgData(svgData)
|
||||
if (res) {
|
||||
this.parsedMindMapData.svgdata = `![[${res}]]`
|
||||
this.parsedMindMapData.svgdataUpdateAt = moment().format(
|
||||
'YYYY-MM-DD HH:mm:ss'
|
||||
)
|
||||
} else {
|
||||
new Notice(this.plugin._t('tip.imgCreateFail'))
|
||||
}
|
||||
} else {
|
||||
this.parsedMindMapData.svgdata = LZString.compressToBase64(
|
||||
svgData
|
||||
)
|
||||
}
|
||||
}
|
||||
this.forceSave()
|
||||
} catch (error) {}
|
||||
},
|
||||
// 获取思维导图配置
|
||||
getMindMapConfig: () => {
|
||||
return this.plugin.settings.mindMapConfig || {}
|
||||
},
|
||||
// 设置思维导图配置
|
||||
saveMindMapConfig: newConfig => {
|
||||
this.plugin.settings.mindMapConfig = newConfig
|
||||
this.plugin._saveSettings()
|
||||
},
|
||||
// 获取思维导图本地配置
|
||||
getMindMapLocalConfig: () => {
|
||||
const { mindMapLocalConfig, themeMode } = this.plugin.settings
|
||||
const config = mindMapLocalConfig || {}
|
||||
|
|
@ -281,12 +278,10 @@ class SmmEditView extends TextFileView {
|
|||
: themeMode === 'dark'
|
||||
return config
|
||||
},
|
||||
// 设置思维导图本地配置
|
||||
saveMindMapLocalConfig: newConfig => {
|
||||
this.plugin.settings.mindMapLocalConfig = newConfig
|
||||
this.plugin._saveSettings()
|
||||
},
|
||||
// 获取设置
|
||||
getSettings: () => {
|
||||
return this.plugin.settings || {}
|
||||
},
|
||||
|
|
@ -297,7 +292,6 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
this.plugin._saveSettings()
|
||||
},
|
||||
// 获取所有文件
|
||||
getObAllFiles: (extraSelf = true) => {
|
||||
const fileList = this.app.vault.getFiles()
|
||||
if (extraSelf && this.file) {
|
||||
|
|
@ -307,140 +301,190 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
return fileList
|
||||
},
|
||||
// 创建内链文件信息
|
||||
createLinkInfoFromFilePath: filePath => {
|
||||
const file = this.app.vault.getFileByPath(filePath)
|
||||
createLinkInfoFromFilePath: (filePath, subpath = '', alias = '') => {
|
||||
const file = this.tryGetFileByPath(filePath)
|
||||
if (!file) {
|
||||
return null
|
||||
}
|
||||
const linkText = this.app.fileManager.generateMarkdownLink(
|
||||
file,
|
||||
this.file?.path || ''
|
||||
this.file?.path || '',
|
||||
subpath,
|
||||
alias
|
||||
)
|
||||
return {
|
||||
link: file.path,
|
||||
linkText
|
||||
}
|
||||
},
|
||||
// 打开文件
|
||||
openFile: (filePath, isNewTab = false) => {
|
||||
openFile: (filePath, isNewTab = false, createOnNoExist = false) => {
|
||||
if (!filePath) return
|
||||
// [[xxx#xxx]]、[[xxx^xxx]]、[[xxx|xxx]]
|
||||
const arr = filePath.split(/(#|^|\|)/)
|
||||
filePath = arr[0]
|
||||
let postfix = ''
|
||||
if (arr.length > 1 && arr[1] !== '|') {
|
||||
postfix = arr.slice(1).join('')
|
||||
}
|
||||
this.app.vault.getAbstractFileBy
|
||||
let file = this.app.vault.getFileByPath(filePath)
|
||||
if (!file) {
|
||||
file = this.app.vault.getFileByPath(filePath + '.md')
|
||||
}
|
||||
const file = this.tryGetFileByPath(filePath)
|
||||
if (file && file instanceof TFile) {
|
||||
this.app.workspace.openLinkText(file.path + postfix, '', isNewTab)
|
||||
this.app.workspace.openLinkText(
|
||||
file.path + postfix,
|
||||
this.file?.path || '',
|
||||
isNewTab
|
||||
)
|
||||
} else if (createOnNoExist) {
|
||||
this.app.workspace.openLinkText(
|
||||
/\.md$/.test(filePath) ? filePath : filePath + '.md',
|
||||
this.file?.path || '',
|
||||
isNewTab
|
||||
)
|
||||
}
|
||||
},
|
||||
// 核心方法:打开网页链接
|
||||
openWebLink: url => {
|
||||
if (this.app.openExternal) {
|
||||
this.app.openExternal(url)
|
||||
return
|
||||
}
|
||||
// 兼容旧版 Obsidian 和移动端
|
||||
try {
|
||||
// 桌面端 Electron 环境
|
||||
if (window.require) {
|
||||
const { shell } = window.require('electron')
|
||||
shell.openExternal(url)
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Electron module not available', e)
|
||||
}
|
||||
// 通用回退方案
|
||||
} catch (e) {}
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
},
|
||||
// 保存文件到 vault
|
||||
saveFileToVault: async (file, isImg = true) => {
|
||||
try {
|
||||
const folder = isImg
|
||||
? this._getFileSavePath(
|
||||
'imagePathType',
|
||||
'imagePath',
|
||||
'imageSubPath'
|
||||
)
|
||||
: this._getFileSavePath(
|
||||
'attachmentPathType',
|
||||
'attachmentPath',
|
||||
'attachmentSubPath'
|
||||
)
|
||||
await this.plugin._ensureDirectoryExists(folder)
|
||||
// 处理文件名冲突
|
||||
let fileName = file.name
|
||||
let counter = 1
|
||||
while (
|
||||
await this.app.vault.adapter.exists(
|
||||
folder ? `${folder}/${fileName}` : fileName
|
||||
)
|
||||
) {
|
||||
const parts = file.name.split('.')
|
||||
const ext = parts.pop()
|
||||
fileName = `${parts.join('.')}_${counter}.${ext}`
|
||||
counter++
|
||||
}
|
||||
// 读取文件内容
|
||||
const arrayBuffer = await file.arrayBuffer()
|
||||
// 在vault中创建路径
|
||||
const vaultPath = folder ? `${folder}/${fileName}` : fileName
|
||||
// 写入文件
|
||||
await this.app.vault.createBinary(vaultPath, arrayBuffer)
|
||||
return vaultPath
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
saveFileToVault: async (...args) => {
|
||||
const res = await this.saveFileToVault(...args)
|
||||
return res
|
||||
},
|
||||
// 获取资源路径
|
||||
getResourcePath: vaultPath => {
|
||||
return this.app.vault.adapter.getResourcePath(vaultPath)
|
||||
},
|
||||
// 获取当前文件的链接信息
|
||||
getObInternalLink: (...args) => {
|
||||
return this.app.fileManager.generateMarkdownLink(this.file, ...args)
|
||||
return this.app.fileManager.generateMarkdownLink(this.file, '', ...args)
|
||||
},
|
||||
// 显示ob提示
|
||||
showTip: tip => {
|
||||
new Notice(tip)
|
||||
},
|
||||
// 从ob://协议的URI中获取文件信息
|
||||
getFileFromUri: uri => {
|
||||
const params = new URLSearchParams(uri.split('?')[1])
|
||||
const vault = decodeURIComponent(params.get('vault') || '')
|
||||
const filePath = decodeURIComponent(params.get('file') || '')
|
||||
if (!vault || !filePath) return null
|
||||
// 检查当前vault是否匹配
|
||||
if (this.app.vault.getName() !== vault) {
|
||||
// 只允许添加当前仓库的文件
|
||||
new Notice(this.plugin._t('tip.onlyEnableSelectCurrentVaultFile'))
|
||||
return null
|
||||
}
|
||||
let res = this.app.vault.getFileByPath(filePath)
|
||||
if (!res) {
|
||||
res = this.app.vault.getFileByPath(filePath + '.md')
|
||||
}
|
||||
return res
|
||||
return this.tryGetFileByPath(filePath)
|
||||
},
|
||||
isMobile: () => {
|
||||
return Platform.isMobile
|
||||
},
|
||||
getObsidianApp: () => {
|
||||
return this.app
|
||||
},
|
||||
getFormatFileName: (str, ignores = []) => {
|
||||
return formatFileName(str, {
|
||||
notename: this.file?.basename.split('.')[0],
|
||||
ignores
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this._listenMindMapEvent()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 根据uid定位到指定节点
|
||||
tryGetFileByPath(filePath) {
|
||||
let res = this.app.vault.getFileByPath(filePath)
|
||||
if (!res) {
|
||||
res = this.app.vault.getFileByPath(filePath + '.md')
|
||||
}
|
||||
if (!res) {
|
||||
res = this.app.metadataCache.getFirstLinkpathDest(
|
||||
filePath,
|
||||
this.file?.path
|
||||
)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
async _getFileSvgData(svgData) {
|
||||
const targetFileName = smmFilePathToFileName(this.file?.path, '.svg')
|
||||
const { embedImageIsSeparateFileFolder } = this.plugin.settings
|
||||
const originSvgData = this.parsedMindMapData.svgdata
|
||||
if (originSvgData && /^!\[\[/.test(originSvgData)) {
|
||||
const match = originSvgData.match(/!\[\[([^\]]+)\]\]/)
|
||||
if (match && match[1]) {
|
||||
const filePath = match[1]
|
||||
const fileName = filePath.split('/').pop()
|
||||
if (fileName !== targetFileName) {
|
||||
const exist = await this.app.vault.exists(filePath)
|
||||
if (exist) {
|
||||
await this.app.vault.adapter.remove(filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
svgData = base64ToFile(svgData, targetFileName)
|
||||
const res = await this.saveFileToVault(
|
||||
svgData,
|
||||
true,
|
||||
embedImageIsSeparateFileFolder,
|
||||
true
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
async saveFileToVault(file, isImg = true, targetFolder = '', cover = false) {
|
||||
try {
|
||||
let folder = targetFolder || ''
|
||||
if (!folder) {
|
||||
folder = isImg
|
||||
? this._getFileSavePath('imagePathType', 'imagePath', 'imageSubPath')
|
||||
: this._getFileSavePath(
|
||||
'attachmentPathType',
|
||||
'attachmentPath',
|
||||
'attachmentSubPath'
|
||||
)
|
||||
}
|
||||
await this.plugin._ensureDirectoryExists(folder)
|
||||
let fileName = file.name
|
||||
let fileExist = false
|
||||
if (!cover) {
|
||||
let counter = 1
|
||||
while (
|
||||
await this.app.vault.adapter.exists(
|
||||
folder ? `${folder}/${fileName}` : fileName
|
||||
)
|
||||
) {
|
||||
const parts = file.name.split('.')
|
||||
const ext = parts.pop()
|
||||
fileName = `${parts.join('.')}_${counter}.${ext}`
|
||||
counter++
|
||||
}
|
||||
} else {
|
||||
fileExist = await this.app.vault.adapter.exists(
|
||||
folder ? `${folder}/${fileName}` : fileName
|
||||
)
|
||||
}
|
||||
// 读取文件内容
|
||||
const arrayBuffer = await file.arrayBuffer()
|
||||
// 在vault中创建路径
|
||||
const vaultPath = folder ? `${folder}/${fileName}` : fileName
|
||||
// 写入文件
|
||||
if (fileExist) {
|
||||
await app.vault.adapter.writeBinary(vaultPath, arrayBuffer)
|
||||
} else {
|
||||
await this.app.vault.createBinary(vaultPath, arrayBuffer)
|
||||
}
|
||||
return vaultPath
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
jumpToNodeByUid(uid) {
|
||||
if (!this.mindMapAPP) {
|
||||
return
|
||||
|
|
@ -448,13 +492,12 @@ class SmmEditView extends TextFileView {
|
|||
this.mindMapAPP.$bus.$emit('jumpToNodeByUid', uid)
|
||||
}
|
||||
|
||||
// 重写保存钩子(最直接的方式)
|
||||
async save() {
|
||||
if (!this.isActive) {
|
||||
async save(ignoreActiveCheck = false) {
|
||||
if (!ignoreActiveCheck && !this.isActive) {
|
||||
return
|
||||
}
|
||||
// 获取一下最新数据
|
||||
// 取消本次自动保存
|
||||
if (this.app.noSaveOnClose) return
|
||||
const t = Date.now()
|
||||
if (this.mindMapAPP) {
|
||||
this.mindMapAPP.$bus.$emit('getMindMapCurrentData')
|
||||
this.mindMapAPP.$bus.$emit('clearAutoSave')
|
||||
|
|
@ -467,26 +510,21 @@ class SmmEditView extends TextFileView {
|
|||
this._hideSavingTip()
|
||||
}
|
||||
|
||||
// 强制保存
|
||||
forceSave() {
|
||||
forceSave(ignoreActiveCheck = false) {
|
||||
this._showSavingTip()
|
||||
this.save()
|
||||
this.save(ignoreActiveCheck)
|
||||
}
|
||||
|
||||
// 强制保存并更新图像数据
|
||||
forceSaveAndUpdateImage() {
|
||||
this._showSavingTip()
|
||||
this.mindMapAPP.$bus.$emit('saveToLocal', true, true)
|
||||
}
|
||||
|
||||
// 监听思维导图事件
|
||||
_listenMindMapEvent() {
|
||||
this._onDataChange = this._onDataChange.bind(this)
|
||||
this.mindMapAPP.$bus.$on('data_change', this._onDataChange)
|
||||
// this.mindMapAPP.$bus.$on('view_data_change', this._onDataChange)
|
||||
}
|
||||
|
||||
// 监听思维导图数据改变
|
||||
_onDataChange() {
|
||||
if (this.isNotTriggerDataChange) {
|
||||
this.isNotTriggerDataChange = false
|
||||
|
|
@ -495,7 +533,6 @@ class SmmEditView extends TextFileView {
|
|||
this._setIsUnSave(true)
|
||||
}
|
||||
|
||||
// 处理窗口大小变化
|
||||
_handleResize() {
|
||||
if (!this.mindMapAPP || this.needResize) {
|
||||
return
|
||||
|
|
@ -511,20 +548,15 @@ class SmmEditView extends TextFileView {
|
|||
this.mindMapAPP.$updateMindMapSize()
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('更新尺寸失败:', e)
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// 处理标签激活状态变化
|
||||
async _handleActiveChange(leaf) {
|
||||
const nowActive = leaf?.view === this
|
||||
if (nowActive && !this.isActive) {
|
||||
// 标签被激活
|
||||
this._registerHotkeyOverrides()
|
||||
this.isActive = true
|
||||
// 激活后刷新视图
|
||||
if (this.mindMapAPP) {
|
||||
// 更新外部修改
|
||||
if (this.isHidden) {
|
||||
this.isHidden = false
|
||||
this._checkForExternalChanges()
|
||||
|
|
@ -539,7 +571,7 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
}
|
||||
} else if (!nowActive && this.isActive) {
|
||||
// 标签失去激活
|
||||
this._clearPopScope()
|
||||
const rect = this.warpEl.getBoundingClientRect()
|
||||
this.isHidden = rect.width === 0 || rect.height === 0
|
||||
if (this.mindMapAPP) {
|
||||
|
|
@ -551,7 +583,6 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
}
|
||||
|
||||
// 处理外部文件修改
|
||||
async _handleExternalChange(file) {
|
||||
if (this.isActive) return
|
||||
if (file.path === this.file?.path) {
|
||||
|
|
@ -559,29 +590,20 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
}
|
||||
|
||||
// 检查外部修改并更新
|
||||
async _checkForExternalChanges() {
|
||||
if (!this.file) return
|
||||
try {
|
||||
// 重新加载文件数据
|
||||
const data = await this.app.vault.read(this.file)
|
||||
await this.setViewData(data, false)
|
||||
} catch (e) {
|
||||
console.error('处理外部修改失败:', e)
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// 清空视图数据
|
||||
clear() {
|
||||
// 清理观察者
|
||||
this._clearObserver()
|
||||
// 销毁应用实例
|
||||
if (this.mindMapAPP) {
|
||||
try {
|
||||
this.mindMapAPP.$destroy()
|
||||
} catch (e) {
|
||||
console.error('销毁实例时出错:', e)
|
||||
}
|
||||
} catch (e) {}
|
||||
this.mindMapAPP = null
|
||||
}
|
||||
this.warpEl.empty()
|
||||
|
|
@ -596,7 +618,9 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
|
||||
async onClose() {
|
||||
await this.save() // 手动保存
|
||||
this._clearPopScope()
|
||||
this.modifyFileName.unload()
|
||||
await this.save()
|
||||
this.clear()
|
||||
this.saveButton = null
|
||||
this.exportButton = null
|
||||
|
|
@ -607,7 +631,6 @@ class SmmEditView extends TextFileView {
|
|||
this.toggleReadonlyButton = null
|
||||
}
|
||||
|
||||
// 防抖函数实现
|
||||
_debounce(func, wait) {
|
||||
let timeout
|
||||
return () => {
|
||||
|
|
@ -618,12 +641,9 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
}
|
||||
|
||||
// 在视图头部右侧添加工具按钮
|
||||
_addActionBtns() {
|
||||
// svg需添加class="svg-icon"
|
||||
const viewActions = this.headerEl.querySelector('.view-actions')
|
||||
|
||||
// 切换只读/编辑模式
|
||||
this.toggleReadonlyButton = this._createActionBtn(
|
||||
this.plugin._t('action.changeToReadonly'),
|
||||
'book-open',
|
||||
|
|
@ -661,7 +681,6 @@ class SmmEditView extends TextFileView {
|
|||
true
|
||||
)
|
||||
|
||||
// 切换为大纲模式
|
||||
this.changeToOutlineButton = this._createActionBtn(
|
||||
this.plugin._t('action.changeToOutline'),
|
||||
OUTLINE_ICON,
|
||||
|
|
@ -677,7 +696,6 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
)
|
||||
|
||||
// 切换为思维导图模式
|
||||
this.changeToMindmapButton = this._createActionBtn(
|
||||
this.plugin._t('action.changeToMindMap'),
|
||||
MINDMAP_ICON,
|
||||
|
|
@ -690,7 +708,6 @@ class SmmEditView extends TextFileView {
|
|||
)
|
||||
this._hideButtons([this.changeToMindmapButton])
|
||||
|
||||
// 打印大纲
|
||||
this.printOutlineButton = this._createActionBtn(
|
||||
this.plugin._t('action.printOutline'),
|
||||
PRINT_ICON,
|
||||
|
|
@ -701,7 +718,6 @@ class SmmEditView extends TextFileView {
|
|||
)
|
||||
this._hideButtons([this.printOutlineButton])
|
||||
|
||||
// 导入
|
||||
this.importButton = this._createActionBtn(
|
||||
this.plugin._t('action.import'),
|
||||
IMPORT_ICON,
|
||||
|
|
@ -711,7 +727,6 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
)
|
||||
|
||||
// 导出
|
||||
if (!Platform.isMobile) {
|
||||
this.exportButton = this._createActionBtn(
|
||||
this.plugin._t('action.export'),
|
||||
|
|
@ -733,7 +748,6 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
)
|
||||
|
||||
// 保存中的提示
|
||||
this.savingTipEl = this.headerEl.createEl('div', {
|
||||
cls: 'smm-save-tip'
|
||||
})
|
||||
|
|
@ -742,7 +756,6 @@ class SmmEditView extends TextFileView {
|
|||
|
||||
_showSavingTip() {
|
||||
if (!this.savingTipEl) return
|
||||
// 保存中...
|
||||
this.savingTipEl.innerText = this.plugin._t('tip.saving')
|
||||
}
|
||||
|
||||
|
|
@ -751,7 +764,6 @@ class SmmEditView extends TextFileView {
|
|||
this.savingTipEl.innerText = ''
|
||||
}
|
||||
|
||||
// 创建工具按钮
|
||||
_createActionBtn(label, icon, parentEl, onClick, isObIcon = false) {
|
||||
const el = this.headerEl.createEl('button', {
|
||||
cls: 'clickable-icon'
|
||||
|
|
@ -767,7 +779,6 @@ class SmmEditView extends TextFileView {
|
|||
return el
|
||||
}
|
||||
|
||||
// 重置大纲编辑模式
|
||||
_resetOnOutlineEditMode() {
|
||||
if (this.isOutlineEditMode) {
|
||||
this.saveButton.setAttribute(
|
||||
|
|
@ -794,19 +805,14 @@ class SmmEditView extends TextFileView {
|
|||
})
|
||||
}
|
||||
|
||||
// 设置是否存在未保存的内容
|
||||
_setIsUnSave(isUnSave) {
|
||||
if (!this.saveButton) return
|
||||
this.isUnSave = isUnSave
|
||||
this.saveButton.style.color = isUnSave ? '#ff6600' : 'var(--icon-color)'
|
||||
}
|
||||
|
||||
// 检测主题模式
|
||||
_initThemeMode() {
|
||||
// 初始检查
|
||||
this._updateThemeMode()
|
||||
|
||||
// 监听主题变化事件
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('css-change', () => {
|
||||
this._updateThemeMode()
|
||||
|
|
@ -814,7 +820,6 @@ class SmmEditView extends TextFileView {
|
|||
)
|
||||
}
|
||||
|
||||
// 更新主题模式
|
||||
_updateThemeMode() {
|
||||
if (this.plugin.settings.themeMode === 'follow') {
|
||||
const isDarkMode = this.plugin._getObIsDark()
|
||||
|
|
@ -824,7 +829,6 @@ class SmmEditView extends TextFileView {
|
|||
}
|
||||
}
|
||||
|
||||
// 获取图片、文件存储路径
|
||||
_getFileSavePath(typeKey, pathKey, subPathKey) {
|
||||
const type = this.plugin.settings[typeKey]
|
||||
const folder = this.plugin.settings[pathKey] || ''
|
||||
|
|
@ -843,6 +847,36 @@ class SmmEditView extends TextFileView {
|
|||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
_clearPopScope() {
|
||||
if (this.popScope) {
|
||||
this.popScope()
|
||||
this.popScope = null
|
||||
}
|
||||
}
|
||||
|
||||
_registerHotkeyOverrides() {
|
||||
this._clearPopScope()
|
||||
const scope = this.app.keymap.getRootScope()
|
||||
const ctrlFHandler = scope.register(['Mod'], 'f', evt => {
|
||||
evt.preventDefault()
|
||||
return true
|
||||
})
|
||||
const ctrlSHandler = scope.register(['Mod'], 's', evt => {
|
||||
evt.preventDefault()
|
||||
const view = this.plugin._getActiveSmmView()
|
||||
if (view) {
|
||||
view.forceSave()
|
||||
}
|
||||
return true
|
||||
})
|
||||
scope.keys.unshift(scope.keys.pop())
|
||||
scope.keys.unshift(scope.keys.pop())
|
||||
this.popScope = () => {
|
||||
scope.unregister(ctrlFHandler)
|
||||
scope.unregister(ctrlSHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SmmEditView
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"mindMap": "Mind map",
|
||||
"rootNodeDefaultText": "Root node",
|
||||
"secondNodeDefaultText": "Secondary node",
|
||||
"branchNodeDefaultText": "Branch node"
|
||||
"branchNodeDefaultText": "Branch topic"
|
||||
},
|
||||
"action": {
|
||||
"createMindMap": "Create new mind map",
|
||||
|
|
@ -23,9 +23,13 @@
|
|||
"changeToMindMapFile": "Convert to mind map document"
|
||||
},
|
||||
"tip": {
|
||||
"tip": "Tip",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"fileExist": "File already exists",
|
||||
"createMindMapFail": "Failed to create mind map",
|
||||
"fileIsNotMd": "Current file is not a Markdown document",
|
||||
"fileIsNotMd": "Current file is not a markdown document",
|
||||
"fileIsEmpty": "File content is empty",
|
||||
"fileFormatError": "File format error",
|
||||
"onlyEnableSelectCurrentVaultFile": "Only files from the current vault can be added",
|
||||
|
|
@ -40,7 +44,14 @@
|
|||
"pluginNoNewVersion": "The current version is already the latest version",
|
||||
"pluginVersionCheckError": "Check failed",
|
||||
"changeSuccess": "Change completed",
|
||||
"mdToMindMapFail": "Conversion failed, unable to parse data that can be converted into a mind map"
|
||||
"mdToMindMapFail": "Conversion failed, unable to parse data that can be converted into a mind map",
|
||||
"imgCreateFail": "Image file creation failed",
|
||||
"mdModifyTip": "Do not modify any information except YAML",
|
||||
"fileNameEmpty": "The file name cannot be empty",
|
||||
"fileNameIllegal": "The file name contains illegal characters",
|
||||
"fileRenameFail": "File renaming failed",
|
||||
"mindMapToMdTip": "After converting to a Markdown document, only some content will be retained, such as node text, summary text, notes, etc. Other content will be discarded and cannot be recovered. Please make a backup first.",
|
||||
"mdToMindMapTip": "Only supports converting syntax such as titles and lists into mind maps. Other content will be discarded and cannot be recovered. Please make a backup first."
|
||||
},
|
||||
"setting": {
|
||||
"title": {
|
||||
|
|
@ -48,7 +59,8 @@
|
|||
"title2": "File",
|
||||
"title3": "Image compression",
|
||||
"title4": "Embed",
|
||||
"title5": "Other"
|
||||
"title5": "Other",
|
||||
"title6": "Image hosting"
|
||||
},
|
||||
"themeMode": {
|
||||
"title": "Theme mode",
|
||||
|
|
@ -61,7 +73,7 @@
|
|||
"title": "Default theme (light color)",
|
||||
"desc": "Default theme for creating a mind map file in light color mode",
|
||||
"title2": "Default theme (dark color)",
|
||||
"desc2": "Default theme for creating a mind map file in dark color mode"
|
||||
"desc2": "Default theme for creating a mind map file in dark mode"
|
||||
},
|
||||
"layout": {
|
||||
"title": "Default layout",
|
||||
|
|
@ -89,7 +101,9 @@
|
|||
"title8": "Support ob search",
|
||||
"desc8": "Mind map data is stored after encoding and compression, so it cannot be searched in OB. If you want to search for the node text content of the mind map in OB, you can turn on this option. After turning it on, the text content of all nodes will be saved in the file for search. The side effect is that it will cause the file size to increase, which may affect the speed of file opening and saving",
|
||||
"title9": "Store canvas position and scaling data",
|
||||
"desc9": "After opening, the canvas position and zoom data will be saved in the file, so that the next time it is opened, it can be restored to the position and zoom state when it was closed last time. The disadvantage is that moving and scaling the canvas will cause file changes"
|
||||
"desc9": "After opening, the canvas position and zoom data will be saved in the file, so that the next time it is opened, it can be restored to the position and zoom state when it was closed last time. The disadvantage is that moving and scaling the canvas will cause file changes",
|
||||
"title10": "The maximum level for converting node text to Markdown titles",
|
||||
"desc10": "Nodes with text less than or equal to this level will be converted to Markdown titles, while the rest will be converted to unordered lists"
|
||||
},
|
||||
"compress": {
|
||||
"title1": "Compress images",
|
||||
|
|
@ -109,7 +123,9 @@
|
|||
},
|
||||
"codeBlock": {
|
||||
"title": "Code block initial height",
|
||||
"desc": "Initial height when embedding via smm code block (pixels)"
|
||||
"desc": "Initial height when embedding via smm code block (pixels)",
|
||||
"title2": "Code block background transparent",
|
||||
"desc2": "Whether the background of the embedded smm code block is transparent, otherwise it will follow the selected mind map theme background color"
|
||||
},
|
||||
"linkInfo": {
|
||||
"issues": "Bugs and feature requests",
|
||||
|
|
@ -117,16 +133,32 @@
|
|||
"community": "Community"
|
||||
},
|
||||
"embed": {
|
||||
"title1": "Embed preview double click to open a new window",
|
||||
"desc1": "When embedding an MD document in format ![[]], double-click to preview the image and open the file in a new window",
|
||||
"title1": "Embed preview double click to open a new tab",
|
||||
"desc1": "When embedding an MD document in format ![[]], double-click to preview the image and open the file in a new tab",
|
||||
"title2": "Embed preview background transparent",
|
||||
"desc2": "Set whether the background of the embedded preview image is transparent"
|
||||
"desc2": "Is the background of the preview image embedded in the format of ![[]] transparent",
|
||||
"title3": "Is the embedded image stored as a separate file",
|
||||
"desc3": "By default, images used for embedding preview in ![[]] format will be stored separately in the repository as SVG files. This way, only the file path is stored in the mind map file, which can significantly reduce the file size and speed up file reading and saving. If closed, the image data will be stored in the mind map file, and the volume will significantly increase",
|
||||
"title4": "Embedded image file storage directory",
|
||||
"desc4": "The folder where the embedded preview image files are stored should not be empty relative to the repository root directory, and the directory should start with . as the hidden folder",
|
||||
"title5": "New tab opens node link",
|
||||
"desc5": "Does clicking on the inserted node link (excluding network links) open in a new tab. After closing, you can also open a new tab by pressing Ctrl+left mouse button"
|
||||
},
|
||||
"file": {
|
||||
"title1": "New mind map file name prefix",
|
||||
"desc1": "Default naming format for new mind map files: Prefix + Space + Date Timestamp",
|
||||
"title2": "Date timestamp format for new mind map files",
|
||||
"desc2": "Default format: YYYY-MM-DD HH.mm.ss, Example: 2025-01-01 23.59.59. For details on format meaning and modification, refer to: https://day.js.org/docs/en/parse/string-format"
|
||||
"title1": "File name format for creating a new mind map",
|
||||
"desc1": "Naming format for creating a new mind map file name. Support variables other than notename",
|
||||
"title3": "Naming format for pasting images",
|
||||
"desc3": "Naming format for images pasted on nodes",
|
||||
"filenameFormatDesc": "File name format description"
|
||||
},
|
||||
"font": {
|
||||
"title": "Font",
|
||||
"desc": "Extended font list for mind maps",
|
||||
"btn": "Manage",
|
||||
"placeholder": "Search font. You can also press Enter to add input fonts directly",
|
||||
"empty": "No font",
|
||||
"noMatch": "No matching font",
|
||||
"noFont": "No available font"
|
||||
},
|
||||
"button": {
|
||||
"select": "Select"
|
||||
|
|
@ -134,48 +166,21 @@
|
|||
"other": {
|
||||
"title1": "Check for new versions",
|
||||
"desc1": "After activation, it will check if a new version is available when the plugin starts. If your network cannot access Github, it is recommended to disable this option"
|
||||
},
|
||||
"imageHosting": {
|
||||
"title1": "Open the picture bed",
|
||||
"desc1": "After activation, all images will be uploaded to the image bed",
|
||||
"tip1": "Please first improve the configuration information of the drawing bed",
|
||||
"title2": "Picture bed URL",
|
||||
"desc2": "Fill in the corresponding upload interface address based on the graphic tool you are using",
|
||||
"title3": "Form field",
|
||||
"desc3": "Name of the form field when uploading images",
|
||||
"title4": "Response structure",
|
||||
"desc4": "Response structure when uploading images. Please refer to the detailed description for more information:",
|
||||
"desc5": "Image bed function description"
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"save": "Edit operation-Save",
|
||||
"saveAndUpdateImage": "Edit operation-Save and update image",
|
||||
"undo": "Edit operation-Undo",
|
||||
"redo": "Edit operation-Redo",
|
||||
"searchReplace": "Edit operation-Search and replace",
|
||||
"insertChildNode": "Node operation-Insert child node",
|
||||
"insertSiblingNode": "Node operation-Insert sibling node",
|
||||
"insertParentNode": "Node operation-Insert parent node",
|
||||
"moveUpNode": "Node operation-Move up node",
|
||||
"moveDownNode": "Node operation-Move down node",
|
||||
"insertSummary": "Node operation-Insert summary",
|
||||
"toggleExpandNode": "Node operation-Expand/collapse node",
|
||||
"deleteNode": "Node operation-Delete node",
|
||||
"deleteNodeOnly": "Node operation-Delete current node only",
|
||||
"copyNode": "Node operation-Copy node",
|
||||
"cutNode": "Node operation-Cut node",
|
||||
"pasteNode": "Node operation-Paste node",
|
||||
"selectAllNode": "Node operation-Select all",
|
||||
"organizeLayout": "Node operation-Organize layout with one click",
|
||||
"toggleBoldNode": "Node operation-Toggle bold",
|
||||
"toggleItalicNode": "Node operation-Toggle italic",
|
||||
"toggleUnderlineNode": "Node operation-Toggle underline",
|
||||
"toggleStrikethroughNode": "Node operation-Toggle strikethrough",
|
||||
"increaseFontSizeNode": "Node operation-Increase font size",
|
||||
"decreaseFontSizeNode": "Node operation-Decrease font size",
|
||||
"zoomInCanvas": "Canvas operation-Zoom in",
|
||||
"zoomOutCanvas": "Canvas operation-Zoom out",
|
||||
"backToRootCanvas": "Canvas operation-Back to root node",
|
||||
"activateLeftCanvas": "Canvas operation-Move focus left",
|
||||
"activateTopCanvas": "Canvas operation-Move focus up",
|
||||
"activateRightCanvas": "Canvas operation-Move focus right",
|
||||
"activateBottomCanvas": "Canvas operation-Move focus down",
|
||||
"zoomFitCanvas": "Canvas operation-Fit to canvas",
|
||||
"enterDemonstrate": "Auxiliary operation-Enter demonstration mode",
|
||||
"openImageEdit": "Interface operation-Open image edit dialog",
|
||||
"openHyperlinkEdit": "Interface operation-Open hyperlink edit dialog",
|
||||
"openRemarkEdit": "Interface operation-Open remark edit dialog",
|
||||
"openTagEdit": "Interface operation-Open tag edit dialog",
|
||||
"openImport": "Interface operation-Open import dialog",
|
||||
"openExport": "Interface operation-Open export dialog"
|
||||
"html": {
|
||||
"filenameFormatDesc": "<p>In addition to fixed names, variables can also be used, enclosed in {}. Available variables are as follows:</p><p>1.{notename}: The current file name without extension;</p><p>2.{date}: Current date in YYYY-MM-DD format;</p><p>3.{time}: Current time in HH:mm:ss format;</p><p>4.{datatime}: Current date and time in YYYY-MM-DD HH:mm:ss format;</p><p>5.{date:Format}: Specify date/time format, e.g.: MM-DD HH:mm, reference: <a href='https://day.js.org/docs/en/parse/string-format'>Format specification</a>;</p><p>6.{random}: Random string with 6-character length;</p>"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@
|
|||
"changeToMindMapFile": "Chuyển đổi sang tài liệu bản đồ tư duy"
|
||||
},
|
||||
"tip": {
|
||||
"tip": "Mẹo",
|
||||
"confirm": "Xác nhận",
|
||||
"cancel": "Hủy bỏ",
|
||||
"close": "Đóng cửa",
|
||||
"fileExist": "Tệp đã tồn tại",
|
||||
"createMindMapFail": "Tạo sơ đồ tư duy thất bại",
|
||||
"fileIsNotMd": "Tệp hiện tại không phải tệp markdown",
|
||||
|
|
@ -40,7 +44,14 @@
|
|||
"pluginNoNewVersion": "Hiện tại là phiên bản mới nhất",
|
||||
"pluginVersionCheckError": "Kiểm tra thất bại",
|
||||
"changeSuccess": "Hoàn thành chuyển đổi",
|
||||
"mdToMindMapFail": "Chuyển đổi thất bại, không có dữ liệu được phân tích để có thể được chuyển đổi thành Bản đồ tư duy"
|
||||
"mdToMindMapFail": "Chuyển đổi thất bại, không có dữ liệu được phân tích để có thể được chuyển đổi thành Bản đồ tư duy",
|
||||
"imgCreateFail": "Lỗi tạo tập tin ảnh",
|
||||
"mdModifyTip": "Không thay đổi bất kỳ thông tin nào khác ngoài YAML",
|
||||
"fileNameEmpty": "Tên tập tin không được để trống",
|
||||
"fileNameIllegal": "Tên tập tin chứa ký tự không hợp lệ",
|
||||
"fileRenameFail": "Lỗi đổi tên tập tin",
|
||||
"mindMapToMdTip": "Sau khi chuyển đổi sang tài liệu Markdown, chỉ một số nội dung sẽ được giữ lại, chẳng hạn như văn bản nút, văn bản tóm tắt, ghi chú, v.v., những nội dung khác sẽ bị loại bỏ và không thể khôi phục, hãy sao lưu trước.",
|
||||
"mdToMindMapTip": "Chỉ hỗ trợ chuyển đổi tiêu đề, danh sách và cú pháp khác thành bản đồ tư duy, các nội dung khác sẽ bị loại bỏ và không thể phục hồi, hãy sao lưu trước."
|
||||
},
|
||||
"setting": {
|
||||
"title": {
|
||||
|
|
@ -48,7 +59,8 @@
|
|||
"title2": "Tệp",
|
||||
"title3": "Nén hình ảnh",
|
||||
"title4": "Hình ảnh",
|
||||
"title5": "Khác"
|
||||
"title5": "Khác",
|
||||
"title6": "Hình ảnh hosting"
|
||||
},
|
||||
"themeMode": {
|
||||
"title": "Chế độ chủ đề",
|
||||
|
|
@ -89,7 +101,9 @@
|
|||
"title8": "Hỗ trợ tìm kiếm OB",
|
||||
"desc8": "Tư duy bản đồ dữ liệu là mã hóa nén sau khi lưu trữ, vì vậy không thể tìm kiếm trong ob, nếu bạn muốn trong ob có thể tìm kiếm nội dung văn bản nút của bản đồ tư duy, bạn có thể mở tùy chọn này, sau khi mở sẽ lưu thêm tất cả các nút trong các tập tin nội dung văn bản để tìm kiếm, mang lại tác dụng phụ sẽ dẫn đến khối lượng tập tin lớn hơn, có thể ảnh hưởng đến tốc độ mở và lưu tập tin",
|
||||
"title9": "Lưu trữ vị trí canvas và dữ liệu thu phóng",
|
||||
"desc9": "Vị trí khung và dữ liệu thu phóng được lưu trong tệp sau khi mở để bạn có thể khôi phục lại vị trí và trạng thái thu phóng của lần đóng cuối cùng khi mở tiếp theo. Nhược điểm là chỉ cần di chuyển và phóng to canvas sẽ làm thay đổi tập tin."
|
||||
"desc9": "Vị trí khung và dữ liệu thu phóng được lưu trong tệp sau khi mở để bạn có thể khôi phục lại vị trí và trạng thái thu phóng của lần đóng cuối cùng khi mở tiếp theo. Nhược điểm là chỉ cần di chuyển và phóng to canvas sẽ làm thay đổi tập tin.",
|
||||
"title10": "Mức tối đa của văn bản nút đến tiêu đề Markdown",
|
||||
"desc10": "Văn bản nút nhỏ hơn hoặc bằng cấp này được chuyển thành tiêu đề Markdown và phần còn lại được chuyển thành danh sách không có thứ tự."
|
||||
},
|
||||
"compress": {
|
||||
"title1": "Có nén ảnh không",
|
||||
|
|
@ -109,7 +123,9 @@
|
|||
},
|
||||
"codeBlock": {
|
||||
"title": "Chiều cao ban đầu của khối mã nhúng",
|
||||
"desc": "Chiều cao ban đầu khi nhúng bằng khối mã smm, đơn vị: px"
|
||||
"desc": "Chiều cao ban đầu khi nhúng bằng khối mã smm, đơn vị: px",
|
||||
"title2": "Nền nhúng khối mã có trong suốt hay không",
|
||||
"desc2": "Nền có trong suốt khi nhúng khối mã smm hay không, nếu không nó sẽ theo màu nền của chủ đề bản đồ tư duy đã chọn"
|
||||
},
|
||||
"linkInfo": {
|
||||
"issues": "Gửi bug hoặc gợi ý",
|
||||
|
|
@ -120,13 +136,29 @@
|
|||
"title1": "Nhúng xem thử Nhấp đúp vào cửa sổ mới để mở",
|
||||
"desc1": "![[]] Định dạng Nhấp đúp vào ô xem thử ảnh khi nhúng tài liệu md có cửa sổ mới Mở tập tin không",
|
||||
"title2": "Hình nền nhúng xem thử có trong suốt không",
|
||||
"desc2": "Thiết lập hình nền nhúng xem thử có trong suốt không"
|
||||
"desc2": "![[]] Định dạng Nền ảnh được nhúng trong ô xem thử có trong suốt không",
|
||||
"title3": "Cho dù hình ảnh nhúng được lưu trữ dưới dạng tệp riêng biệt",
|
||||
"desc3": "Hình ảnh được sử dụng mặc định để nhúng xem trước ở định dạng ![[]] được lưu trữ riêng biệt trong kho dưới dạng tệp svg để chỉ đường dẫn tệp được lưu trữ trong tệp Bản đồ Tư duy có thể làm giảm đáng kể khối lượng tệp và tăng tốc độ đọc và lưu tệp. Nếu tắt, dữ liệu hình ảnh sẽ được lưu trữ trong tệp Bản đồ Tư duy và khối lượng sẽ tăng lên đáng kể.",
|
||||
"title4": "Nhúng thư mục lưu trữ tập tin ảnh",
|
||||
"desc4": "Nhúng xem trước hình ảnh tập tin lưu trữ thư mục, liên quan đến thư mục gốc kho, không nên để trống và thư mục bắt đầu bằng . như một thư mục ẩn",
|
||||
"title5": "Trang nhãn mới Mở liên kết nút",
|
||||
"desc5": "Nhấp vào liên kết nút được chèn (không chứa liên kết mạng) để mở trên trang tab mới. Bạn cũng có thể mở tab mới bằng Ctrl+chuột trái sau khi đóng"
|
||||
},
|
||||
"file": {
|
||||
"title1": "Tiền tố tên tệp sơ đồ tư duy mới",
|
||||
"desc1": "Định dạng đặt tên mặc định cho tên tệp sơ đồ tư duy mới là: Tiền tố + Dấu cách + Dấu thời gian ngày tháng",
|
||||
"title2": "Định dạng dấu thời gian ngày tháng cho tên tệp sơ đồ tư duy mới",
|
||||
"desc2": "Mặc định: YYYY-MM-DD HH.mm.ss, Ví dụ thực tế: 2025-01-01 23.59.59. Nếu không rõ ý nghĩa cụ thể của định dạng này và cách sửa đổi, vui lòng tham khảo: https://day.js.org/docs/en/parse/string-format"
|
||||
"title1": "Định dạng tên tập tin cho bản đồ tư duy mới",
|
||||
"desc1": "Dạng thức đặt tên cho tên tệp bản đồ tư duy mới. Hỗ trợ các biến khác ngoài notename",
|
||||
"title3": "Định dạng có tên cho ảnh dán",
|
||||
"desc3": "Định dạng có tên của ảnh được dán trên nút",
|
||||
"filenameFormatDesc": "Mô tả định dạng tên tập tin"
|
||||
},
|
||||
"font": {
|
||||
"title": "Phông chữ",
|
||||
"desc": "Phông chữ mặc định khi tạo bản đồ tư duy mới",
|
||||
"btn": "Quản lý",
|
||||
"placeholder": "Tìm kiếm phông chữ. Bạn cũng có thể nhấn Enter để thêm các phông chữ nhập trực tiếp",
|
||||
"empty": "Không có phông chữ",
|
||||
"noMatch": "Không tìm thấy phông chữ",
|
||||
"noFont": "Không có phông chữ"
|
||||
},
|
||||
"button": {
|
||||
"select": "Chọn"
|
||||
|
|
@ -134,48 +166,21 @@
|
|||
"other": {
|
||||
"title1": "Kiểm tra phiên bản mới",
|
||||
"desc1": "Khi plugin khởi động, bạn sẽ kiểm tra xem có phiên bản mới không và nếu mạng của bạn không truy cập được Github, bạn nên tắt tùy chọn này."
|
||||
},
|
||||
"imageHosting": {
|
||||
"title1": "Mở giường",
|
||||
"desc1": "Sau khi mở, tất cả hình ảnh sẽ được tải lên giường.",
|
||||
"tip1": "Vui lòng hoàn thiện thông tin cấu hình giường",
|
||||
"title2": "Giường Url",
|
||||
"desc2": "Điền địa chỉ giao diện tải lên tương ứng với công cụ giường chiếu mà bạn sử dụng",
|
||||
"title3": "Trường mẫu",
|
||||
"desc3": "Tên trường trong biểu mẫu khi tải lên hình ảnh",
|
||||
"title4": "Cấu trúc phản hồi",
|
||||
"desc4": "Cấu trúc phản hồi khi tải lên hình ảnh. Hãy tham khảo thêm tại:",
|
||||
"desc5": "Hướng dẫn sử dụng giường"
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"save": "Thao tác chỉnh sửa - Lưu",
|
||||
"saveAndUpdateImage": "Thao tác chỉnh sửa - Lưu và cập nhật hình ảnh",
|
||||
"undo": "Thao tác chỉnh sửa - Quay lại",
|
||||
"redo": "Thao tác chỉnh sửa - Tiến tới",
|
||||
"searchReplace": "Thao tác chỉnh sửa - Tìm kiếm và thay thế",
|
||||
"insertChildNode": "Thao tác nút - Chèn nút con",
|
||||
"insertSiblingNode": "Thao tác nút - Chèn nút cùng cấp",
|
||||
"insertParentNode": "Thao tác nút - Chèn nút cha",
|
||||
"moveUpNode": "Thao tác nút - Di chuyển nút lên",
|
||||
"moveDownNode": "Thao tác nút - Di chuyển nút xuống",
|
||||
"insertSummary": "Thao tác nút - Chèn tóm tắt",
|
||||
"toggleExpandNode": "Thao tác nút - Mở rộng/Thu gọn nút",
|
||||
"deleteNode": "Thao tác nút - Xóa nút",
|
||||
"deleteNodeOnly": "Thao tác nút - Chỉ xóa nút hiện tại",
|
||||
"copyNode": "Thao tác nút - Sao chép nút",
|
||||
"cutNode": "Thao tác nút - Cắt nút",
|
||||
"pasteNode": "Thao tác nút - Dán nút",
|
||||
"selectAllNode": "Thao tác nút - Chọn tất cả",
|
||||
"organizeLayout": "Thao tác nút - Sắp xếp bố cục một lần",
|
||||
"toggleBoldNode": "Thao tác nút - Chuyển đổi in đậm",
|
||||
"toggleItalicNode": "Thao tác nút - Chuyển đổi in nghiêng",
|
||||
"toggleUnderlineNode": "Thao tác nút - Chuyển đổi gạch chân",
|
||||
"toggleStrikethroughNode": "Thao tác nút - Chuyển đổi gạch ngang",
|
||||
"increaseFontSizeNode": "Thao tác nút - Tăng kích thước chữ",
|
||||
"decreaseFontSizeNode": "Thao tác nút - Giảm kích thước chữ",
|
||||
"zoomInCanvas": "Thao tác canvas - Phóng to",
|
||||
"zoomOutCanvas": "Thao tác canvas - Thu nhỏ",
|
||||
"backToRootCanvas": "Thao tác canvas - Quay về nút gốc",
|
||||
"activateLeftCanvas": "Thao tác canvas - Di chuyển tiêu điểm sang trái",
|
||||
"activateTopCanvas": "Thao tác canvas - Di chuyển tiêu điểm lên trên",
|
||||
"activateRightCanvas": "Thao tác canvas - Di chuyển tiêu điểm sang phải",
|
||||
"activateBottomCanvas": "Thao tác canvas - Di chuyển tiêu điểm xuống dưới",
|
||||
"zoomFitCanvas": "Thao tác canvas - Vừa với canvas",
|
||||
"enterDemonstrate": "Thao tác hỗ trợ - Vào chế độ trình diễn",
|
||||
"openImageEdit": "Thao tác giao diện - Mở hộp thoại chỉnh sửa hình ảnh",
|
||||
"openHyperlinkEdit": "Thao tác giao diện - Mở hộp thoại chỉnh sửa siêu liên kết",
|
||||
"openRemarkEdit": "Thao tác giao diện - Mở hộp thoại chỉnh sửa ghi chú",
|
||||
"openTagEdit": "Thao tác giao diện - Mở hộp thoại chỉnh sửa thẻ",
|
||||
"openImport": "Thao tác giao diện - Mở hộp thoại nhập",
|
||||
"openExport": "Thao tác giao diện - Mở hộp thoại xuất"
|
||||
"html": {
|
||||
"filenameFormatDesc": "<p>Ngoài các tên cố định, bạn cũng có thể sử dụng các biến, được đặt trong dấu ngoặc nhọn {}, các biến khả dụng như sau:</p><p>1.{notename}: Tên tệp hiện tại, không bao gồm phần mở rộng;</p><p>2.{date}: Ngày hiện tại, định dạng YYYY-MM-DD;</p><p>3.{time}: Thời gian hiện tại, định dạng HH:mm:ss;</p><p>4.{datatime}: Ngày giờ hiện tại, định dạng: YYYY-MM-DD HH:mm:ss;</p><p>5.{date:Format}: Chỉ định định dạng ngày giờ, ví dụ: MM-DD HH:mm, tham khảo thêm tại: <a href='https://day.js.org/docs/en/parse/string-format'>hướng dẫn định dạng</a>;</p><p>6.{random}: Chuỗi ngẫu nhiên, độ dài 6 ký tự;</p>"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@
|
|||
"changeToMindMapFile": "转换为思维导图文档"
|
||||
},
|
||||
"tip": {
|
||||
"tip": "提示",
|
||||
"confirm": "确认",
|
||||
"cancel": "取消",
|
||||
"close": "关闭",
|
||||
"fileExist": "文件已经存在",
|
||||
"createMindMapFail": "新建思维导图失败",
|
||||
"fileIsNotMd": "当前文件不是markdown文件",
|
||||
|
|
@ -40,7 +44,14 @@
|
|||
"pluginNoNewVersion": "当前已是最新版本",
|
||||
"pluginVersionCheckError": "检查失败",
|
||||
"changeSuccess": "转换完成",
|
||||
"mdToMindMapFail": "转换失败,没有解析到可以转换为思维导图的数据"
|
||||
"mdToMindMapFail": "转换失败,没有解析到可以转换为思维导图的数据",
|
||||
"imgCreateFail": "图片文件创建失败",
|
||||
"mdModifyTip": "请勿修改除YAML外的任何信息",
|
||||
"fileNameEmpty": "文件名不能为空",
|
||||
"fileNameIllegal": "文件名包含非法字符",
|
||||
"fileRenameFail": "文件重命名失败",
|
||||
"mindMapToMdTip": "转换为Markdown文档后,仅会保留部分内容,比如节点文本、概要文本、备注等,其他内容将丢弃且不可恢复,请先做好备份。",
|
||||
"mdToMindMapTip": "仅支持将标题、列表等语法转换为思维导图,其他内容将丢弃且不可恢复,请先做好备份。"
|
||||
},
|
||||
"setting": {
|
||||
"title": {
|
||||
|
|
@ -48,12 +59,13 @@
|
|||
"title2": "文件",
|
||||
"title3": "图片压缩",
|
||||
"title4": "嵌入",
|
||||
"title5": "其他"
|
||||
"title5": "其他",
|
||||
"title6": "图床"
|
||||
},
|
||||
"themeMode": {
|
||||
"title": "主题模式",
|
||||
"desc": "深色、浅色模式",
|
||||
"option1": "跟随Obsidian",
|
||||
"option1": "跟随obsidian",
|
||||
"option2": "浅色模式",
|
||||
"option3": "深色模式"
|
||||
},
|
||||
|
|
@ -89,10 +101,12 @@
|
|||
"title8": "支持ob搜索",
|
||||
"desc8": "思维导图数据是编码压缩后存储的,所以无法在ob中搜索,如果你想在ob中能搜索到思维导图的节点文本内容,可以开启该选项,开启后会在文件中额外保存所有节点的文本内容用于搜索,带来的副作用是会导致文件体积变大,可能会影响文件打开和保存的速度",
|
||||
"title9": "存储画布位置和缩放数据",
|
||||
"desc9": "开启后会在文件中保存画布位置和缩放数据,这样下次打开时能恢复到上次关闭时的位置和缩放状态。缺点是只要移动和缩放过画布就会造成文件改动"
|
||||
"desc9": "开启后会在文件中保存画布位置和缩放数据,这样下次打开时能恢复到上次关闭时的位置和缩放状态。缺点是只要移动和缩放过画布就会造成文件改动",
|
||||
"title10": "节点文字转Markdown标题的最大层级",
|
||||
"desc10": "小于等于该层级的节点文字会转为Markdown标题,其余的转为无序列表"
|
||||
},
|
||||
"compress": {
|
||||
"title1": "是否压缩图片",
|
||||
"title1": "压缩图片",
|
||||
"desc1": "是否压缩上传的节点图片(从本地选择的图片)",
|
||||
"title2": "最大压缩宽度",
|
||||
"desc2": "图片压缩后的最大宽度",
|
||||
|
|
@ -109,7 +123,9 @@
|
|||
},
|
||||
"codeBlock": {
|
||||
"title": "代码块嵌入初始高度",
|
||||
"desc": "smm代码块方式嵌入时初始高度,单位:px"
|
||||
"desc": "smm代码块方式嵌入时初始高度,单位:px",
|
||||
"title2": "代码块嵌入背景是否透明",
|
||||
"desc2": "smm代码块嵌入时背景是否透明,否则会跟随所选的思维导图主题背景颜色"
|
||||
},
|
||||
"linkInfo": {
|
||||
"issues": "提交bug或建议",
|
||||
|
|
@ -117,16 +133,32 @@
|
|||
"community": "社区"
|
||||
},
|
||||
"embed": {
|
||||
"title1": "嵌入预览双击是否新窗口打开",
|
||||
"desc1": "![[]]格式嵌入md文档时双击预览图像是否新窗口打开文件",
|
||||
"title1": "嵌入预览双击是否新标签页打开",
|
||||
"desc1": "![[]]格式嵌入md文档时双击预览图像是否新标签页打开文件",
|
||||
"title2": "嵌入预览的图像背景是否透明",
|
||||
"desc2": "![[]]格式嵌入预览的图像背景是否透明"
|
||||
"desc2": "![[]]格式嵌入预览的图像背景是否透明",
|
||||
"title3": "嵌入的图片是否以单独文件的形式存储",
|
||||
"desc3": "默认用于![[]]格式嵌入预览的图片会以svg格式的文件单独存储到仓库里,这样思维导图文件中只存储文件路径,可以显著减小文件体积,加快文件的读取和保存速度。如果关闭,则会把图片数据存储到思维导图文件中,体积会显著增大",
|
||||
"title4": "嵌入图片文件存储目录",
|
||||
"desc4": "嵌入预览的图片文件存储的文件夹,相对于仓库根目录,建议不要为空,且目录以.开头作为隐藏文件夹",
|
||||
"title5": "新标签页打开节点链接",
|
||||
"desc5": "点击插入的节点链接(不包含网络链接)是否在新标签页打开。关闭后也可以通过Ctrl+鼠标左键在新标签页打开"
|
||||
},
|
||||
"file": {
|
||||
"title1": "新建思维导图的文件名前缀",
|
||||
"desc1": "新建思维导图文件名默认的命名格式为:前缀 + 空格 + 日期时间戳",
|
||||
"title2": "新建思维导图的文件名日期时间戳格式",
|
||||
"desc2": "默认为:YYYY-MM-DD HH.mm.ss,实际示例:2025-01-01 23.59.59,如不清楚该格式的具体含义及如何修改,请参考:https://day.js.org/docs/en/parse/string-format"
|
||||
"title1": "新建思维导图的文件名格式",
|
||||
"desc1": "新建思维导图文件名的命名格式。支持除notename外的其他变量",
|
||||
"title3": "粘贴图片的命名格式",
|
||||
"desc3": "在节点上粘贴的图片的命名格式",
|
||||
"filenameFormatDesc": "文件名格式说明"
|
||||
},
|
||||
"font": {
|
||||
"title": "字体",
|
||||
"desc": "扩展思维导图的字体列表",
|
||||
"btn": "管理",
|
||||
"placeholder": "搜索字体。也可按回车键直接添加输入的字体",
|
||||
"empty": "暂无已添加字体",
|
||||
"noMatch": "无匹配字体",
|
||||
"noFont": "没有可用字体"
|
||||
},
|
||||
"button": {
|
||||
"select": "选择"
|
||||
|
|
@ -134,48 +166,21 @@
|
|||
"other": {
|
||||
"title1": "检查新版本",
|
||||
"desc1": "开启后会在插件启动时检查是否有新版本可用,如果你的网络访问不到Github,则建议关闭该选项"
|
||||
},
|
||||
"imageHosting": {
|
||||
"title1": "开启图床",
|
||||
"desc1": "开启后,所有图片都将上传到图床",
|
||||
"tip1": "请先完善图床配置信息",
|
||||
"title2": "图床url",
|
||||
"desc2": "根据你使用的图床工具填写对应的上传接口地址",
|
||||
"title3": "表单字段",
|
||||
"desc3": "上传图片时的表单字段名称",
|
||||
"title4": "接口响应结构",
|
||||
"desc4": "上传图片时的接口响应结构。详细说明请参考:",
|
||||
"desc5": "图床功能说明"
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"save": "编辑操作-保存",
|
||||
"saveAndUpdateImage": "编辑操作-保存并更新图像",
|
||||
"undo": "编辑操作-回退",
|
||||
"redo": "编辑操作-前进",
|
||||
"searchReplace": "编辑操作-搜索和替换",
|
||||
"insertChildNode": "节点操作-插入下级节点",
|
||||
"insertSiblingNode": "节点操作-插入同级节点",
|
||||
"insertParentNode": "节点操作-插入父节点",
|
||||
"moveUpNode": "节点操作-上移节点",
|
||||
"moveDownNode": "节点操作-下移节点",
|
||||
"insertSummary": "节点操作-插入概要",
|
||||
"toggleExpandNode": "节点操作-展开/收起节点",
|
||||
"deleteNode": "节点操作-删除节点",
|
||||
"deleteNodeOnly": "节点操作-仅删除当前节点",
|
||||
"copyNode": "节点操作-复制节点",
|
||||
"cutNode": "节点操作-剪切节点",
|
||||
"pasteNode": "节点操作-粘贴节点",
|
||||
"selectAllNode": "节点操作-全选",
|
||||
"organizeLayout": "节点操作-一键整理布局",
|
||||
"toggleBoldNode": "节点操作-切换粗体",
|
||||
"toggleItalicNode": "节点操作-切换斜体",
|
||||
"toggleUnderlineNode": "节点操作-切换下划线",
|
||||
"toggleStrikethroughNode": "节点操作-切换删除线",
|
||||
"increaseFontSizeNode": "节点操作-增大字号",
|
||||
"decreaseFontSizeNode": "节点操作-减小字号",
|
||||
"zoomInCanvas": "画布操作-放大",
|
||||
"zoomOutCanvas": "画布操作-缩小",
|
||||
"backToRootCanvas": "画布操作-回到根节点",
|
||||
"activateLeftCanvas": "画布操作-向左移动焦点",
|
||||
"activateTopCanvas": "画布操作-向上移动焦点",
|
||||
"activateRightCanvas": "画布操作-向右移动焦点",
|
||||
"activateBottomCanvas": "画布操作-向下移动焦点",
|
||||
"zoomFitCanvas": "画布操作-适应画布",
|
||||
"enterDemonstrate": "辅助操作-进入演示模式",
|
||||
"openImageEdit": "界面操作-打开图片编辑弹窗",
|
||||
"openHyperlinkEdit": "界面操作-打开超链接编辑弹窗",
|
||||
"openRemarkEdit": "界面操作-打开备注编辑弹窗",
|
||||
"openTagEdit": "界面操作-打开标签编辑弹窗",
|
||||
"openImport": "界面操作-打开导入弹窗",
|
||||
"openExport": "界面操作-打开导出弹窗"
|
||||
"html": {
|
||||
"filenameFormatDesc": "<p>除了固定名称外,也可以使用变量,用 {} 括起来,可用变量如下:</p><p>1.{notename}:当前文件的文件名,不包含扩展名;</p><p>2.{date}:当前日期,格式为 YYYY-MM-DD;</p><p>3.{time}:当前时间,格式为 HH:mm:ss;</p><p>4.{datatime}:当前日期时间,格式为:YYYY-MM-DD HH:mm:ss;</p><p>5.{date:Format}:指定日期时间格式,比如:MM-DD HH:mm,可参考:<a href='https://day.js.org/docs/en/parse/string-format'>格式说明</a>;</p><p>6.{random}:随机字符串,长度为 6 位;</p>"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@
|
|||
"changeToMindMapFile": "轉換為心智圖檔案"
|
||||
},
|
||||
"tip": {
|
||||
"tip": "提示",
|
||||
"confirm": "確認",
|
||||
"cancel": "取消",
|
||||
"close": "關閉",
|
||||
"fileExist": "檔案已經存在",
|
||||
"createMindMapFail": "新建思維導圖失敗",
|
||||
"fileIsNotMd": "當前檔案不是 markdown 檔案",
|
||||
|
|
@ -40,7 +44,14 @@
|
|||
"pluginNoNewVersion": "當前已是最新版本",
|
||||
"pluginVersionCheckError": "檢查失敗",
|
||||
"changeSuccess": "轉換完成",
|
||||
"mdToMindMapFail": "轉換失敗,沒有解析到可以轉換為心智圖的數據"
|
||||
"mdToMindMapFail": "轉換失敗,沒有解析到可以轉換為心智圖的數據",
|
||||
"imgCreateFail": "圖片檔案創建失敗",
|
||||
"mdModifyTip": "請勿修改除YAML外的任何資訊",
|
||||
"fileNameEmpty": "檔名不能為空",
|
||||
"fileNameIllegal": "檔名包含非法字元",
|
||||
"fileRenameFail": "檔案重命名失敗",
|
||||
"mindMapToMdTip": "轉換為Markdown檔案後,僅會保留部分內容,比如節點文字、概要文字、備註等,其他內容將丟棄且不可恢復,請先做好備份。",
|
||||
"mdToMindMapTip": "僅支持將標題、清單等語法轉換為心智圖,其他內容將丟棄且不可恢復,請先做好備份。"
|
||||
},
|
||||
"setting": {
|
||||
"title": {
|
||||
|
|
@ -48,7 +59,8 @@
|
|||
"title2": "文件",
|
||||
"title3": "圖片壓縮",
|
||||
"title4": "嵌入",
|
||||
"title5": "其他"
|
||||
"title5": "其他",
|
||||
"title6": "圖床主機"
|
||||
},
|
||||
"themeMode": {
|
||||
"title": "主題模式",
|
||||
|
|
@ -89,7 +101,9 @@
|
|||
"title8": "支持ob蒐索",
|
||||
"desc8": "心智圖數據是編碼壓縮後存儲的,所以無法在ob中蒐索,如果你想在ob中能蒐索到心智圖的節點文字內容,可以開啟該選項,開啟後會在檔案中額外保存所有節點的文字內容用於蒐索,帶來的副作用是會導致檔案體積變大,可能會影響檔案打開和保存的速度",
|
||||
"title9": "存儲畫布位置和縮放數據",
|
||||
"desc9": "開啟後會在檔案中保存畫布位置和縮放數據,這樣下次打開時能恢復到上次關閉時的位置和縮放狀態。缺點是只要移動和縮放過畫布就會造成檔案改動"
|
||||
"desc9": "開啟後會在檔案中保存畫布位置和縮放數據,這樣下次打開時能恢復到上次關閉時的位置和縮放狀態。缺點是只要移動和縮放過畫布就會造成檔案改動",
|
||||
"title10": "節點文字轉Markdown標題的最大層級",
|
||||
"desc10": "小於等於該層級的節點文字會轉為Markdown標題,其餘的轉為無序清單"
|
||||
},
|
||||
"compress": {
|
||||
"title1": "壓縮圖片",
|
||||
|
|
@ -109,7 +123,9 @@
|
|||
},
|
||||
"codeBlock": {
|
||||
"title": "程式碼區塊嵌入初始高度",
|
||||
"desc": "smm 程式碼區塊方式嵌入時初始高度,單位:px"
|
||||
"desc": "smm 程式碼區塊方式嵌入時初始高度,單位:px",
|
||||
"title2": "程式碼塊嵌入背景是否透明",
|
||||
"desc2": "smm程式碼塊嵌入時背景是否透明,否則會跟隨所選的心智圖主題背景顏色"
|
||||
},
|
||||
"linkInfo": {
|
||||
"issues": "提交bug或建議",
|
||||
|
|
@ -117,16 +133,32 @@
|
|||
"community": "社區"
|
||||
},
|
||||
"embed": {
|
||||
"title1": "嵌入預覽按兩下是否新窗口打開",
|
||||
"desc1": "![[]]格式嵌入md檔案時按兩下預覽影像是否新窗口打開文件",
|
||||
"title1": "嵌入預覽按兩下是否新標籤頁打開",
|
||||
"desc1": "![[]]格式嵌入md檔案時按兩下預覽影像是否新標籤頁打開文件",
|
||||
"title2": "嵌入預覽的圖像背景是否透明",
|
||||
"desc2": "設定![[]]格式嵌入預覽的圖像背景是否透明"
|
||||
"desc2": "設定![[]]格式嵌入預覽的圖像背景是否透明",
|
||||
"title3": "嵌入的圖片是否以單獨檔案的形式存儲",
|
||||
"desc3": "默認用於![[]]格式嵌入預覽的圖片會以svg格式的檔案單獨存儲到倉庫裏,這樣心智圖檔案中只存儲檔案路徑,可以顯著减小檔案體積,加快檔案的讀取和保存速度。 如果關閉,則會把圖片資料存儲到心智圖檔案中,體積會顯著增大",
|
||||
"title4": "嵌入圖片檔存儲目錄",
|
||||
"desc4": "嵌入預覽的圖片檔存儲的資料夾,相對於倉庫根目錄,建議不要為空,且目錄以.開頭作為隱藏資料夾",
|
||||
"title5": "新標籤頁打開節點連結",
|
||||
"desc5": "點擊插入的節點連結(不包含網絡連結)是否在新標籤頁打開。 關閉後也可以通過Ctrl+滑鼠左鍵在新標籤頁打開"
|
||||
},
|
||||
"file": {
|
||||
"title1": "新建思維導圖的檔案名稱前綴",
|
||||
"desc1": "新建思維導圖檔案名稱預設的命名格式為:前綴 + 空格 + 日期時間戳",
|
||||
"title2": "新建思維導圖的檔案名稱日期時間戳格式",
|
||||
"desc2": "預設為:YYYY-MM-DD HH.mm.ss,實際範例:2025-01-01 23.59.59,如不清楚該格式的具體含義及如何修改,請參考:https://day.js.org/docs/en/parse/string-format"
|
||||
"title1": "新建心智圖的檔名格式",
|
||||
"desc1": "新建心智圖檔名的命名格式。支持除notename外的其他變數",
|
||||
"title3": "粘貼圖片的命名格式",
|
||||
"desc3": "在節點上粘貼的圖片的命名格式",
|
||||
"filenameFormatDesc": "檔名格式說明"
|
||||
},
|
||||
"font": {
|
||||
"title": "字體",
|
||||
"desc": "擴展心智圖的字體清單",
|
||||
"btn": "管理",
|
||||
"placeholder": "蒐索字體。 也可按回車鍵直接添加輸入的字體",
|
||||
"empty": "暫無已添加字體",
|
||||
"noMatch": "無匹配字體",
|
||||
"noFont": "沒有可用字體"
|
||||
},
|
||||
"button": {
|
||||
"select": "選擇"
|
||||
|
|
@ -134,48 +166,21 @@
|
|||
"other": {
|
||||
"title1": "檢查新版本",
|
||||
"desc1": "開啟後會在挿件啟動時檢查是否有新版本可用,如果你的網絡訪問不到Github,則建議關閉該選項"
|
||||
},
|
||||
"imageHosting": {
|
||||
"title1": "開啟圖床",
|
||||
"desc1": "開啟後,所有圖片都將上傳到圖床",
|
||||
"tip1": "請先完善圖床配寘資訊",
|
||||
"title2": "圖床url",
|
||||
"desc2": "根據你使用的圖床工具填寫對應的上傳介面地址",
|
||||
"title3": "表單欄位",
|
||||
"desc3": "上傳圖片時的表單欄位名稱",
|
||||
"title4": "接口響應結構",
|
||||
"desc4": "上傳圖片時的接口響應結構。詳細說明請參考:",
|
||||
"desc5": "圖床功能說明"
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"save": "編輯操作-儲存",
|
||||
"saveAndUpdateImage": "編輯操作-儲存並更新圖像",
|
||||
"undo": "編輯操作-復原",
|
||||
"redo": "編輯操作-重做",
|
||||
"searchReplace": "編輯操作-搜尋和取代",
|
||||
"insertChildNode": "節點操作-插入下級節點",
|
||||
"insertSiblingNode": "節點操作-插入同級節點",
|
||||
"insertParentNode": "節點操作-插入父節點",
|
||||
"moveUpNode": "節點操作-上移節點",
|
||||
"moveDownNode": "節點操作-下移節點",
|
||||
"insertSummary": "節點操作-插入概要",
|
||||
"toggleExpandNode": "節點操作-展開/收起節點",
|
||||
"deleteNode": "節點操作-刪除節點",
|
||||
"deleteNodeOnly": "節點操作-僅刪除目前節點",
|
||||
"copyNode": "節點操作-複製節點",
|
||||
"cutNode": "節點操作-剪下節點",
|
||||
"pasteNode": "節點操作-貼上節點",
|
||||
"selectAllNode": "節點操作-全選",
|
||||
"organizeLayout": "節點操作-一鍵整理版面配置",
|
||||
"toggleBoldNode": "節點操作-切換粗體",
|
||||
"toggleItalicNode": "節點操作-切換斜體",
|
||||
"toggleUnderlineNode": "節點操作-切換底線",
|
||||
"toggleStrikethroughNode": "節點操作-切換刪除線",
|
||||
"increaseFontSizeNode": "節點操作-增大字型大小",
|
||||
"decreaseFontSizeNode": "節點操作-減小字型大小",
|
||||
"zoomInCanvas": "畫布操作-放大",
|
||||
"zoomOutCanvas": "畫布操作-縮小",
|
||||
"backToRootCanvas": "畫布操作-回到根節點",
|
||||
"activateLeftCanvas": "畫布操作-向左移動焦點",
|
||||
"activateTopCanvas": "畫布操作-向上移動焦點",
|
||||
"activateRightCanvas": "畫布操作-向右移動焦點",
|
||||
"activateBottomCanvas": "畫布操作-向下移動焦點",
|
||||
"zoomFitCanvas": "畫布操作-適應畫布",
|
||||
"enterDemonstrate": "輔助操作-進入演示模式",
|
||||
"openImageEdit": "介面操作-開啟圖片編輯彈窗",
|
||||
"openHyperlinkEdit": "介面操作-開啟超連結編輯彈窗",
|
||||
"openRemarkEdit": "介面操作-開啟備註編輯彈窗",
|
||||
"openTagEdit": "介面操作-開啟標籤編輯彈窗",
|
||||
"openImport": "介面操作-開啟匯入彈窗",
|
||||
"openExport": "介面操作-開啟匯出彈窗"
|
||||
"html": {
|
||||
"filenameFormatDesc": "<p>除了固定名稱外,也可以使用變數,用 {} 括起來,可用變數如下:</p><p>1.{notename}:目前檔案的檔名,不包含副檔名;</p><p>2.{date}:目前日期,格式為 YYYY-MM-DD;</p><p>3.{time}:目前時間,格式為 HH:mm:ss;</p><p>4.{datatime}:目前日期時間,格式為:YYYY-MM-DD HH:mm:ss;</p><p>5.{date:Format}:指定日期時間格式,例如:MM-DD HH:mm,可參考:<a href='https://day.js.org/docs/en/parse/string-format'>格式說明</a>;</p><p>6.{random}:隨機字串,長度為 6 位;</p>"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {
|
|||
createDefaultMindMapData
|
||||
} from './ob/metadataAndMarkdown.js'
|
||||
import { around, dedupe } from 'monkey-around'
|
||||
import { getUidFromSource } from './ob/utils.js'
|
||||
import { getUidFromSource, formatFileName } from './ob/utils.js'
|
||||
import MarkdownPostProcessor from './ob/MarkdownPostProcessor.js'
|
||||
import SmmSettingTab from './ob/SmmSettingTab.js'
|
||||
import { langList } from './src/config/zh'
|
||||
|
|
@ -37,12 +37,10 @@ import Menus from './ob/Menus.js'
|
|||
|
||||
export default class SimpleMindMapPlugin extends Plugin {
|
||||
async onload() {
|
||||
// 添加设置
|
||||
await this._addSetting()
|
||||
|
||||
// 初始化多语言
|
||||
i18n.init({
|
||||
lng: getLanguage() || 'en', // 自动检测用户语言
|
||||
lng: getLanguage() || 'en',
|
||||
fallbackLng: 'en',
|
||||
resources: {
|
||||
en: { translation: enTranslations },
|
||||
|
|
@ -54,66 +52,53 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
|
||||
addIcon('smm-icon', SIDE_BAR_ICON)
|
||||
|
||||
// 注册自定义视图
|
||||
this.registerView(SMM_VIEW_TYPE, leaf => new SmmEditView(leaf, this))
|
||||
|
||||
// 添加 Ribbon 图标
|
||||
this._createSmmFile = this._createSmmFile.bind(this)
|
||||
this.addRibbonIcon(
|
||||
'smm-icon',
|
||||
this._t('action.createMindMap'), // 新建思维导图
|
||||
this._t('action.createMindMap'),
|
||||
this._createSmmFile
|
||||
)
|
||||
|
||||
// 添加命令
|
||||
this.commands = new Commands(this)
|
||||
|
||||
// 添加右键菜单
|
||||
this.menus = new Menus(this)
|
||||
|
||||
// 打补丁,拦截默认的打开方法
|
||||
this._registerMonkeyPatches()
|
||||
|
||||
// 切换到思维导图视图
|
||||
this._switchToSmmAfterLoad()
|
||||
|
||||
// 创建状态栏子元素
|
||||
this._initStatusBar()
|
||||
|
||||
// 处理嵌入的思维导图
|
||||
this.markdownPostProcessor = new MarkdownPostProcessor(this)
|
||||
this.markdownPostProcessor.register()
|
||||
|
||||
// 记录打开文件的子路径,用于定位到指定节点
|
||||
this.fileToSubpathMap = {}
|
||||
this.noSaveOnClose = false
|
||||
}
|
||||
|
||||
// 创建思维导图文件
|
||||
async _createSmmFile(folderPath = '', callback) {
|
||||
try {
|
||||
if (typeof folderPath !== 'string') {
|
||||
if (typeof folderPath !== 'string' || folderPath === '/') {
|
||||
folderPath = ''
|
||||
}
|
||||
let { fileNamePrefix, fileNameDateFormat } = this.settings
|
||||
fileNamePrefix = fileNamePrefix || 'MindMap'
|
||||
const { fileNameFormat } = this.settings
|
||||
folderPath = this._getCreateFolderPath(folderPath)
|
||||
await this._ensureDirectoryExists(folderPath)
|
||||
const { vault } = this.app
|
||||
const filePath = `${
|
||||
folderPath ? folderPath + '/' : ''
|
||||
}${fileNamePrefix} ${moment().format(fileNameDateFormat)}.smm.md`
|
||||
}${formatFileName(fileNameFormat, { ignores: ['notename'] })}.smm.md`
|
||||
const fileContent = createDefaultText(
|
||||
filePath,
|
||||
this._getCreateDefaultMindMapOptions()
|
||||
)
|
||||
try {
|
||||
// 检查文件是否已存在
|
||||
const existingFile = vault.getFileByPath(filePath)
|
||||
if (existingFile) {
|
||||
// 文件已经存在
|
||||
new Notice(this._t('tip.fileExist'))
|
||||
} else {
|
||||
// 创建新文件
|
||||
await vault.create(filePath, fileContent)
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback(filePath)
|
||||
|
|
@ -122,18 +107,13 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 新建思维导图失败
|
||||
console.error(error)
|
||||
new Notice(this._t('tip.createMindMapFail'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
// 新建思维导图失败
|
||||
new Notice(this._t('tip.createMindMapFail'))
|
||||
}
|
||||
}
|
||||
|
||||
// 获取创建文件夹路径
|
||||
_getCreateFolderPath(folderPath) {
|
||||
if (folderPath) return folderPath
|
||||
const { filePathType, filePath } = this.settings
|
||||
|
|
@ -149,7 +129,6 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// 处理文件名重复问题
|
||||
async _getAvailableFilaName(filePath, extCount = 1) {
|
||||
let counter = 1
|
||||
while (await this.app.vault.adapter.exists(filePath)) {
|
||||
|
|
@ -165,13 +144,11 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
return filePath
|
||||
}
|
||||
|
||||
// 添加设置
|
||||
async _addSetting() {
|
||||
await this._loadSettings()
|
||||
this.addSettingTab(new SmmSettingTab(this.app, this))
|
||||
}
|
||||
|
||||
// 加载设置
|
||||
async _loadSettings() {
|
||||
const data = await this.loadData()
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, data || {})
|
||||
|
|
@ -182,14 +159,11 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
this.settings.lang = target ? target.value : 'en'
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
async _saveSettings() {
|
||||
await this.saveData(this.settings)
|
||||
}
|
||||
|
||||
// 打补丁,拦截默认的打开方法
|
||||
_registerMonkeyPatches() {
|
||||
// 防止其他插件或 Obsidian 核心代码通过 getActiveViewOfType 获取到 SmmEditView 时产生冲突(例如误判为 Markdown 视图)
|
||||
const key = 'https://github.com/wanglin2/mind-map'
|
||||
this.register(
|
||||
around(Workspace.prototype, {
|
||||
|
|
@ -206,14 +180,6 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
}
|
||||
})
|
||||
)
|
||||
// 背景:Obsidian 的嵌套工作区(如拆分窗口或某些插件创建的容器)可能导致 WorkspaceLeaf 的根节点获取异常。原始 getRoot() 可能返回非最顶层的容器
|
||||
/*
|
||||
修复逻辑:1.先调用原始 getRoot() 获取当前层级的根节点 top
|
||||
2.检查 top.getRoot 方法是否与当前实例的 getRoot 相同:
|
||||
2.1:如果相同,说明 top 已是最顶层根节点,直接返回
|
||||
2.2:如果不同,说明 top 仍是某个子容器,需递归调用 top.getRoot() 继续向上查找
|
||||
*/
|
||||
// 如果该插件已存在,则跳过补丁(因为 hover-editor 已实现相同功能),避免冲突
|
||||
if (!this.app.plugins?.plugins?.['obsidian-hover-editor']) {
|
||||
this.register(
|
||||
around(WorkspaceLeaf.prototype, {
|
||||
|
|
@ -226,7 +192,6 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
})
|
||||
)
|
||||
}
|
||||
// 通过修改 Obsidian 核心的 WorkspaceLeaf.setViewState 方法,强制将特定文件定向到自定义视图
|
||||
const self = this
|
||||
this.register(
|
||||
around(WorkspaceLeaf.prototype, {
|
||||
|
|
@ -237,10 +202,10 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
},
|
||||
setViewState(next) {
|
||||
return function(state, ...rest) {
|
||||
// 手动将smm视图切换为md视图不用拦截、md的预览和源码视图也不拦截
|
||||
if (
|
||||
state.state?.isSwitchToMarkdownViewFromSmmView ||
|
||||
['preview', 'source'].includes(state.state?.mode)
|
||||
(state.state?.isSwitchToMarkdownViewFromSmmView ||
|
||||
['preview', 'source'].includes(state.state?.mode)) &&
|
||||
!state.state?.isPreviewToMindMapViewFromMdView
|
||||
) {
|
||||
return next.apply(this, [state, ...rest])
|
||||
}
|
||||
|
|
@ -261,7 +226,6 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
) {
|
||||
const uid = getUidFromSource(rest[0])
|
||||
if (uid) {
|
||||
// 从当前已经打开的视图中查找是否已存在
|
||||
const existLeaf = this.app.workspace
|
||||
.getLeavesOfType(SMM_VIEW_TYPE)
|
||||
.find(leaf => {
|
||||
|
|
@ -300,11 +264,9 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
}
|
||||
})
|
||||
)
|
||||
// 在md视图下的smm文件右上角菜单增加切换为思维导图菜单
|
||||
this.menus.addMarkdownFileMenus()
|
||||
}
|
||||
|
||||
// 切换到思维导图视图
|
||||
_switchToSmmAfterLoad() {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
let leaf
|
||||
|
|
@ -321,7 +283,6 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
})
|
||||
}
|
||||
|
||||
// 设置思维导图视图状态
|
||||
async _setSmmView(leaf) {
|
||||
await leaf.setViewState({
|
||||
type: SMM_VIEW_TYPE,
|
||||
|
|
@ -330,18 +291,15 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
})
|
||||
}
|
||||
|
||||
// 获取当前激活的思维导图视图
|
||||
_getActiveSmmView() {
|
||||
return this.app.workspace.getActiveViewOfType(SmmEditView, IGNORE_CHECK_SMM)
|
||||
}
|
||||
|
||||
// 判断文件是否为思维导图
|
||||
_isSmmFile(file) {
|
||||
if (!file) {
|
||||
return false
|
||||
}
|
||||
const isString = typeof file === 'string'
|
||||
// 统一获取文件路径
|
||||
const filePath = isString ? file : file.path
|
||||
if (filePath.endsWith('.smm.md')) {
|
||||
return true
|
||||
|
|
@ -364,19 +322,15 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
return false
|
||||
}
|
||||
|
||||
// 创建状态栏子元素
|
||||
_initStatusBar() {
|
||||
// 创建状态栏主元素
|
||||
this.statusBarItem = this.addStatusBarItem()
|
||||
this.statusBarItem.empty()
|
||||
this.statusBarItem.style.display = 'none' // 默认隐藏
|
||||
this.statusBarItem.style.display = 'none'
|
||||
|
||||
// 创建专门用于显示 smm 文件状态的子元素
|
||||
this.smmFileStatus = this.statusBarItem.createEl('div', {
|
||||
cls: 'smm-file-status'
|
||||
})
|
||||
|
||||
// 监听文件打开和视图变化
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('file-open', this._updateStatusBar.bind(this))
|
||||
)
|
||||
|
|
@ -388,51 +342,38 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
)
|
||||
)
|
||||
|
||||
// 初始更新
|
||||
this._updateStatusBar()
|
||||
}
|
||||
|
||||
// 更新状态栏显示
|
||||
_updateStatusBar() {
|
||||
const activeFile = this.app.workspace.getActiveFile()
|
||||
|
||||
// 检查是否是 smm 文件
|
||||
const isSmmFile = activeFile && this._isSmmFile(activeFile)
|
||||
|
||||
// 获取状态栏容器
|
||||
const statusBar = this.app.statusBar.containerEl
|
||||
|
||||
// 更新状态栏可见性
|
||||
if (isSmmFile) {
|
||||
this.statusBarItem.style.display = 'block'
|
||||
// 添加隐藏默认状态栏的类
|
||||
statusBar.classList.add('smm-file-status-active')
|
||||
} else {
|
||||
this.statusBarItem.style.display = 'none'
|
||||
// 移除隐藏类
|
||||
statusBar.classList.remove('smm-file-status-active')
|
||||
}
|
||||
}
|
||||
|
||||
// 确保目录存在
|
||||
async _ensureDirectoryExists(dirPath) {
|
||||
if (!dirPath) return
|
||||
const { vault } = this.app
|
||||
// 检查目录是否存在
|
||||
// getFolderByPath无法检查到隐藏文件夹
|
||||
const exist = await vault.exists(dirPath)
|
||||
if (!exist) {
|
||||
// 递归创建目录
|
||||
await vault.createFolder(dirPath)
|
||||
}
|
||||
}
|
||||
|
||||
// 多语言
|
||||
_t(val) {
|
||||
return i18n.t(val)
|
||||
}
|
||||
|
||||
// 获取生成默认思维导图数据的配置
|
||||
_getCreateDefaultMindMapOptions() {
|
||||
const { defaultTheme, defaultThemeDark, defaultLayout } = this.settings
|
||||
return {
|
||||
|
|
@ -444,7 +385,6 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// 根据参数生成默认思维导图数据
|
||||
_createDefaultMindMapData() {
|
||||
return createDefaultMindMapData(
|
||||
this._getCreateDefaultMindMapOptions(),
|
||||
|
|
@ -452,40 +392,31 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
)
|
||||
}
|
||||
|
||||
// 获取激活文件的目录
|
||||
_getActiveFileDirectory() {
|
||||
// 获取当前激活文件
|
||||
const activeFile = this.app.workspace.getActiveFile()
|
||||
if (!activeFile) {
|
||||
return null
|
||||
}
|
||||
// 检查是否为文件(排除文件夹)
|
||||
if (activeFile instanceof TFolder) {
|
||||
return null
|
||||
}
|
||||
// 获取父目录路径
|
||||
const parent = activeFile.parent
|
||||
if (!parent) {
|
||||
return null
|
||||
}
|
||||
// 返回目录路径(Vault 相对路径)
|
||||
return parent.path.replace(/\/$/, '')
|
||||
}
|
||||
|
||||
// 获取ob是否是暗黑模式
|
||||
_getObIsDark() {
|
||||
return document.body.classList.contains('theme-dark')
|
||||
}
|
||||
|
||||
// md 转思维导图数据
|
||||
async _mdToMindmapData(md) {
|
||||
// 过滤掉开头的 YAML front matter
|
||||
const frontMatterRegex = /^---\n[\s\S]*?\n---\n/
|
||||
const cleanedMd = md.replace(frontMatterRegex, '')
|
||||
if (!cleanedMd.trim()) {
|
||||
return null
|
||||
}
|
||||
// 解析 Markdown 数据
|
||||
const data = await markdown.transformMarkdownTo(cleanedMd, false)
|
||||
if (!data) {
|
||||
return null
|
||||
|
|
@ -501,11 +432,9 @@ export default class SimpleMindMapPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// 卸载时清理
|
||||
onunload() {
|
||||
this.app.workspace.detachLeavesOfType(SMM_VIEW_TYPE)
|
||||
this.markdownPostProcessor.destroy()
|
||||
// 清理状态栏
|
||||
this.statusBarItem?.remove()
|
||||
this.commands.clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ export default class Commands {
|
|||
}
|
||||
|
||||
_addCreatesCommand() {
|
||||
// 新建思维导图
|
||||
this.addCommand({
|
||||
id: 'create-smm-mindmap',
|
||||
name: this._t('action.createMindMap'),
|
||||
|
|
@ -28,7 +27,6 @@ export default class Commands {
|
|||
this.plugin._createSmmFile()
|
||||
}
|
||||
})
|
||||
// 新建思维导图并插入当前文档
|
||||
this.addCommand({
|
||||
id: 'create-smm-mindmap-insert-markdown',
|
||||
name: this._t('action.createMindMapInsertToMd'),
|
||||
|
|
@ -61,437 +59,39 @@ export default class Commands {
|
|||
})
|
||||
}
|
||||
|
||||
_commonCheckCallback(checking, run) {
|
||||
const view = this.plugin._getActiveSmmView()
|
||||
if (view) {
|
||||
if (!checking) {
|
||||
run(view)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
_addActionsCommand() {
|
||||
this._addMindMapEditActionCommands()
|
||||
this._addMinMapNodeActionCommands()
|
||||
this._addCanvasActionCommands()
|
||||
this._addHelpActionCommands()
|
||||
this._addUiActionCommands()
|
||||
}
|
||||
|
||||
_addMindMapEditActionCommands() {
|
||||
// 编辑操作-保存
|
||||
this.addCommand({
|
||||
id: 'save-smm-mindmap',
|
||||
name: this._t('commands.save'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.forceSave()
|
||||
})
|
||||
}
|
||||
})
|
||||
// 编辑操作-保存并更新图像
|
||||
this.addCommand({
|
||||
id: 'save-smm-mindmap-and-update-image',
|
||||
name: this._t('commands.saveAndUpdateImage'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.forceSaveAndUpdateImage()
|
||||
})
|
||||
}
|
||||
})
|
||||
// 编辑操作-回退
|
||||
this.addCommand({
|
||||
id: 'undo-smm-mindmap-node',
|
||||
name: this._t('commands.undo'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'BACK')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 编辑操作-前进
|
||||
this.addCommand({
|
||||
id: 'redo-smm-mindmap-node',
|
||||
name: this._t('commands.redo'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'FORWARD')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 编辑操作-搜索和替换
|
||||
this.addCommand({
|
||||
id: 'show-smm-mindmap-search',
|
||||
name: this._t('commands.searchReplace'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('show_search')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_addMinMapNodeActionCommands() {
|
||||
// 节点操作-插入下级节点
|
||||
this.addCommand({
|
||||
id: 'insert-smm-mindmap-child-node',
|
||||
name: this._t('commands.insertChildNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'INSERT_CHILD_NODE')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-插入同级节点
|
||||
this.addCommand({
|
||||
id: 'insert-smm-mindmap-sibling-node',
|
||||
name: this._t('commands.insertSiblingNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'INSERT_NODE')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-插入父节点
|
||||
this.addCommand({
|
||||
id: 'insert-smm-mindmap-parent-node',
|
||||
name: this._t('commands.insertParentNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'INSERT_PARENT_NODE')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-上移节点
|
||||
this.addCommand({
|
||||
id: 'move-smm-mindmap-node-up',
|
||||
name: this._t('commands.moveUpNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'UP_NODE')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-下移节点
|
||||
this.addCommand({
|
||||
id: 'move-smm-mindmap-node-down',
|
||||
name: this._t('commands.moveDownNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'DOWN_NODE')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-插入概要
|
||||
this.addCommand({
|
||||
id: 'insert-smm-mindmap-summary',
|
||||
name: this._t('commands.insertSummary'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'ADD_GENERALIZATION')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-展开/收起节点
|
||||
this.addCommand({
|
||||
id: 'toggle-expand-smm-mindmap-node',
|
||||
name: this._t('commands.toggleExpandNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('toggleActiveExpand')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-删除节点
|
||||
this.addCommand({
|
||||
id: 'delete-smm-mindmap-node',
|
||||
name: this._t('commands.deleteNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'REMOVE_NODE')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-仅删除当前节点
|
||||
this.addCommand({
|
||||
id: 'delete-smm-mindmap-node-only',
|
||||
name: this._t('commands.deleteNodeOnly'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'REMOVE_CURRENT_NODE')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-复制节点
|
||||
this.addCommand({
|
||||
id: 'copy-smm-mindmap-node',
|
||||
name: this._t('commands.copyNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('copyNode')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-剪切节点
|
||||
this.addCommand({
|
||||
id: 'cut-smm-mindmap-node',
|
||||
name: this._t('commands.cutNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('cutNode')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-粘贴节点
|
||||
this.addCommand({
|
||||
id: 'paste-smm-mindmap-node',
|
||||
name: this._t('commands.pasteNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('pasteNode')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-全选
|
||||
this.addCommand({
|
||||
id: 'select-all-smm-mindmap-node',
|
||||
name: this._t('commands.selectAllNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'SELECT_ALL')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-一键整理布局
|
||||
this.addCommand({
|
||||
id: 'organize-smm-mindmap-layout',
|
||||
name: this._t('commands.organizeLayout'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('execCommand', 'RESET_LAYOUT')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 同级节点对齐,todo
|
||||
// 节点操作-切换粗体
|
||||
this.addCommand({
|
||||
id: 'toggle-bold-smm-mindmap-node',
|
||||
name: this._t('commands.toggleBoldNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('toggleBold')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-切换斜体
|
||||
this.addCommand({
|
||||
id: 'toggle-italic-smm-mindmap-node',
|
||||
name: this._t('commands.toggleItalicNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('toggleItalic')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-切换下划线
|
||||
this.addCommand({
|
||||
id: 'toggle-underline-smm-mindmap-node',
|
||||
name: this._t('commands.toggleUnderlineNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('toggleUnderline')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-切换删除线
|
||||
this.addCommand({
|
||||
id: 'toggle-strikethrough-smm-mindmap-node',
|
||||
name: this._t('commands.toggleStrikethroughNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('toggleStrikethrough')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-增大字号
|
||||
this.addCommand({
|
||||
id: 'increase-font-size-smm-mindmap-node',
|
||||
name: this._t('commands.increaseFontSizeNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('increaseFontSize')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 节点操作-减小字号
|
||||
this.addCommand({
|
||||
id: 'decrease-font-size-smm-mindmap-node',
|
||||
name: this._t('commands.decreaseFontSizeNode'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('decreaseFontSize')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_addCanvasActionCommands() {
|
||||
// 画布操作-放大
|
||||
this.addCommand({
|
||||
id: 'zoom-in-smm-mindmap',
|
||||
name: this._t('commands.zoomInCanvas'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('canvasZoomIn')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 画布操作-缩小
|
||||
this.addCommand({
|
||||
id: 'zoom-out-smm-mindmap',
|
||||
name: this._t('commands.zoomOutCanvas'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('canvasZoomOut')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 画布操作-回到根节点
|
||||
this.addCommand({
|
||||
id: 'back-to-root-smm-mindmap',
|
||||
name: this._t('commands.backToRootCanvas'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('canvasBackToRoot')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 画布操作-向左移动焦点
|
||||
this.addCommand({
|
||||
id: 'activate-left-smm-mindmap',
|
||||
name: this._t('commands.activateLeftCanvas'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('canvasActivateLeftNode')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 画布操作-向上移动焦点
|
||||
this.addCommand({
|
||||
id: 'activate-top-smm-mindmap',
|
||||
name: this._t('commands.activateTopCanvas'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('canvasActivateTopNode')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 画布操作-向右移动焦点
|
||||
this.addCommand({
|
||||
id: 'activate-right-smm-mindmap',
|
||||
name: this._t('commands.activateRightCanvas'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('canvasActivateRightNode')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 画布操作-向下移动焦点
|
||||
this.addCommand({
|
||||
id: 'activate-bottom-smm-mindmap',
|
||||
name: this._t('commands.activateBottomCanvas'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('canvasActivateBottomNode')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 画布操作-适应画布
|
||||
this.addCommand({
|
||||
id: 'zoom-fit-smm-mindmap-node',
|
||||
name: this._t('commands.zoomFitCanvas'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('canvasZoomFit')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_addHelpActionCommands() {
|
||||
// 进入演示模式
|
||||
this.addCommand({
|
||||
id: 'enter-smm-mindmap-demonstrate',
|
||||
name: this._t('commands.enterDemonstrate'),
|
||||
name: this._t('action.enterDemonstrate'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('enter_demonstrate')
|
||||
})
|
||||
const view = this.plugin._getActiveSmmView()
|
||||
if (view) {
|
||||
if (!checking) {
|
||||
view.mindMapAPP.$bus.$emit('enter_demonstrate')
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_addUiActionCommands() {
|
||||
// 打开图片编辑弹窗
|
||||
this.addCommand({
|
||||
id: 'open-smm-mindmap-image-edit',
|
||||
name: this._t('commands.openImageEdit'),
|
||||
id: 'save-smm-mindmap-and-update-image',
|
||||
name: this._t('action.saveAndUpdateImage'),
|
||||
hotkeys: [
|
||||
{
|
||||
modifiers: ['Mod', 'Shift'],
|
||||
key: 's'
|
||||
}
|
||||
],
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('showNodeImage')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 打开超链接编辑弹窗
|
||||
this.addCommand({
|
||||
id: 'open-smm-mindmap-hyperlink-edit',
|
||||
name: this._t('commands.openHyperlinkEdit'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('showNodeLink')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 打开备注编辑弹窗
|
||||
this.addCommand({
|
||||
id: 'open-smm-mindmap-remark-edit',
|
||||
name: this._t('commands.openRemarkEdit'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('showNodeNote')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 打开标签编辑弹窗
|
||||
this.addCommand({
|
||||
id: 'open-smm-mindmap-tag-edit',
|
||||
name: this._t('commands.openTagEdit'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('showNodeTag')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 打开导入弹窗
|
||||
this.addCommand({
|
||||
id: 'open-smm-mindmap-import',
|
||||
name: this._t('commands.openImport'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('showImport')
|
||||
})
|
||||
}
|
||||
})
|
||||
// 打开导出弹窗
|
||||
this.addCommand({
|
||||
id: 'open-smm-mindmap-export',
|
||||
name: this._t('commands.openExport'),
|
||||
checkCallback: checking => {
|
||||
return this._commonCheckCallback(checking, view => {
|
||||
view.mindMapAPP.$bus.$emit('showExport')
|
||||
})
|
||||
const view = this.plugin._getActiveSmmView()
|
||||
if (view) {
|
||||
if (!checking) {
|
||||
view.forceSaveAndUpdateImage()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
64
plugin/ob/ConfirmDialog.js
Normal file
64
plugin/ob/ConfirmDialog.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { Modal } from 'obsidian'
|
||||
import i18n from 'i18next'
|
||||
|
||||
export class ConfirmDialog extends Modal {
|
||||
constructor(app, params) {
|
||||
super(app)
|
||||
this.params = {
|
||||
title: i18n.t('tip.tip'),
|
||||
confirmText: i18n.t('tip.confirm'),
|
||||
cancelText: i18n.t('tip.cancel'),
|
||||
content: '',
|
||||
danger: false,
|
||||
...params
|
||||
}
|
||||
this.resolvePromise = null
|
||||
this.promise = new Promise(resolve => {
|
||||
this.resolvePromise = resolve
|
||||
})
|
||||
this.setTitle(this.params.title)
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this
|
||||
contentEl.empty()
|
||||
const contentContainer = contentEl.createDiv('smm-confirm-dialog-content')
|
||||
if (typeof this.params.content === 'string') {
|
||||
contentContainer.createEl('p', { text: this.params.content })
|
||||
} else {
|
||||
contentContainer.appendChild(this.params.content)
|
||||
}
|
||||
const buttonContainer = contentEl.createDiv('smm-confirm-dialog-buttons')
|
||||
const cancelBtn = buttonContainer.createEl('button', {
|
||||
text: this.params.cancelText
|
||||
})
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
this.params.onCancel?.()
|
||||
this.resolvePromise(false)
|
||||
this.close()
|
||||
})
|
||||
const confirmBtn = buttonContainer.createEl('button', {
|
||||
text: this.params.confirmText,
|
||||
cls: this.params.danger ? 'mod-warning' : 'mod-cta'
|
||||
})
|
||||
confirmBtn.addEventListener('click', () => {
|
||||
this.params.onConfirm?.()
|
||||
this.resolvePromise(true)
|
||||
this.close()
|
||||
})
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this
|
||||
contentEl.empty()
|
||||
if (this.resolvePromise) {
|
||||
this.resolvePromise(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function showConfirmationDialog(app, params) {
|
||||
const dialog = new ConfirmDialog(app, params)
|
||||
dialog.open()
|
||||
return dialog.promise
|
||||
}
|
||||
|
|
@ -1,31 +1,32 @@
|
|||
import { TFile } from 'obsidian'
|
||||
import { parseMarkdownText } from './metadataAndMarkdown.js'
|
||||
import { dataURItoBlob } from './utils.js'
|
||||
import { dataURItoBlob, smmFilePathToFileName } from './utils.js'
|
||||
import LZString from 'lz-string'
|
||||
import { SMM_VIEW_TYPE } from './constant.js'
|
||||
import SmmEditView from '../SmmEditView.js'
|
||||
|
||||
export default class MarkdownPostProcessor {
|
||||
constructor(plugin) {
|
||||
this.plugin = plugin
|
||||
|
||||
this.urlCache = new Map() // 文件路径 -> 图片URL的缓存
|
||||
this.imageElements = new Map() // 文件路径 -> 图片元素集合
|
||||
this.emptyMap = new Map() // 文件路径 -> 当前没有预览图像的集合
|
||||
this.urlCache = new Map()
|
||||
this.imageElements = new Map()
|
||||
this.emptyMap = new Map()
|
||||
|
||||
// 绑定事件处理器
|
||||
this.handleFileModify = this.handleFileModify.bind(this)
|
||||
this.handleFileDelete = this.handleFileDelete.bind(this)
|
||||
this.handleModifySvgName = this.handleModifySvgName.bind(this)
|
||||
|
||||
// 注册文件监听
|
||||
plugin.app.vault.on('modify', this.handleFileModify)
|
||||
plugin.app.vault.on('delete', this.handleFileDelete)
|
||||
plugin.app.vault.on('rename', this.handleModifySvgName)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// 清理事件监听
|
||||
this.plugin.app.vault.off('modify', this.handleFileModify)
|
||||
this.plugin.app.vault.off('delete', this.handleFileDelete)
|
||||
this.plugin.app.vault.off('rename', this.handleModifySvgName)
|
||||
|
||||
// 释放所有URL对象
|
||||
this.urlCache.forEach(url => URL.revokeObjectURL(url))
|
||||
this.urlCache.clear()
|
||||
this.imageElements.clear()
|
||||
|
|
@ -42,15 +43,13 @@ export default class MarkdownPostProcessor {
|
|||
return
|
||||
}
|
||||
const curIsNoImage = noUrlCache && hasEmpty
|
||||
// 重新生成图片URL
|
||||
const data = await this.plugin.app.vault.read(file)
|
||||
const parsedata = parseMarkdownText(data)
|
||||
const svgBlob = dataURItoBlob(
|
||||
LZString.decompressFromBase64(parsedata.svgdata)
|
||||
)
|
||||
const newUrl = URL.createObjectURL(svgBlob)
|
||||
const newUrl = this._getImgUrl(parsedata.svgdata)
|
||||
if (!newUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
const oldUrl = this.urlCache.get(file.path)
|
||||
if (oldUrl) URL.revokeObjectURL(oldUrl)
|
||||
this.urlCache.set(file.path, newUrl)
|
||||
|
|
@ -60,7 +59,6 @@ export default class MarkdownPostProcessor {
|
|||
this.emptyMap.delete(file.path)
|
||||
}
|
||||
|
||||
// 更新所有关联的图片元素
|
||||
const elements = this.imageElements.get(file.path) || new Set()
|
||||
elements.forEach(img => {
|
||||
img.src = newUrl
|
||||
|
|
@ -74,10 +72,9 @@ export default class MarkdownPostProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
handleFileDelete(file) {
|
||||
async handleFileDelete(file) {
|
||||
if (!(file instanceof TFile)) return
|
||||
|
||||
// 清理被删除文件的资源
|
||||
const url = this.urlCache.get(file.path)
|
||||
if (url) {
|
||||
URL.revokeObjectURL(url)
|
||||
|
|
@ -95,13 +92,63 @@ export default class MarkdownPostProcessor {
|
|||
})
|
||||
this.imageElements.delete(file.path)
|
||||
}
|
||||
// 没有预览图像时修改提示信息
|
||||
const emptyDiv = this.emptyMap.get(file.path)
|
||||
if (emptyDiv) {
|
||||
emptyDiv.textContent = this.plugin._t('tip.fileDeleted')
|
||||
emptyDiv.removeEventListener('dblclick', emptyDiv.dblclickHandler)
|
||||
this.emptyMap.delete(file.path)
|
||||
}
|
||||
|
||||
this.deletePreviewSvgFile(file)
|
||||
}
|
||||
|
||||
async deletePreviewSvgFile(file) {
|
||||
try {
|
||||
const targetFileName = smmFilePathToFileName(file.path, '.svg')
|
||||
const { embedImageIsSeparateFileFolder } = this.plugin.settings
|
||||
const previewFilePath = embedImageIsSeparateFileFolder
|
||||
? `${embedImageIsSeparateFileFolder}/${targetFileName}`
|
||||
: targetFileName
|
||||
const exist = await this.plugin.app.vault.exists(previewFilePath)
|
||||
console.log('删除预览图像文件:', previewFilePath, exist)
|
||||
if (exist) {
|
||||
await this.plugin.app.vault.adapter.remove(previewFilePath)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除预览图像文件失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async handleModifySvgName(file, oldName) {
|
||||
try {
|
||||
oldName = smmFilePathToFileName(oldName, '.svg')
|
||||
const { embedImageIsSeparateFileFolder } = this.plugin.settings
|
||||
const oldPath = embedImageIsSeparateFileFolder + '/' + oldName
|
||||
const exist = await this.plugin.app.vault.exists(oldPath)
|
||||
if (exist) {
|
||||
const newName = smmFilePathToFileName(file.name, '.svg')
|
||||
const newPath = embedImageIsSeparateFileFolder + '/' + newName
|
||||
await this.plugin.app.vault.adapter.rename(oldPath, newPath)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('修改svg文件名失败:', error)
|
||||
}
|
||||
try {
|
||||
const markdownLeafs = this.plugin.app.workspace.getLeavesOfType(
|
||||
SMM_VIEW_TYPE
|
||||
)
|
||||
for (const leaf of markdownLeafs) {
|
||||
if (
|
||||
leaf.view instanceof SmmEditView &&
|
||||
leaf.view.file.path === file.path
|
||||
) {
|
||||
leaf.view.forceSave(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
register() {
|
||||
|
|
@ -112,12 +159,11 @@ export default class MarkdownPostProcessor {
|
|||
|
||||
async _markdownPostProcessor(el, ctx) {
|
||||
try {
|
||||
// 特殊卡片处理
|
||||
if (this._isSpecialCard(el)) {
|
||||
console.log('特殊卡片处理')
|
||||
return this._processSpecialCard(el)
|
||||
}
|
||||
|
||||
// 模式检测与处理
|
||||
const embeddedItems = el.querySelectorAll('.internal-embed')
|
||||
if (embeddedItems.length === 0) {
|
||||
await this._processEditMode(el, ctx)
|
||||
|
|
@ -141,7 +187,7 @@ export default class MarkdownPostProcessor {
|
|||
_processSpecialCard(el) {
|
||||
el.classList.add('smmMindCard')
|
||||
const svgdata = el.children[3].children[0].innerHTML
|
||||
const url = URL.createObjectURL(dataURItoBlob(svgdata))
|
||||
const url = this._getImgUrl(svgdata)
|
||||
|
||||
const img = this._createSvgImageElement(url)
|
||||
el.replaceChildren(img)
|
||||
|
|
@ -178,8 +224,8 @@ export default class MarkdownPostProcessor {
|
|||
const img = await this._createImageFromFile(file, data)
|
||||
containerEl.empty()
|
||||
if (img) {
|
||||
this._updateImgSize(containerEl, img)
|
||||
containerEl.appendChild(img)
|
||||
this._updateImgSize(containerEl, img)
|
||||
} else {
|
||||
const emptyDiv = this._createEmptyDiv(
|
||||
containerEl,
|
||||
|
|
@ -223,7 +269,6 @@ export default class MarkdownPostProcessor {
|
|||
}
|
||||
|
||||
_updateImgSize(containerEl, img) {
|
||||
// 悬浮预览弹窗
|
||||
if (
|
||||
containerEl.parentNode &&
|
||||
containerEl.parentNode.classList.contains('popover')
|
||||
|
|
@ -242,49 +287,55 @@ export default class MarkdownPostProcessor {
|
|||
}
|
||||
|
||||
async _createImageFromFile(file, data) {
|
||||
// 优先使用缓存
|
||||
if (this.urlCache.has(file.path)) {
|
||||
const url = this.urlCache.get(file.path)
|
||||
return this._createSvgImageElement(url, file.path)
|
||||
}
|
||||
|
||||
// 生成新URL并缓存
|
||||
const parsedata = parseMarkdownText(data)
|
||||
if (!parsedata.svgdata) {
|
||||
const svgdata = parsedata.svgdata
|
||||
if (!svgdata) {
|
||||
return null
|
||||
}
|
||||
const svgBlob = dataURItoBlob(
|
||||
LZString.decompressFromBase64(parsedata.svgdata)
|
||||
)
|
||||
const url = URL.createObjectURL(svgBlob)
|
||||
const url = this._getImgUrl(svgdata)
|
||||
this.urlCache.set(file.path, url)
|
||||
|
||||
const img = this._createSvgImageElement(url, file.path)
|
||||
return img
|
||||
}
|
||||
|
||||
_getImgUrl(svgdata) {
|
||||
if (!svgdata) return ''
|
||||
let url = ''
|
||||
if (/^!\[\[/.test(svgdata)) {
|
||||
const match = svgdata.match(/!\[\[([^\]]+)\]\]/)
|
||||
if (match && match[1]) {
|
||||
url = this.plugin.app.vault.adapter.getResourcePath(match[1])
|
||||
}
|
||||
} else {
|
||||
const svgBlob = dataURItoBlob(LZString.decompressFromBase64(svgdata))
|
||||
url = URL.createObjectURL(svgBlob)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
_createSvgImageElement(url, filePath = '') {
|
||||
const img = document.createElement('img')
|
||||
img.src = url
|
||||
img.draggable = false
|
||||
|
||||
if (filePath) {
|
||||
// 添加文件路径标识
|
||||
img.setAttribute('data-smm-file', filePath)
|
||||
|
||||
// 注册双击打开源文件
|
||||
img.addEventListener('dblclick', () => {
|
||||
const newWindow = this.plugin.settings.embedDblClickNewWindow
|
||||
this.plugin.app.workspace.openLinkText(filePath, '', newWindow)
|
||||
})
|
||||
|
||||
// 添加到元素追踪
|
||||
if (!this.imageElements.has(filePath)) {
|
||||
this.imageElements.set(filePath, new Set())
|
||||
}
|
||||
this.imageElements.get(filePath).add(img)
|
||||
|
||||
// 元素卸载时清理
|
||||
const observer = new MutationObserver(() => {
|
||||
if (!document.contains(img)) {
|
||||
this.imageElements.get(filePath)?.delete(img)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import LZString from 'lz-string'
|
|||
import { SMM_VIEW_TYPE, SMM_TAG } from './constant.js'
|
||||
import { around } from 'monkey-around'
|
||||
import markdown from 'simple-mind-map/src/parse/markdown.js'
|
||||
import { showConfirmationDialog } from './ConfirmDialog.js'
|
||||
import SmmEditView from '../SmmEditView.js'
|
||||
|
||||
export default class Menus {
|
||||
|
|
@ -23,12 +24,10 @@ export default class Menus {
|
|||
return this.plugin._t(key)
|
||||
}
|
||||
|
||||
// 添加右键菜单
|
||||
_addFileMenus() {
|
||||
this.plugin.registerEvent(
|
||||
this.app.workspace.on('file-menu', (menu, file) => {
|
||||
if (file instanceof TFolder) {
|
||||
// 新建思维导图
|
||||
menu.addItem(item => {
|
||||
item
|
||||
.setTitle(this._t('action.createMindMap'))
|
||||
|
|
@ -41,7 +40,6 @@ export default class Menus {
|
|||
if (this.plugin._isSmmFile(file)) {
|
||||
hideTargetMenu(menu, '在新窗口中打开')
|
||||
hideTargetMenu(menu, '移动至新窗口')
|
||||
// 打开为Markdown文档
|
||||
menu.addItem(item =>
|
||||
item
|
||||
.setTitle(this._t('action.openAsMd'))
|
||||
|
|
@ -50,8 +48,9 @@ export default class Menus {
|
|||
.onClick(async () => {
|
||||
await this.app.workspace.openLinkText(file.path, '')
|
||||
const leaf = this.app.workspace.getLeaf(false)
|
||||
const mdViewType =
|
||||
this.app.viewRegistry.getTypeByExtension('md')
|
||||
const mdViewType = this.app.viewRegistry.getTypeByExtension(
|
||||
'md'
|
||||
)
|
||||
await leaf.setViewState({
|
||||
type: mdViewType,
|
||||
state: {
|
||||
|
|
@ -63,13 +62,18 @@ export default class Menus {
|
|||
this.app.workspace.revealLeaf(leaf)
|
||||
})
|
||||
)
|
||||
// 转换为md文档
|
||||
menu.addItem(item =>
|
||||
item
|
||||
.setTitle(this._t('action.changeToMdFile'))
|
||||
.setIcon('refresh-cw')
|
||||
.setSection('open')
|
||||
.onClick(async () => {
|
||||
const confirmed = await showConfirmationDialog(this.app, {
|
||||
content: this._t('tip.mindMapToMdTip')
|
||||
})
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
const content = await this.app.vault.read(file)
|
||||
const result = parseMarkdownText(content)
|
||||
const mindMapData = LZString.decompressFromBase64(
|
||||
|
|
@ -91,18 +95,24 @@ export default class Menus {
|
|||
if (mdStr) {
|
||||
mdStr = '---\n' + mdStr + '---\n'
|
||||
}
|
||||
const {
|
||||
nodeTextToMarkdownTitleMaxLevel
|
||||
} = this.plugin.settings
|
||||
const mdMindMap = markdown.transformToMarkdown(
|
||||
JSON.parse(mindMapData).root
|
||||
JSON.parse(mindMapData).root,
|
||||
nodeTextToMarkdownTitleMaxLevel
|
||||
)
|
||||
mdStr += mdMindMap
|
||||
let newPath = file.path.replace('.smm.md', '.md')
|
||||
let newPath = file.path.replace(/\.smm.*\.md$/g, '.md')
|
||||
newPath = await this.plugin._getAvailableFilaName(newPath)
|
||||
await this.app.vault.rename(file, newPath)
|
||||
const renamedFile = this.app.vault.getFileByPath(newPath)
|
||||
if (renamedFile) {
|
||||
// 关闭当前标签页
|
||||
const markdownLeafs =
|
||||
this.app.workspace.getLeavesOfType(SMM_VIEW_TYPE)
|
||||
this.plugin.markdownPostProcessor.deletePreviewSvgFile(file)
|
||||
this.app.noSaveOnClose = true
|
||||
const markdownLeafs = this.app.workspace.getLeavesOfType(
|
||||
SMM_VIEW_TYPE
|
||||
)
|
||||
for (const leaf of markdownLeafs) {
|
||||
if (
|
||||
leaf.view instanceof SmmEditView &&
|
||||
|
|
@ -112,9 +122,8 @@ export default class Menus {
|
|||
break
|
||||
}
|
||||
}
|
||||
// 修改文件内容
|
||||
await this.app.vault.modify(renamedFile, mdStr)
|
||||
// 重新打开
|
||||
this.app.noSaveOnClose = false
|
||||
const ref = this.app.metadataCache.on(
|
||||
'changed',
|
||||
changedFile => {
|
||||
|
|
@ -140,15 +149,13 @@ export default class Menus {
|
|||
|
||||
addMarkdownFileMenus() {
|
||||
const self = this
|
||||
// 在md视图下的smm文件右上角菜单增加切换为思维导图菜单
|
||||
this.plugin.register(
|
||||
around(MarkdownView.prototype, {
|
||||
onPaneMenu(next) {
|
||||
return function (menu, source) {
|
||||
return function(menu, source) {
|
||||
const res = next.apply(this, [menu, source])
|
||||
if (self.plugin._isSmmFile(this.file)) {
|
||||
if (source === 'more-options') {
|
||||
// 打开为思维导图文档
|
||||
menu.addItem(item =>
|
||||
item
|
||||
.setTitle(self._t('action.openAsMindMap'))
|
||||
|
|
@ -161,7 +168,6 @@ export default class Menus {
|
|||
}
|
||||
} else {
|
||||
if (source === 'more-options') {
|
||||
// 预览为思维导图
|
||||
menu.addItem(item =>
|
||||
item
|
||||
.setTitle(self._t('action.previewAsMindMap'))
|
||||
|
|
@ -177,13 +183,18 @@ export default class Menus {
|
|||
})
|
||||
})
|
||||
)
|
||||
// 转换为思维导图文档
|
||||
menu.addItem(item =>
|
||||
item
|
||||
.setTitle(self._t('action.changeToMindMapFile'))
|
||||
.setIcon('refresh-cw')
|
||||
.setSection('pane')
|
||||
.onClick(async () => {
|
||||
const confirmed = await showConfirmationDialog(this.app, {
|
||||
content: self._t('tip.mdToMindMapTip')
|
||||
})
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
let yaml = ''
|
||||
const tagList = []
|
||||
await this.app.fileManager.processFrontMatter(
|
||||
|
|
@ -229,9 +240,9 @@ export default class Menus {
|
|||
linkdata: []
|
||||
})
|
||||
if (renamedFile) {
|
||||
// 关闭当前标签页
|
||||
const markdownLeafs =
|
||||
this.app.workspace.getLeavesOfType('markdown')
|
||||
const markdownLeafs = this.app.workspace.getLeavesOfType(
|
||||
'markdown'
|
||||
)
|
||||
for (const leaf of markdownLeafs) {
|
||||
if (
|
||||
leaf.view instanceof MarkdownView &&
|
||||
|
|
@ -241,9 +252,7 @@ export default class Menus {
|
|||
break
|
||||
}
|
||||
}
|
||||
// 修改文件内容
|
||||
await this.app.vault.modify(renamedFile, str)
|
||||
// 重新打开
|
||||
const ref = this.app.metadataCache.on(
|
||||
'changed',
|
||||
changedFile => {
|
||||
|
|
|
|||
104
plugin/ob/ModifyFileName.js
Normal file
104
plugin/ob/ModifyFileName.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
export default class ModifyFileName {
|
||||
constructor(view) {
|
||||
this.view = view
|
||||
this.plugin = view.plugin
|
||||
this.app = view.app
|
||||
this.titleEl = null
|
||||
}
|
||||
|
||||
setupTitleEditing() {
|
||||
this.titleEl = this.getTitleElement()
|
||||
if (!this.titleEl) return
|
||||
this.titleEl.setAttribute('contenteditable', 'true')
|
||||
this.onBlur = this.onBlur.bind(this)
|
||||
this.titleEl.addEventListener('blur', this.onBlur)
|
||||
this.onKeydown = this.onKeydown.bind(this)
|
||||
this.titleEl.addEventListener('keydown', this.onKeydown)
|
||||
}
|
||||
|
||||
getTitleElement() {
|
||||
return this.view.containerEl.querySelector('.view-header-title')
|
||||
}
|
||||
|
||||
async finishEditing(accept) {
|
||||
if (!this.titleEl.isConnected) return
|
||||
const file = this.view.file
|
||||
if (!file) return
|
||||
const originalName = file.basename
|
||||
if (accept) {
|
||||
const newName = this.titleEl.textContent.trim()
|
||||
if (!newName) {
|
||||
new Notice(this.plugin._t('tip.fileNameEmpty'))
|
||||
return
|
||||
}
|
||||
this.titleEl.innerHTML = newName
|
||||
if (newName === originalName) return
|
||||
try {
|
||||
const parentPath = file.parent.path
|
||||
const extension = file.extension
|
||||
const newPath = `${parentPath}${
|
||||
parentPath !== '/' ? '/' : ''
|
||||
}${newName}${extension ? '.' + extension : ''}`
|
||||
if (!/^[^<>:"/\\|?*]+$/.test(newName)) {
|
||||
new Notice(this.plugin._t('tip.fileNameIllegal'))
|
||||
return
|
||||
}
|
||||
const existingFile =
|
||||
this.app.vault.getFileByPath(newPath) ||
|
||||
this.app.vault.getFileByPath(newPath.replace(/^\//, ''))
|
||||
if (existingFile) {
|
||||
new Notice(this.plugin._t('tip.fileExist'))
|
||||
return
|
||||
}
|
||||
await this.app.fileManager.renameFile(file, newPath)
|
||||
} catch (error) {
|
||||
console.error('重命名文件失败:', error)
|
||||
new Notice(this.plugin._t('tip.fileRenameFail'))
|
||||
}
|
||||
} else {
|
||||
this.titleEl.innerHTML = originalName
|
||||
const selection = window.getSelection()
|
||||
const range = document.createRange()
|
||||
let lastTextNode = null
|
||||
const walker = document.createTreeWalker(
|
||||
this.titleEl,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
null,
|
||||
false
|
||||
)
|
||||
let node
|
||||
while ((node = walker.nextNode())) {
|
||||
lastTextNode = node
|
||||
}
|
||||
range.setStart(
|
||||
lastTextNode,
|
||||
/\.smm$/.test(lastTextNode.textContent)
|
||||
? lastTextNode.length - 4
|
||||
: lastTextNode.length
|
||||
)
|
||||
range.collapse(true)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
}
|
||||
|
||||
onBlur() {
|
||||
this.finishEditing(true)
|
||||
}
|
||||
|
||||
onKeydown(e) {
|
||||
if (e.key === 'Enter') {
|
||||
this.finishEditing(true)
|
||||
} else if (e.key === 'Escape') {
|
||||
this.finishEditing(false)
|
||||
}
|
||||
}
|
||||
|
||||
unload() {
|
||||
if (this.titleEl) {
|
||||
this.titleEl.removeAttribute('contenteditable')
|
||||
this.titleEl.removeEventListener('blur', this.onBlur)
|
||||
this.titleEl.removeEventListener('keydown', this.onKeydown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ export class PreviewMindMap {
|
|||
this._renderView()
|
||||
}
|
||||
|
||||
// 渲染思维导图
|
||||
async _renderView() {
|
||||
const content = await this.app.vault.read(this.file)
|
||||
const mindMapData = await this.plugin._mdToMindmapData(content)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import { PluginSettingTab, Setting, Notice } from 'obsidian'
|
||||
import themeList from 'simple-mind-map-plugin-themes/themeList'
|
||||
import { layoutGroupList } from '../src/config'
|
||||
import { GITHUB_ICON, DEFAULT_SETTINGS, COMMUNITY_ICON } from './constant'
|
||||
import { GITHUB_ICON, COMMUNITY_ICON } from './constant'
|
||||
import { DEFAULT_SETTINGS } from './constant'
|
||||
import { SuggestionModal } from './SuggestionModal'
|
||||
import { checkVersion } from './utils'
|
||||
import { TextInfoDialog } from './TextInfoDialog'
|
||||
import { fragWithHTML } from './utils'
|
||||
|
||||
const validateInteger = (value, defaultValue = 0, errorTip) => {
|
||||
value = Number(value)
|
||||
if (!Number.isNaN(value) && value > 0) {
|
||||
return value
|
||||
} else {
|
||||
// 请输入大于0的数字
|
||||
new Notice(errorTip)
|
||||
return defaultValue
|
||||
}
|
||||
|
|
@ -28,6 +29,7 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
this.compressImageOptionsMaxWidthSettings = null
|
||||
this.compressImageOptionsMaxHeightSettings = null
|
||||
this.compressImageOptionsQualitySettings = null
|
||||
this.embedImageIsSeparateFileFolderSettings = null
|
||||
}
|
||||
|
||||
display() {
|
||||
|
|
@ -40,6 +42,8 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
|
||||
this._addCompressSetting()
|
||||
|
||||
this._addImageHostingSetting()
|
||||
|
||||
this._addEmbedSetting()
|
||||
|
||||
this._addOtherSetting()
|
||||
|
|
@ -47,16 +51,14 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
this._addHelpInfo()
|
||||
}
|
||||
|
||||
// 基本设置
|
||||
_addBaseSetting() {
|
||||
const { containerEl } = this
|
||||
|
||||
containerEl.createEl('h2', { text: this.plugin._t('setting.title.title1') })
|
||||
|
||||
// 自动保存时间设置
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.autoSave.title')) // 自动保存时间
|
||||
.setDesc(this.plugin._t('setting.autoSave.desc')) // 无操作自动保存时间,单位:秒
|
||||
.setName(this.plugin._t('setting.autoSave.title'))
|
||||
.setDesc(this.plugin._t('setting.autoSave.desc'))
|
||||
.addText(text => {
|
||||
text
|
||||
.setValue(String(this.plugin.settings.autoSaveTime))
|
||||
|
|
@ -71,22 +73,21 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
})
|
||||
})
|
||||
|
||||
// 主题模式设置
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.themeMode.title')) // 主题模式
|
||||
.setDesc(this.plugin._t('setting.themeMode.desc')) // '设置深色、浅色模式'
|
||||
.setName(this.plugin._t('setting.themeMode.title'))
|
||||
.setDesc(this.plugin._t('setting.themeMode.desc'))
|
||||
.addDropdown(dropdown => {
|
||||
;[
|
||||
{
|
||||
name: this.plugin._t('setting.themeMode.option1'), // 跟随obsidian
|
||||
name: this.plugin._t('setting.themeMode.option1'),
|
||||
value: 'follow'
|
||||
},
|
||||
{
|
||||
name: this.plugin._t('setting.themeMode.option2'), // 浅色模式
|
||||
name: this.plugin._t('setting.themeMode.option2'),
|
||||
value: 'light'
|
||||
},
|
||||
{
|
||||
name: this.plugin._t('setting.themeMode.option3'), // 深色模式
|
||||
name: this.plugin._t('setting.themeMode.option3'),
|
||||
value: 'dark'
|
||||
}
|
||||
].forEach(item => {
|
||||
|
|
@ -100,19 +101,17 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
})
|
||||
})
|
||||
|
||||
// 默认主题设置
|
||||
const allThemeList = [
|
||||
{
|
||||
name: this.plugin._t('setting.theme.title'), // 默认主题
|
||||
name: this.plugin._t('setting.theme.title'),
|
||||
value: 'default',
|
||||
dark: false
|
||||
},
|
||||
...themeList
|
||||
].reverse()
|
||||
// 浅色模式的默认主题
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.theme.title'))
|
||||
.setDesc(this.plugin._t('setting.theme.desc')) // '设置默认主题'
|
||||
.setDesc(this.plugin._t('setting.theme.desc'))
|
||||
.addDropdown(dropdown => {
|
||||
allThemeList.forEach(item => {
|
||||
dropdown.addOption(item.value, item.name)
|
||||
|
|
@ -124,10 +123,9 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
// 深色模式的默认主题
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.theme.title2'))
|
||||
.setDesc(this.plugin._t('setting.theme.desc2')) // '设置默认主题'
|
||||
.setDesc(this.plugin._t('setting.theme.desc2'))
|
||||
.addDropdown(dropdown => {
|
||||
allThemeList.forEach(item => {
|
||||
dropdown.addOption(item.value, item.name)
|
||||
|
|
@ -140,7 +138,6 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
})
|
||||
})
|
||||
|
||||
// 默认结构设置
|
||||
const allLayoutList = []
|
||||
;(layoutGroupList[this.plugin.settings.lang] || layoutGroupList.en).forEach(
|
||||
item => {
|
||||
|
|
@ -154,10 +151,9 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
)
|
||||
}
|
||||
)
|
||||
// 默认结构
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.layout.title'))
|
||||
.setDesc(this.plugin._t('setting.layout.desc')) // '设置默认结构'
|
||||
.setDesc(this.plugin._t('setting.layout.desc'))
|
||||
.addDropdown(dropdown => {
|
||||
allLayoutList.forEach(item => {
|
||||
dropdown.addOption(item.value, item.name)
|
||||
|
|
@ -171,44 +167,64 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
})
|
||||
}
|
||||
|
||||
// 文件创建、保存相关设置
|
||||
_addFileSaveSetting() {
|
||||
const { containerEl } = this
|
||||
|
||||
containerEl.createEl('h2', { text: this.plugin._t('setting.title.title2') })
|
||||
|
||||
// 新建文件名的前缀
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.file.title1')) // 新建思维导图的文件名前缀
|
||||
.setDesc(this.plugin._t('setting.file.desc1')) // 新建思维导图文件名默认的命名格式为:前缀 + 空格 + 日期时间戳
|
||||
.setName(this.plugin._t('setting.file.title1'))
|
||||
.setDesc(this.plugin._t('setting.file.desc1'))
|
||||
.addText(text => {
|
||||
text
|
||||
.setValue(this.plugin.settings.fileNamePrefix)
|
||||
.setValue(this.plugin.settings.fileNameFormat)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.fileNamePrefix = value
|
||||
this.plugin.settings.fileNameFormat = value.trim()
|
||||
? value.trim()
|
||||
: DEFAULT_SETTINGS.fileNameFormat
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
.addExtraButton(button =>
|
||||
button
|
||||
.setIcon('help')
|
||||
.setTooltip(this.plugin._t('setting.file.filenameFormatDesc'))
|
||||
.onClick(async () => {
|
||||
new TextInfoDialog(this.app, {
|
||||
title: this.plugin._t('setting.file.filenameFormatDesc'),
|
||||
html: this.plugin._t('html.filenameFormatDesc')
|
||||
}).open()
|
||||
})
|
||||
)
|
||||
|
||||
// 新建文件名的日期时间戳格式
|
||||
const fileNameDateFormatSetting = new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.file.title2')) // 新建思维导图的文件名日期时间戳格式
|
||||
.setDesc(this.plugin._t('setting.file.desc2')) // 默认为:YYYY-MM-DD HH.mm.ss,实际示例:2025-01-01 23.59.59,如你不清楚该格式的具体含义及如何修改,请参考:https://day.js.org/docs/en/parse/string-format
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.file.title3'))
|
||||
.setDesc(this.plugin._t('setting.file.desc3'))
|
||||
.addText(text => {
|
||||
text
|
||||
.setValue(this.plugin.settings.fileNameDateFormat)
|
||||
.setValue(this.plugin.settings.nodePasteImageNameFormat)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.fileNameDateFormat = value
|
||||
this.plugin.settings.nodePasteImageNameFormat = value.trim()
|
||||
? value.trim()
|
||||
: DEFAULT_SETTINGS.nodePasteImageNameFormat
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
fileNameDateFormatSetting.settingEl.className +=
|
||||
' smm-setting-item-enable-select'
|
||||
.addExtraButton(button =>
|
||||
button
|
||||
.setIcon('help')
|
||||
.setTooltip(this.plugin._t('setting.file.filenameFormatDesc'))
|
||||
.onClick(async () => {
|
||||
new TextInfoDialog(this.app, {
|
||||
title: this.plugin._t('setting.file.filenameFormatDesc'),
|
||||
html: this.plugin._t('html.filenameFormatDesc')
|
||||
}).open()
|
||||
})
|
||||
)
|
||||
|
||||
// 文件存储位置
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title4')) // 文件存储位置
|
||||
.setDesc(this.plugin._t('setting.folder.desc4')) // 设置SimpleMindMap文件存储位置,默认为仓库根路径
|
||||
.setName(this.plugin._t('setting.folder.title4'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc4'))
|
||||
.addDropdown(dropdown => {
|
||||
this._getFilePathOptions().forEach(item => {
|
||||
dropdown.addOption(item.value, item.name)
|
||||
|
|
@ -221,10 +237,9 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
this._updateFilePathSettingsVisibility()
|
||||
})
|
||||
})
|
||||
// 文件存储路径设置
|
||||
this.filePathSettings = new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title1')) // 文件存储路径
|
||||
.setDesc(this.plugin._t('setting.folder.desc1')) // 设置SimpleMindMap文件存储路径,默认为仓库根路径
|
||||
.setName(this.plugin._t('setting.folder.title1'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc1'))
|
||||
.addText(text => {
|
||||
text.setValue(this.plugin.settings.filePath).onChange(async value => {
|
||||
this.plugin.settings.filePath = value
|
||||
|
|
@ -238,10 +253,9 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
this.filePathSettings.settingEl.className += ' smm-setting-sub-item'
|
||||
this._updateFilePathSettingsVisibility()
|
||||
|
||||
// 图片存储位置
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title5')) // 图片存储位置
|
||||
.setDesc(this.plugin._t('setting.folder.desc5')) // 图片存储位置
|
||||
.setName(this.plugin._t('setting.folder.title5'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc5'))
|
||||
.addDropdown(dropdown => {
|
||||
this._getFilePathOptions(true).forEach(item => {
|
||||
dropdown.addOption(item.value, item.name)
|
||||
|
|
@ -254,10 +268,9 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
this._updateImagePathSettingsVisibility()
|
||||
})
|
||||
})
|
||||
// 自定义图片存储目录
|
||||
this.imagePathSettings = new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title2')) // 图片存储路径
|
||||
.setDesc(this.plugin._t('setting.folder.desc2')) // 设置上传的图片文件(节点图片、背景图片)存储路径
|
||||
.setName(this.plugin._t('setting.folder.title2'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc2'))
|
||||
.addText(text => {
|
||||
text.setValue(this.plugin.settings.imagePath).onChange(async value => {
|
||||
this.plugin.settings.imagePath = value
|
||||
|
|
@ -269,10 +282,9 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
})
|
||||
})
|
||||
this.imagePathSettings.settingEl.className += ' smm-setting-sub-item'
|
||||
// 自定义子文件夹
|
||||
this.imageSubPathSettings = new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title6')) // 图片存储路径
|
||||
.setDesc(this.plugin._t('setting.folder.desc6')) // 设置上传的图片文件(节点图片、背景图片)存储路径
|
||||
.setName(this.plugin._t('setting.folder.title6'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc6'))
|
||||
.addText(text => {
|
||||
text
|
||||
.setValue(this.plugin.settings.imageSubPath)
|
||||
|
|
@ -288,7 +300,6 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
this.imageSubPathSettings.settingEl.className += ' smm-setting-sub-item'
|
||||
this._updateImagePathSettingsVisibility()
|
||||
|
||||
// 附件存储位置
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title7'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc7'))
|
||||
|
|
@ -304,7 +315,6 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
this._updateAttachmentPathSettingsVisibility()
|
||||
})
|
||||
})
|
||||
// 附件存储路径设置
|
||||
this.attachmentPathSettings = new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title3'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc3'))
|
||||
|
|
@ -321,7 +331,6 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
})
|
||||
})
|
||||
this.attachmentPathSettings.settingEl.className += ' smm-setting-sub-item'
|
||||
// 自定义子文件夹
|
||||
this.attachmentSubPathSettings = new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title6'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc6'))
|
||||
|
|
@ -341,7 +350,6 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
' smm-setting-sub-item'
|
||||
this._updateAttachmentPathSettingsVisibility()
|
||||
|
||||
// 是否支持ob搜索
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title8'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc8'))
|
||||
|
|
@ -354,7 +362,6 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
})
|
||||
})
|
||||
|
||||
// 是否存储画布位置和缩放数据
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title9'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc9'))
|
||||
|
|
@ -366,18 +373,31 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.folder.title10'))
|
||||
.setDesc(this.plugin._t('setting.folder.desc10'))
|
||||
.addDropdown(dropdown => {
|
||||
new Array(6).fill(0).forEach((_, index) => {
|
||||
dropdown.addOption(index + 1, index + 1)
|
||||
})
|
||||
dropdown
|
||||
.setValue(this.plugin.settings.nodeTextToMarkdownTitleMaxLevel)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.nodeTextToMarkdownTitleMaxLevel = value
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 图片压缩设置
|
||||
_addCompressSetting() {
|
||||
const { containerEl } = this
|
||||
|
||||
containerEl.createEl('h2', { text: this.plugin._t('setting.title.title3') })
|
||||
|
||||
// 是否压缩图片设置
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.compress.title1')) // 是否压缩图片
|
||||
.setDesc(this.plugin._t('setting.compress.desc1')) // 设置上传的图片文件(节点图片、背景图片)存储路径
|
||||
.setName(this.plugin._t('setting.compress.title1'))
|
||||
.setDesc(this.plugin._t('setting.compress.desc1'))
|
||||
.addToggle(toggle => {
|
||||
toggle
|
||||
.setValue(this.plugin.settings.compressImage)
|
||||
|
|
@ -388,10 +408,9 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
})
|
||||
})
|
||||
|
||||
// 压缩参数设置
|
||||
this.compressImageOptionsMaxWidthSettings = new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.compress.title2')) // 最大压缩宽度
|
||||
.setDesc(this.plugin._t('setting.compress.desc2')) // 设置图片压缩后的最大宽度
|
||||
.setName(this.plugin._t('setting.compress.title2'))
|
||||
.setDesc(this.plugin._t('setting.compress.desc2'))
|
||||
.addText(text => {
|
||||
text
|
||||
.setValue(String(this.plugin.settings.compressImageOptionsMaxWidth))
|
||||
|
|
@ -408,8 +427,8 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
this.compressImageOptionsMaxWidthSettings.settingEl.className +=
|
||||
' smm-setting-sub-item'
|
||||
this.compressImageOptionsMaxHeightSettings = new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.compress.title3')) // 最大压缩高度
|
||||
.setDesc(this.plugin._t('setting.compress.desc3')) // 设置图片压缩后的最大高度
|
||||
.setName(this.plugin._t('setting.compress.title3'))
|
||||
.setDesc(this.plugin._t('setting.compress.desc3'))
|
||||
.addText(text => {
|
||||
text
|
||||
.setValue(String(this.plugin.settings.compressImageOptionsMaxHeight))
|
||||
|
|
@ -426,15 +445,15 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
this.compressImageOptionsMaxHeightSettings.settingEl.className +=
|
||||
' smm-setting-sub-item'
|
||||
this.compressImageOptionsQualitySettings = new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.compress.title4')) // 图片质量
|
||||
.setName(this.plugin._t('setting.compress.title4'))
|
||||
.setDesc(
|
||||
`${this.plugin._t('setting.compress.desc4')},${this.plugin._t(
|
||||
'setting.compress.curValue'
|
||||
)}: ${this.plugin.settings.compressImageOptionsQuality}`
|
||||
) // 显示当前值
|
||||
)
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0, 1, 0.1) // 最小值0,最大值1,步长0.1
|
||||
.setLimits(0, 1, 0.1)
|
||||
.setValue(this.plugin.settings.compressImageOptionsQuality)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.compressImageOptionsQuality = value
|
||||
|
|
@ -472,16 +491,46 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
this._updateCompressImageSettingsVisibility()
|
||||
}
|
||||
|
||||
// 嵌入设置
|
||||
_addEmbedSetting() {
|
||||
const { containerEl } = this
|
||||
|
||||
containerEl.createEl('h2', { text: this.plugin._t('setting.title.title4') })
|
||||
|
||||
// 嵌入预览的图像背景是否透明
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.embed.title2')) // 嵌入预览的图像背景是否透明
|
||||
.setDesc(this.plugin._t('setting.embed.desc2')) // 嵌入预览的图像背景是否透明
|
||||
.setName(this.plugin._t('setting.embed.title3'))
|
||||
.setDesc(this.plugin._t('setting.embed.desc3'))
|
||||
.addToggle(toggle => {
|
||||
toggle
|
||||
.setValue(this.plugin.settings.embedImageIsSeparateFile)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.embedImageIsSeparateFile = value
|
||||
this._updateEmbedImageFileFolderSettingsVisibility()
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
|
||||
this.embedImageIsSeparateFileFolderSettings = new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.embed.title4'))
|
||||
.setDesc(this.plugin._t('setting.embed.desc4'))
|
||||
.addText(text => {
|
||||
text
|
||||
.setValue(this.plugin.settings.embedImageIsSeparateFileFolder)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.embedImageIsSeparateFileFolder = value
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
this._addFolderSelectBtn(text, selected => {
|
||||
this.plugin.settings.embedImageIsSeparateFileFolder = selected
|
||||
this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
this.embedImageIsSeparateFileFolderSettings.settingEl.className +=
|
||||
' smm-setting-sub-item'
|
||||
this._updateEmbedImageFileFolderSettingsVisibility()
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.embed.title2'))
|
||||
.setDesc(this.plugin._t('setting.embed.desc2'))
|
||||
.addToggle(toggle => {
|
||||
toggle
|
||||
.setValue(this.plugin.settings.compressImageIsTransparent)
|
||||
|
|
@ -491,10 +540,9 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
})
|
||||
})
|
||||
|
||||
// 嵌入预览双击是否新窗口打开
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.embed.title1')) // 嵌入预览双击是否新窗口打开
|
||||
.setDesc(this.plugin._t('setting.embed.desc1')) // ![[]]格式嵌入md文档时双击预览图像是否新窗口打开文件
|
||||
.setName(this.plugin._t('setting.embed.title1'))
|
||||
.setDesc(this.plugin._t('setting.embed.desc1'))
|
||||
.addToggle(toggle => {
|
||||
toggle
|
||||
.setValue(this.plugin.settings.embedDblClickNewWindow)
|
||||
|
|
@ -503,9 +551,20 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.embed.title5'))
|
||||
.setDesc(this.plugin._t('setting.embed.desc5'))
|
||||
.addToggle(toggle => {
|
||||
toggle
|
||||
.setValue(this.plugin.settings.embedLinkNewWindowOpen)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.embedLinkNewWindowOpen = value
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 给目录输入框控件添加选择按钮
|
||||
_addFolderSelectBtn(text, callback = () => {}) {
|
||||
const inputEl = text.inputEl
|
||||
const suggestionButton = inputEl.parentElement?.createEl('button', {
|
||||
|
|
@ -526,32 +585,39 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
})
|
||||
}
|
||||
|
||||
// 获取文件路径选项
|
||||
_getFilePathOptions(full = false) {
|
||||
const res = [
|
||||
{
|
||||
name: this.plugin._t('setting.folder.option1'), // 仓库的根目录
|
||||
name: this.plugin._t('setting.folder.option1'),
|
||||
value: 'root'
|
||||
},
|
||||
{
|
||||
name: this.plugin._t('setting.folder.option2'), // 指定文件夹
|
||||
name: this.plugin._t('setting.folder.option2'),
|
||||
value: 'custom'
|
||||
},
|
||||
{
|
||||
name: this.plugin._t('setting.folder.option3'), // 当前文件所在的文件夹
|
||||
name: this.plugin._t('setting.folder.option3'),
|
||||
value: 'currentFileFolder'
|
||||
}
|
||||
]
|
||||
if (full) {
|
||||
res.push({
|
||||
name: this.plugin._t('setting.folder.option4'), // 当前文件所在的文件夹的子文件夹
|
||||
name: this.plugin._t('setting.folder.option4'),
|
||||
value: 'currentFileFolderSubFolder'
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 切换文件路径设置的显示状态
|
||||
_updateEmbedImageFileFolderSettingsVisibility() {
|
||||
const isVisible = this.plugin.settings.embedImageIsSeparateFile
|
||||
if (this.embedImageIsSeparateFileFolderSettings) {
|
||||
this.embedImageIsSeparateFileFolderSettings.settingEl.style.display = isVisible
|
||||
? ''
|
||||
: 'none'
|
||||
}
|
||||
}
|
||||
|
||||
_updateFilePathSettingsVisibility() {
|
||||
const isVisible = this.plugin.settings.filePathType === 'custom'
|
||||
if (this.filePathSettings) {
|
||||
|
|
@ -559,7 +625,6 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
}
|
||||
}
|
||||
|
||||
// 切换图片路径设置的显示状态
|
||||
_updateImagePathSettingsVisibility() {
|
||||
const isVisible = this.plugin.settings.imagePathType === 'custom'
|
||||
if (this.imagePathSettings) {
|
||||
|
|
@ -574,7 +639,6 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
}
|
||||
}
|
||||
|
||||
// 切换附件路径设置的显示状态
|
||||
_updateAttachmentPathSettingsVisibility() {
|
||||
const isVisible = this.plugin.settings.attachmentPathType === 'custom'
|
||||
if (this.attachmentPathSettings) {
|
||||
|
|
@ -591,7 +655,6 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
}
|
||||
}
|
||||
|
||||
// 切换压缩图片设置的显示状态
|
||||
_updateCompressImageSettingsVisibility() {
|
||||
const isVisible = this.plugin.settings.compressImage
|
||||
if (this.compressImageOptionsMaxWidthSettings) {
|
||||
|
|
@ -611,43 +674,87 @@ export default class SmmSettingTab extends PluginSettingTab {
|
|||
}
|
||||
}
|
||||
|
||||
_addImageHostingSetting() {
|
||||
const { containerEl } = this
|
||||
|
||||
containerEl.createEl('h2', { text: this.plugin._t('setting.title.title6') })
|
||||
|
||||
// 是否开启图床
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.imageHosting.title1'))
|
||||
.setDesc(this.plugin._t('setting.imageHosting.desc1'))
|
||||
.addToggle(toggle => {
|
||||
toggle
|
||||
.setValue(this.plugin.settings.useImgHosting)
|
||||
.onChange(async value => {
|
||||
if (
|
||||
value &&
|
||||
(!this.plugin.settings.imgHostingUrl ||
|
||||
!this.plugin.settings.imgHostingFormField ||
|
||||
!this.plugin.settings.imgHostingResponseField)
|
||||
) {
|
||||
toggle.setValue(false)
|
||||
new Notice(this.plugin._t('setting.imageHosting.tip1'))
|
||||
return
|
||||
}
|
||||
this.plugin.settings.useImgHosting = value
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
|
||||
// 图床url
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.imageHosting.title2'))
|
||||
.setDesc(this.plugin._t('setting.imageHosting.desc2'))
|
||||
.addText(text => {
|
||||
text
|
||||
.setValue(String(this.plugin.settings.imgHostingUrl))
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.imgHostingUrl = value
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
|
||||
// 表单字段
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.imageHosting.title3'))
|
||||
.setDesc(this.plugin._t('setting.imageHosting.desc3'))
|
||||
.addText(text => {
|
||||
text
|
||||
.setValue(String(this.plugin.settings.imgHostingFormField))
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.imgHostingFormField = value
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
|
||||
// 接口响应结构
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.imageHosting.title4'))
|
||||
.setDesc(
|
||||
fragWithHTML(
|
||||
this.plugin._t('setting.imageHosting.desc4') +
|
||||
'<a href="https://github.com/wanglin2/obsidian-simplemindmap/blob/main/docs/imageHosting.md" target="_blank">' +
|
||||
this.plugin._t('setting.imageHosting.desc5') +
|
||||
'</a>。'
|
||||
)
|
||||
)
|
||||
.addText(text => {
|
||||
text
|
||||
.setValue(String(this.plugin.settings.imgHostingResponseField))
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.imgHostingResponseField = value
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_addOtherSetting() {
|
||||
const { containerEl } = this
|
||||
|
||||
containerEl.createEl('h2', { text: this.plugin._t('setting.title.title5') })
|
||||
|
||||
// 是否开启版本检查
|
||||
new Setting(containerEl)
|
||||
.setName(this.plugin._t('setting.other.title1')) // 是否开启版本检查
|
||||
.setDesc(this.plugin._t('setting.other.desc1')) // 是否开启版本检查
|
||||
.addToggle(toggle => {
|
||||
toggle
|
||||
.setValue(this.plugin.settings.openVersionCheck)
|
||||
.onChange(async value => {
|
||||
this.plugin.settings.openVersionCheck = value
|
||||
await this.plugin._saveSettings()
|
||||
})
|
||||
})
|
||||
.addExtraButton(button => {
|
||||
button.setIcon('refresh-cw').onClick(async () => {
|
||||
checkVersion(
|
||||
version => {
|
||||
if (version) {
|
||||
new Notice(this.plugin._t('tip.pluginNewVersion') + version)
|
||||
} else {
|
||||
new Notice(this.plugin._t('tip.pluginNoNewVersion'))
|
||||
}
|
||||
},
|
||||
true,
|
||||
() => {
|
||||
new Notice(this.plugin._t('tip.pluginVersionCheckError'))
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 添加辅助信息
|
||||
_addHelpInfo() {
|
||||
const { containerEl } = this
|
||||
const linkEl = containerEl.createDiv('setting-item smm-setting-link-list')
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export class SuggestionModal extends SuggestModal {
|
|||
super(app)
|
||||
this.plugin = plugin
|
||||
this.emptyStateText = this.plugin._t('tip.noMatchResult')
|
||||
this.type = type // folder(选择目录)
|
||||
this.type = type
|
||||
if (this.type === 'folder') {
|
||||
this.getSuggestions = this._queryFolders.bind(this)
|
||||
} else {
|
||||
|
|
@ -14,7 +14,6 @@ export class SuggestionModal extends SuggestModal {
|
|||
this.onSelect = onSelect
|
||||
}
|
||||
|
||||
// 查询目录
|
||||
_queryFolders(query) {
|
||||
const list = this.app.vault.getAllFolders()
|
||||
return list
|
||||
|
|
|
|||
34
plugin/ob/TextInfoDialog.js
Normal file
34
plugin/ob/TextInfoDialog.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { Modal } from 'obsidian'
|
||||
import i18n from 'i18next'
|
||||
|
||||
export class TextInfoDialog extends Modal {
|
||||
constructor(app, params = {}) {
|
||||
super(app)
|
||||
this.params = {
|
||||
title: '',
|
||||
html: '',
|
||||
closeText: i18n.t('tip.close'),
|
||||
...params
|
||||
}
|
||||
this.setTitle(this.params.title)
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this
|
||||
contentEl.empty()
|
||||
const contentContainer = contentEl.createDiv('smm-text-info-dialog-content')
|
||||
contentContainer.innerHTML = this.params.html
|
||||
const buttonContainer = contentEl.createDiv('smm-text-info-dialog-buttons')
|
||||
const closeBtn = buttonContainer.createEl('button', {
|
||||
text: this.params.closeText
|
||||
})
|
||||
closeBtn.addEventListener('click', () => {
|
||||
this.close()
|
||||
})
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const { contentEl } = this
|
||||
contentEl.empty()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,9 @@ export const SMM_TAG = 'simplemindmap'
|
|||
export const getDefaultSmmData = (options = {}) => {
|
||||
const theme = options.theme || 'classic13'
|
||||
const layout = options.layout || 'logicalStructure'
|
||||
const rootNodeDefaultText = options.rootNodeDefaultText || 'Root Node' // 根节点
|
||||
const secondNodeDefaultText = options.secondNodeDefaultText || 'Second Node' // 二级节点
|
||||
const branchNodeDefaultText = options.branchNodeDefaultText || 'Branch Node' // 分支主题
|
||||
const rootNodeDefaultText = options.rootNodeDefaultText || 'Root node' // 根节点
|
||||
const secondNodeDefaultText = options.secondNodeDefaultText || 'Second node' // 二级节点
|
||||
const branchNodeDefaultText = options.branchNodeDefaultText || 'Branch node' // 分支主题
|
||||
return {
|
||||
root: {
|
||||
data: { text: rootNodeDefaultText },
|
||||
|
|
@ -27,36 +27,46 @@ export const getDefaultSmmData = (options = {}) => {
|
|||
}
|
||||
|
||||
export const DEFAULT_SETTINGS = {
|
||||
isFirstUse: true, // 是否是第一次使用插件
|
||||
fileNamePrefix: 'MindMap', // 新建思维导图的文件名前缀
|
||||
fileNameDateFormat: 'YYYY-MM-DD HH.mm.ss', // 新建思维导图的文件名日期时间戳格式
|
||||
lang: 'zh', // 语言
|
||||
themeMode: 'follow', // 主题模式
|
||||
defaultLayout: 'logicalStructure', // 默认布局
|
||||
defaultTheme: 'classic13', // 浅色模式的默认主题
|
||||
defaultThemeDark: 'classic', // 深色模式的默认主题
|
||||
filePathType: 'root', // 文件存储位置
|
||||
filePath: '', // 文件存储路径
|
||||
imagePathType: 'currentFileFolder', // 图片存储位置
|
||||
imagePath: '', // 图片存储路径
|
||||
imageSubPath: '', // 自定义子文件夹
|
||||
attachmentPathType: 'currentFileFolder', // 附件存储位置
|
||||
attachmentPath: '', // 附件存储路径
|
||||
attachmentSubPath: '', // 自定义子文件夹
|
||||
compressImage: true, // 是否压缩图片
|
||||
compressImageOptionsMaxWidth: 1200, // 图片压缩后的最大宽度
|
||||
compressImageOptionsMaxHeight: 1200, // 图片压缩后的最大高度
|
||||
compressImageOptionsQuality: 0.8, // 图片质量
|
||||
attachmentPath: 'assets/files', // 附件存储路径
|
||||
compressImageIsTransparent: false, // 嵌入预览的图像背景是否透明
|
||||
embedDblClickNewWindow: true, // 嵌入预览双击是否新窗口打开
|
||||
autoSaveTime: 5, // 无操作自动保存时间,单位s
|
||||
isFirstUse: true,
|
||||
fileNameFormat: 'MindMap {date:YYYY-MM-DD HH.mm.ss}',
|
||||
lang: 'zh',
|
||||
themeMode: 'follow',
|
||||
defaultLayout: 'logicalStructure',
|
||||
defaultTheme: 'classic13',
|
||||
defaultThemeDark: 'classic',
|
||||
filePathType: 'root',
|
||||
filePath: '',
|
||||
imagePathType: 'currentFileFolder',
|
||||
imagePath: '',
|
||||
imageSubPath: '',
|
||||
attachmentPathType: 'currentFileFolder',
|
||||
attachmentPath: '',
|
||||
attachmentSubPath: '',
|
||||
compressImage: true,
|
||||
compressImageOptionsMaxWidth: 1200,
|
||||
compressImageOptionsMaxHeight: 1200,
|
||||
compressImageOptionsQuality: 0.8,
|
||||
compressImageIsTransparent: false,
|
||||
embedDblClickNewWindow: true,
|
||||
autoSaveTime: 5,
|
||||
mindMapConfig: null,
|
||||
mindMapLocalConfig: null,
|
||||
codeBlockInitHeight: 300, // smm代码块方式嵌入时初始高度,单位:px
|
||||
supportObSearch: false, // 是否支持ob搜索
|
||||
openVersionCheck: true, // 是否开启版本检查
|
||||
saveCanvasViewData: true // 是否存储画布位置和缩放数据
|
||||
supportObSearch: true,
|
||||
saveCanvasViewData: true,
|
||||
nodeTextToMarkdownTitleMaxLevel: 3,
|
||||
embedImageIsSeparateFile: true,
|
||||
embedImageIsSeparateFileFolder: '.smm-embed-image-files',
|
||||
nodePasteImageNameFormat: '{notename}_pasteImage',
|
||||
embedLinkNewWindowOpen: true,
|
||||
useImgHosting: false,
|
||||
imgHostingUrl: 'http://127.0.0.1:36677/upload',
|
||||
imgHostingFormField: 'files',
|
||||
imgHostingResponseField: 'data.result[0]'
|
||||
}
|
||||
|
||||
let smmEditViewCount = 0
|
||||
export const getSmmEditViewCount = () => {
|
||||
return smmEditViewCount++
|
||||
}
|
||||
|
||||
export const IGNORE_CHECK_SMM = 'ignoreCheckSmm'
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import { getDefaultSmmData, SMM_TAG } from './constant'
|
||||
import LZString from 'lz-string'
|
||||
import i18n from 'i18next'
|
||||
|
||||
export const parseMarkdownText = text => {
|
||||
const result = {
|
||||
metadata: { path: '', tags: [], yaml: '', content: '' },
|
||||
svgdata: '',
|
||||
linkdata: []
|
||||
svgdataUpdateAt: '',
|
||||
linkdata: [],
|
||||
textdata: []
|
||||
}
|
||||
|
||||
// Split text into lines
|
||||
const lines = text.split('\n')
|
||||
let currentSection = ''
|
||||
let inCodeBlock = false
|
||||
|
|
@ -17,13 +19,11 @@ export const parseMarkdownText = text => {
|
|||
let codeBlockContent = []
|
||||
|
||||
for (const line of lines) {
|
||||
// Handle YAML header
|
||||
if (line.trim() === '---') {
|
||||
inYamlHeader = !inYamlHeader
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for section headers
|
||||
if (line.startsWith('# metadata')) {
|
||||
currentSection = 'metadata'
|
||||
continue
|
||||
|
|
@ -34,11 +34,9 @@ export const parseMarkdownText = text => {
|
|||
currentSection = 'linkdata'
|
||||
continue
|
||||
} else if (line.startsWith('# textdata')) {
|
||||
// 用于搜索的节点文本数据,遍历到这个即可结束
|
||||
break
|
||||
}
|
||||
|
||||
// Handle code blocks
|
||||
if (line.trim() === '```metadata' || line.trim() === '```svgData') {
|
||||
inCodeBlock = true
|
||||
continue
|
||||
|
|
@ -57,13 +55,17 @@ export const parseMarkdownText = text => {
|
|||
continue
|
||||
}
|
||||
|
||||
// Collect content
|
||||
if (inCodeBlock) {
|
||||
codeBlockContent.push(line)
|
||||
} else if (currentSection === 'linkdata' && line.startsWith('- ')) {
|
||||
result.linkdata.push(line.replace('- ', '').trim())
|
||||
} else if (currentSection === 'svgdata') {
|
||||
if (line.startsWith('![[')) {
|
||||
result.svgdata = line.trim()
|
||||
} else if (line.startsWith('> updateAt:')) {
|
||||
result.svgdataUpdateAt = line.match(/> updateAt:\s*(.*)/)[1]
|
||||
}
|
||||
} else if (inYamlHeader) {
|
||||
// 解析YAML
|
||||
if (line.startsWith('tags:')) {
|
||||
inTags = true
|
||||
} else if (inTags && line.startsWith(' -')) {
|
||||
|
|
@ -85,7 +87,6 @@ export const parseMarkdownText = text => {
|
|||
export const assembleMarkdownText = obj => {
|
||||
let result = '---\n'
|
||||
|
||||
// Assemble YAML header
|
||||
result += `path: ${obj.metadata.path}\n`
|
||||
result += 'tags:\n'
|
||||
for (const tag of obj.metadata.tags) {
|
||||
|
|
@ -96,30 +97,32 @@ export const assembleMarkdownText = obj => {
|
|||
}
|
||||
result += '---\n'
|
||||
|
||||
// Assemble metadata section
|
||||
result += '> ' + i18n.t('tip.mdModifyTip') + '\n'
|
||||
|
||||
result += '# metadata\n'
|
||||
result += '```metadata\n'
|
||||
result += obj.metadata.content
|
||||
result += '\n```\n'
|
||||
|
||||
// Assemble svgdata section
|
||||
result += '# svgdata\n'
|
||||
result += '```svgData\n'
|
||||
result += obj.svgdata
|
||||
result += '\n```\n'
|
||||
if (obj.svgdata && /^!\[\[/.test(obj.svgdata)) {
|
||||
result += obj.svgdata + '\n'
|
||||
result += '> updateAt: ' + (obj.svgdataUpdateAt || '') + '\n'
|
||||
} else {
|
||||
result += '```svgData\n'
|
||||
result += obj.svgdata
|
||||
result += '\n```\n'
|
||||
}
|
||||
|
||||
// Assemble linkdata section
|
||||
result += '# linkdata\n'
|
||||
for (const item of obj.linkdata) {
|
||||
result += `- ${item}\n`
|
||||
}
|
||||
|
||||
// Assemble textdata section
|
||||
result += '# textdata\n'
|
||||
if (obj.textdata && Array.isArray(obj.textdata)) {
|
||||
result += obj.textdata.join('\n\n')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
@ -137,6 +140,8 @@ export const createDefaultText = (filePath, options) => {
|
|||
content: createDefaultMindMapData(options)
|
||||
},
|
||||
svgdata: '',
|
||||
linkdata: []
|
||||
svgdataUpdateAt: '',
|
||||
linkdata: [],
|
||||
textdata: []
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import manifestJson from '../../manifest.json'
|
||||
import { createUid } from 'simple-mind-map/src/utils/index.js'
|
||||
|
||||
// 极简的深拷贝
|
||||
export const simpleDeepClone = data => {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(data))
|
||||
|
|
@ -13,7 +12,6 @@ export const generateRandomString = (
|
|||
length = 12,
|
||||
charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
) => {
|
||||
// 参数验证
|
||||
if (typeof length !== 'number' || length <= 0) {
|
||||
throw new Error('长度必须是正整数')
|
||||
}
|
||||
|
|
@ -21,7 +19,6 @@ export const generateRandomString = (
|
|||
throw new Error('字符集不能为空')
|
||||
}
|
||||
|
||||
// 使用加密安全的随机数生成器(如果可用)
|
||||
let randomValues
|
||||
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
||||
randomValues = new Uint32Array(length)
|
||||
|
|
@ -32,7 +29,6 @@ export const generateRandomString = (
|
|||
.map(() => Math.random() * 0x100000000)
|
||||
}
|
||||
|
||||
// 生成随机字符串
|
||||
let result = ''
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = randomValues[i] % charSet.length
|
||||
|
|
@ -43,9 +39,7 @@ export const generateRandomString = (
|
|||
}
|
||||
|
||||
export const hideTargetMenu = (menu, text = '在新窗口中打开') => {
|
||||
// 隐藏特定默认菜单项
|
||||
menu.items.forEach(item => {
|
||||
// 通过菜单项标题或ID识别要隐藏的项
|
||||
if (item.title === text || item.dom?.innerText?.includes(text)) {
|
||||
item.dom.hide()
|
||||
}
|
||||
|
|
@ -72,8 +66,6 @@ export const getUidFromSource = data => {
|
|||
const last = matches[matches.length - 1]
|
||||
let index = last[1]
|
||||
let str = content[index]
|
||||
// 当检测到^时,记录后面的字符
|
||||
// 当检测到换行符时,跳出循环
|
||||
let uid = ''
|
||||
let isJoin = false
|
||||
while (true || index >= total) {
|
||||
|
|
@ -92,73 +84,44 @@ export const getUidFromSource = data => {
|
|||
return uid
|
||||
}
|
||||
|
||||
export let versionUpdateCheckTimer = null
|
||||
let versionUpdateChecked = false
|
||||
export const checkVersion = async (
|
||||
callback = () => {},
|
||||
force = false,
|
||||
err = () => {}
|
||||
) => {
|
||||
if (!force && versionUpdateChecked) {
|
||||
return
|
||||
}
|
||||
versionUpdateChecked = true
|
||||
try {
|
||||
const gitAPIrequest = async () => {
|
||||
return JSON.parse(
|
||||
await request({
|
||||
url: `https://api.github.com/repos/wanglin2/obsidian-simplemindmap/releases?per_page=15&page=1`
|
||||
})
|
||||
)
|
||||
}
|
||||
const latestVersion = (await gitAPIrequest())
|
||||
.filter(el => !el.draft && !el.prerelease)
|
||||
.map(el => {
|
||||
return {
|
||||
version: el.tag_name,
|
||||
published: new Date(el.published_at)
|
||||
}
|
||||
})
|
||||
.filter(el => el.version.match(/^\d+\.\d+\.\d+$/))
|
||||
.sort((el1, el2) => el2.published - el1.published)[0].version
|
||||
if (isVersionNewerThanOther(latestVersion, manifestJson.version)) {
|
||||
callback(latestVersion)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('检查更新失败', e)
|
||||
err(e)
|
||||
}
|
||||
versionUpdateCheckTimer = window.setTimeout(() => {
|
||||
versionUpdateChecked = false
|
||||
versionUpdateCheckTimer = null
|
||||
}, 28800000 * 3) // 24小时检查一次
|
||||
export const smmFilePathToFileName = (filePath, ext) => {
|
||||
if (!filePath) return createUid() + ext
|
||||
filePath =
|
||||
filePath
|
||||
.replace(/\//g, '_')
|
||||
.replace(/.smm.md$/, '')
|
||||
.replace(/.md$/, '') + ext
|
||||
return filePath
|
||||
}
|
||||
|
||||
export const isVersionNewerThanOther = (version, otherVersion) => {
|
||||
if (!version || !otherVersion) return true
|
||||
const v = version.match(/(\d*)\.(\d*)\.(\d*)/)
|
||||
const o = otherVersion.match(/(\d*)\.(\d*)\.(\d*)/)
|
||||
return Boolean(
|
||||
v &&
|
||||
v.length === 4 &&
|
||||
o &&
|
||||
o.length === 4 &&
|
||||
!(
|
||||
isNaN(parseInt(v[1])) ||
|
||||
isNaN(parseInt(v[2])) ||
|
||||
isNaN(parseInt(v[3]))
|
||||
) &&
|
||||
!(
|
||||
isNaN(parseInt(o[1])) ||
|
||||
isNaN(parseInt(o[2])) ||
|
||||
isNaN(parseInt(o[3]))
|
||||
) &&
|
||||
(parseInt(v[1]) > parseInt(o[1]) ||
|
||||
(parseInt(v[1]) >= parseInt(o[1]) && parseInt(v[2]) > parseInt(o[2])) ||
|
||||
(parseInt(v[1]) >= parseInt(o[1]) &&
|
||||
parseInt(v[2]) >= parseInt(o[2]) &&
|
||||
parseInt(v[3]) > parseInt(o[3])))
|
||||
)
|
||||
export const formatFileName = (str, { notename, ignores } = {}) => {
|
||||
str = str.trim()
|
||||
if (!str) return ''
|
||||
str = str.replace(/{([^{}]+)}/g, (...args) => {
|
||||
const match = args[1].trim()
|
||||
if (ignores && ignores.includes(match)) {
|
||||
return args[0]
|
||||
}
|
||||
if (match === 'notename' && notename) {
|
||||
return notename
|
||||
} else if (match === 'date') {
|
||||
return moment().format('YYYY-MM-DD')
|
||||
} else if (match === 'time') {
|
||||
return moment().format('HH:mm:ss')
|
||||
} else if (match === 'datetime') {
|
||||
return moment().format('YYYY-MM-DD HH:mm:ss')
|
||||
} else if (match === 'random') {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.substring(2, 8)
|
||||
} else if (match.startsWith('date:') && match.slice(5).trim()) {
|
||||
return moment().format(match.slice(5).trim())
|
||||
}
|
||||
return args[0]
|
||||
})
|
||||
return str
|
||||
}
|
||||
|
||||
export const fragWithHTML = html => {
|
||||
return createFragment(frag => (frag.createDiv().innerHTML = html))
|
||||
}
|
||||
2507
plugin/package-lock.json
generated
2507
plugin/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "obsidian-simple-mind-map",
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
|
@ -45,7 +45,6 @@
|
|||
"katex": "^0.16.9",
|
||||
"lz-string": "^1.5.0",
|
||||
"monkey-around": "^3.0.0",
|
||||
"simple-mind-map": "^0.14.0-fix.1",
|
||||
"simple-mind-map-plugin-themes": "^1.0.0",
|
||||
"v-viewer": "^1.6.4",
|
||||
"vue": "^2.6.14",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1753156289768') format('woff2'),
|
||||
url('iconfont.woff?t=1753156289768') format('woff'),
|
||||
url('iconfont.ttf?t=1753156289768') format('truetype');
|
||||
src: url('iconfont.woff2?t=1764835435069') format('woff2'),
|
||||
url('iconfont.woff?t=1764835435069') format('woff'),
|
||||
url('iconfont.ttf?t=1764835435069') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
|
@ -13,6 +13,78 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconfour-squares:before {
|
||||
content: "\ea3e";
|
||||
}
|
||||
|
||||
.iconyousuojin:before {
|
||||
content: "\e6f0";
|
||||
}
|
||||
|
||||
.icondaimakuai:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.iconcharulianjie:before {
|
||||
content: "\ec7f";
|
||||
}
|
||||
|
||||
.iconcharutupian:before {
|
||||
content: "\ec81";
|
||||
}
|
||||
|
||||
.iconwuxupailie:before {
|
||||
content: "\ec82";
|
||||
}
|
||||
|
||||
.iconyinyong:before {
|
||||
content: "\ec88";
|
||||
}
|
||||
|
||||
.iconyouxupailie:before {
|
||||
content: "\ec89";
|
||||
}
|
||||
|
||||
.iconzitidaima:before {
|
||||
content: "\ec8a";
|
||||
}
|
||||
|
||||
.iconzitishangbiao:before {
|
||||
content: "\ec8b";
|
||||
}
|
||||
|
||||
.iconzitibiaoti:before {
|
||||
content: "\ec8c";
|
||||
}
|
||||
|
||||
.iconzitixiabiao:before {
|
||||
content: "\ec8d";
|
||||
}
|
||||
|
||||
.iconbeijingyanse1:before {
|
||||
content: "\e645";
|
||||
}
|
||||
|
||||
.icondocdocx:before {
|
||||
content: "\e635";
|
||||
}
|
||||
|
||||
.iconsousuo1:before {
|
||||
content: "\e661";
|
||||
}
|
||||
|
||||
.icondodechild:before {
|
||||
content: "\e70e";
|
||||
}
|
||||
|
||||
.iconbofang:before {
|
||||
content: "\e8a3";
|
||||
}
|
||||
|
||||
.iconkuangxuan:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.iconcaidan:before {
|
||||
content: "\e630";
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
plugin/src/assets/img/.DS_Store
vendored
BIN
plugin/src/assets/img/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.5 KiB |
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<div class="imgUploadContainer" :class="{ isDark: isDark }">
|
||||
<div
|
||||
class="imgUploadContainer"
|
||||
:class="{ isDark: isDark }"
|
||||
v-loading="loading"
|
||||
>
|
||||
<div class="imgUploadPanel">
|
||||
<div class="upBtn" v-if="!value">
|
||||
<label
|
||||
|
|
@ -29,50 +33,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { compressImage, isNormalUrl } from '@/utils'
|
||||
import { isNormalUrl } from '@/utils'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
const getDroppedText = item => {
|
||||
return new Promise(resolve => {
|
||||
item.getAsString(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
const handleFiles = files => {
|
||||
if (!files || files.length === 0) return
|
||||
let res = null
|
||||
Array.from(files).forEach(file => {
|
||||
if (/^image\//.test(file.type)) {
|
||||
res = file
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
// 读取文字和图片
|
||||
const getDataFromDt = async dt => {
|
||||
let text = null
|
||||
let file = null
|
||||
// 检查是否有拖拽的文件
|
||||
if (dt.files && dt.files.length > 0) {
|
||||
file = handleFiles(dt.files)
|
||||
}
|
||||
// 检查是否有拖拽的文本内容
|
||||
else if (dt.items && dt.items.length > 0) {
|
||||
for (let i = 0; i < dt.items.length; i++) {
|
||||
const item = dt.items[i]
|
||||
if (item.kind === 'string' && item.type === 'text/plain') {
|
||||
text = await getDroppedText(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
text,
|
||||
file
|
||||
}
|
||||
}
|
||||
import { getDataFromDt } from './utils'
|
||||
import imgHostingUpload from '@/mixins/imgHostingUpload'
|
||||
|
||||
export default {
|
||||
mixins: [imgHostingUpload],
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
|
|
@ -85,7 +52,8 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
file: null
|
||||
file: null,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -94,15 +62,15 @@ export default {
|
|||
})
|
||||
},
|
||||
methods: {
|
||||
// 图片选择事件
|
||||
onImgUploadInputChange(e) {
|
||||
let file = e.target.files[0]
|
||||
this.selectImg(file)
|
||||
},
|
||||
|
||||
// 拖动上传图片
|
||||
async onDrop(e) {
|
||||
const { file, text } = await getDataFromDt(e.dataTransfer)
|
||||
const { file, text } = await getDataFromDt(e.dataTransfer, f => {
|
||||
return /^image\//.test(f.type)
|
||||
})
|
||||
if (file) {
|
||||
this.selectImg(file)
|
||||
} else if (text) {
|
||||
|
|
@ -114,34 +82,18 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 选择图片
|
||||
async selectImg(file) {
|
||||
try {
|
||||
const {
|
||||
compressImage: isCompress,
|
||||
compressImageOptionsMaxWidth,
|
||||
compressImageOptionsMaxHeight,
|
||||
compressImageOptionsQuality
|
||||
} = this.$root.$obsidianAPI.getSettings()
|
||||
if (isCompress) {
|
||||
file = await compressImage(file, {
|
||||
exportType: 'file',
|
||||
maxWidth: compressImageOptionsMaxWidth,
|
||||
maxHeight: compressImageOptionsMaxHeight,
|
||||
quality: compressImageOptionsQuality
|
||||
})
|
||||
}
|
||||
const result = await this.$root.$obsidianAPI.saveFileToVault(file)
|
||||
if (!result) {
|
||||
throw new Error(this.$t('imageUpload.failTip'))
|
||||
}
|
||||
this.loading = true
|
||||
const result = await this.compressAndUploadImg(file)
|
||||
this.$emit('change', result)
|
||||
this.loading = false
|
||||
} catch (error) {
|
||||
this.$root.$obsidianAPI.showTip(error)
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 获取图片大小
|
||||
getSize() {
|
||||
return new Promise(resolve => {
|
||||
let img = new Image()
|
||||
|
|
@ -161,7 +113,6 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 删除图片
|
||||
deleteImg() {
|
||||
this.$emit('change', '')
|
||||
},
|
||||
|
|
|
|||
40
plugin/src/components/ImgUpload/utils.js
Normal file
40
plugin/src/components/ImgUpload/utils.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
export const getDroppedText = item => {
|
||||
return new Promise(resolve => {
|
||||
item.getAsString(resolve)
|
||||
})
|
||||
}
|
||||
|
||||
export const handleFiles = (files, validateFileType) => {
|
||||
if (!files || files.length === 0) return
|
||||
let res = null
|
||||
Array.from(files).forEach(file => {
|
||||
if (typeof validateFileType === 'function') {
|
||||
if (validateFileType(file)) {
|
||||
res = file
|
||||
}
|
||||
} else {
|
||||
res = file
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
export const getDataFromDt = async (dt, validateFileType) => {
|
||||
let text = null
|
||||
let file = null
|
||||
if (dt.files && dt.files.length > 0) {
|
||||
file = handleFiles(dt.files, validateFileType)
|
||||
}
|
||||
else if (dt.items && dt.items.length > 0) {
|
||||
for (let i = 0; i < dt.items.length; i++) {
|
||||
const item = dt.items[i]
|
||||
if (item.kind === 'string' && item.type === 'text/plain') {
|
||||
text = await getDroppedText(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
text,
|
||||
file
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
// 布局结构图片映射
|
||||
export const layoutImgMap = {
|
||||
logicalStructure: require('../assets/img/structures/logicalStructure.jpg'),
|
||||
logicalStructureLeft: require('../assets/img/structures/logicalStructureLeft.jpg'),
|
||||
|
|
@ -8,15 +7,9 @@ export const layoutImgMap = {
|
|||
timeline: require('../assets/img/structures/timeline.jpg'),
|
||||
timeline2: require('../assets/img/structures/timeline2.jpg'),
|
||||
fishbone: require('../assets/img/structures/fishbone.jpg'),
|
||||
fishbone2: require('../assets/img/structures/fishbone2.jpg'),
|
||||
rightFishbone: require('../assets/img/structures/rightFishbone.jpg'),
|
||||
rightFishbone2: require('../assets/img/structures/rightFishbone2.jpg'),
|
||||
verticalTimeline: require('../assets/img/structures/verticalTimeline.jpg'),
|
||||
verticalTimeline2: require('../assets/img/structures/verticalTimeline2.jpg'),
|
||||
verticalTimeline3: require('../assets/img/structures/verticalTimeline3.jpg')
|
||||
verticalTimeline: require('../assets/img/structures/verticalTimeline.jpg')
|
||||
}
|
||||
|
||||
// 公式列表
|
||||
export const formulaList = [
|
||||
'a^2',
|
||||
'a_2',
|
||||
|
|
@ -40,25 +33,14 @@ export const formulaList = [
|
|||
'\\begin{cases}3x + 5y + z \\\\7x - 2y + 4z \\\\-6x + 3y + 2z\\end{cases}'
|
||||
]
|
||||
|
||||
// 支持某种连线类型的结构
|
||||
export const supportLineStyleLayoutsMap = {
|
||||
curve: [
|
||||
'logicalStructure',
|
||||
'logicalStructureLeft',
|
||||
'mindMap',
|
||||
'verticalTimeline',
|
||||
'organizationStructure'
|
||||
],
|
||||
direct: [
|
||||
'logicalStructure',
|
||||
'logicalStructureLeft',
|
||||
'mindMap',
|
||||
'organizationStructure',
|
||||
'verticalTimeline'
|
||||
]
|
||||
}
|
||||
export const supportChangeLineStyleLayouts = [
|
||||
'logicalStructure',
|
||||
'logicalStructureLeft',
|
||||
'mindMap',
|
||||
'organizationStructure',
|
||||
'verticalTimeline'
|
||||
]
|
||||
|
||||
// 直线模式支持设置圆角的结构
|
||||
export const supportLineRadiusLayouts = [
|
||||
'logicalStructure',
|
||||
'logicalStructureLeft',
|
||||
|
|
@ -66,7 +48,6 @@ export const supportLineRadiusLayouts = [
|
|||
'verticalTimeline'
|
||||
]
|
||||
|
||||
// 支持只显示底边直线风格的结构
|
||||
export const supportNodeUseLineStyleLayouts = [
|
||||
'logicalStructure',
|
||||
'logicalStructureLeft',
|
||||
|
|
@ -75,7 +56,6 @@ export const supportNodeUseLineStyleLayouts = [
|
|||
'organizationStructure'
|
||||
]
|
||||
|
||||
// 支持曲线模式下,根节点样式和其他节点样式保持一致的结构
|
||||
export const supportRootLineKeepSameInCurveLayouts = [
|
||||
'logicalStructure',
|
||||
'logicalStructureLeft',
|
||||
|
|
@ -83,7 +63,6 @@ export const supportRootLineKeepSameInCurveLayouts = [
|
|||
'organizationStructure'
|
||||
]
|
||||
|
||||
// 彩虹线条配置
|
||||
export const rainbowLinesOptions = [
|
||||
{
|
||||
value: 'close'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// 字体列表
|
||||
import { checkIsMac } from '@/utils/index.js'
|
||||
|
||||
export const fontFamilyList = [
|
||||
{
|
||||
name: 'Song Ti',
|
||||
|
|
@ -54,7 +55,6 @@ export const fontFamilyList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 边框样式
|
||||
export const borderDasharrayList = [
|
||||
{
|
||||
name: 'Solid',
|
||||
|
|
@ -90,7 +90,6 @@ export const borderDasharrayList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 连线风格
|
||||
export const lineStyleList = [
|
||||
{
|
||||
name: 'Straight',
|
||||
|
|
@ -100,13 +99,16 @@ export const lineStyleList = [
|
|||
name: 'Curve',
|
||||
value: 'curve'
|
||||
},
|
||||
{
|
||||
name: 'Curve2',
|
||||
value: 'curve2'
|
||||
},
|
||||
{
|
||||
name: 'Direct',
|
||||
value: 'direct'
|
||||
}
|
||||
]
|
||||
|
||||
// 曲线风格中,根节点样式是否和其他节点保持一致
|
||||
export const rootLineKeepSameInCurveList = [
|
||||
{
|
||||
name: 'Bracket',
|
||||
|
|
@ -118,7 +120,17 @@ export const rootLineKeepSameInCurveList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 图片重复方式
|
||||
export const rootLineKeepSameInCurveList2 = [
|
||||
{
|
||||
name: 'Bracket',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'Brace',
|
||||
value: false
|
||||
}
|
||||
]
|
||||
|
||||
export const backgroundRepeatList = [
|
||||
{
|
||||
name: 'No repeat',
|
||||
|
|
@ -138,7 +150,6 @@ export const backgroundRepeatList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 背景图片定位
|
||||
export const backgroundPositionList = [
|
||||
{
|
||||
name: 'Default',
|
||||
|
|
@ -182,12 +193,11 @@ export const backgroundPositionList = [
|
|||
}
|
||||
]
|
||||
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
||||
const isMac = checkIsMac()
|
||||
const ctrl = isMac ? '⌘' : 'Ctrl'
|
||||
const enter = isMac ? 'Return' : 'Enter'
|
||||
const macFn = isMac ? 'fn + ' : ''
|
||||
|
||||
// 背景图片大小
|
||||
export const backgroundSizeList = [
|
||||
{
|
||||
name: 'Auto',
|
||||
|
|
@ -203,16 +213,160 @@ export const backgroundSizeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 快捷键列表
|
||||
export const shortcutKeyList = [
|
||||
{
|
||||
type: 'Obsidian specific',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconbaocun',
|
||||
name: 'Save and update image data',
|
||||
value: `${ctrl} + Shfit + S`
|
||||
},
|
||||
{
|
||||
icon: 'iconxinbiaoqianyedakai',
|
||||
name: 'New tab opens hyperlink',
|
||||
value: `${ctrl} + Left key`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Node operation',
|
||||
list: [
|
||||
{
|
||||
icon: 'icontianjiazijiedian',
|
||||
name: 'Inert child node',
|
||||
value: 'Tab | Insert'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: 'Insert sibling node',
|
||||
value: enter
|
||||
},
|
||||
{
|
||||
icon: 'icondodeparent',
|
||||
name: 'Insert parent node',
|
||||
value: 'Shift + Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconshangyi',
|
||||
name: 'Move up node',
|
||||
value: `${ctrl} + ↑`
|
||||
},
|
||||
{
|
||||
icon: 'iconxiayi',
|
||||
name: 'Move down node',
|
||||
value: `${ctrl} + ↓`
|
||||
},
|
||||
{
|
||||
icon: 'icongaikuozonglan',
|
||||
name: 'Insert summary',
|
||||
value: `${ctrl} + G`
|
||||
},
|
||||
{
|
||||
icon: 'iconzhankai',
|
||||
name: 'Expand/UnExpand node',
|
||||
value: '/'
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchu',
|
||||
name: 'Delete node',
|
||||
value: 'Delete | Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchu',
|
||||
name: 'Delete current node',
|
||||
value: 'Shift + Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconbianji',
|
||||
name: 'Edit node',
|
||||
value: macFn + 'F2'
|
||||
},
|
||||
{
|
||||
icon: 'iconzhengli',
|
||||
name: 'Arrange layout',
|
||||
value: `${ctrl} + R`
|
||||
},
|
||||
{
|
||||
icon: 'iconzuoyouduiqi',
|
||||
name: 'One click same level node alignment',
|
||||
value: `${ctrl} + E`
|
||||
},
|
||||
{
|
||||
icon: 'iconsousuo',
|
||||
name: 'Search and Replace',
|
||||
value: `${ctrl} + F`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Edit operation',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconhoutui-shi',
|
||||
name: 'Undo',
|
||||
value: `${ctrl} + Z`
|
||||
},
|
||||
{
|
||||
icon: 'iconqianjin1',
|
||||
name: 'Redo',
|
||||
value: `${ctrl} + Y`
|
||||
},
|
||||
{
|
||||
icon: 'iconhuanhang',
|
||||
name: 'Text Wrap',
|
||||
name: 'Text wrap',
|
||||
value: `Shift + ${enter}`
|
||||
},
|
||||
{
|
||||
icon: 'iconcase',
|
||||
name: 'Increase font size',
|
||||
value: `${ctrl} + Shift + +`
|
||||
},
|
||||
{
|
||||
icon: 'iconcase',
|
||||
name: 'Decrease font size',
|
||||
value: `${ctrl} + Shift + -`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitijiacu',
|
||||
name: 'Bold',
|
||||
value: `${ctrl} + B`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitixieti',
|
||||
name: 'Italic',
|
||||
value: `${ctrl} + I`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitixiahuaxian',
|
||||
name: 'Underline',
|
||||
value: `${ctrl} + U`
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchuxian',
|
||||
name: 'Strikethrough',
|
||||
value: `${ctrl} + [`
|
||||
},
|
||||
{
|
||||
icon: 'iconfuzhi',
|
||||
name: 'Copy node',
|
||||
value: `${ctrl} + C`
|
||||
},
|
||||
{
|
||||
icon: 'iconjianqie',
|
||||
name: 'Cut node',
|
||||
value: `${ctrl} + X`
|
||||
},
|
||||
{
|
||||
icon: 'iconniantie',
|
||||
name: 'Paste node',
|
||||
value: `${ctrl} + V`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanxuan',
|
||||
name: 'Select all',
|
||||
value: `${ctrl} + A`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanxuan',
|
||||
name: 'Multiple choice',
|
||||
|
|
@ -223,17 +377,37 @@ export const shortcutKeyList = [
|
|||
{
|
||||
type: 'Canvas operation',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconfangda',
|
||||
name: 'Zoom in',
|
||||
value: `${ctrl} + +`
|
||||
},
|
||||
{
|
||||
icon: 'iconsuoxiao',
|
||||
name: 'Zoom out',
|
||||
value: `${ctrl} + -`
|
||||
},
|
||||
{
|
||||
icon: 'iconfangda',
|
||||
name: 'Zoom in/Zoom out',
|
||||
value: `${ctrl} + Mouse wheel`
|
||||
},
|
||||
{
|
||||
icon: 'icondingwei',
|
||||
name: 'Back root node',
|
||||
value: `${ctrl} + ${enter}`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanping1',
|
||||
name: 'Fit canvas',
|
||||
value: `${ctrl} + Shift + I`
|
||||
},
|
||||
{
|
||||
icon: 'iconshubiaoyidong',
|
||||
name: 'Moving canvas',
|
||||
value: `Left click / Right click / Space bar + Left click`
|
||||
},
|
||||
{
|
||||
{
|
||||
icon: 'iconshangxiayidong',
|
||||
name: 'Move the canvas up and down',
|
||||
value: `Mouse wheel`
|
||||
|
|
@ -250,7 +424,7 @@ export const shortcutKeyList = [
|
|||
list: [
|
||||
{
|
||||
icon: 'iconhuanhang',
|
||||
name: 'Text Wrap',
|
||||
name: 'Text wrap',
|
||||
value: `Shift + ${enter}`
|
||||
},
|
||||
{
|
||||
|
|
@ -258,26 +432,30 @@ export const shortcutKeyList = [
|
|||
name: 'Delete current node',
|
||||
value: 'Delete'
|
||||
},
|
||||
{
|
||||
icon: 'icontianjiazijiedian',
|
||||
name: 'Inert child node',
|
||||
value: 'Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: 'Insert sibling node',
|
||||
value: enter
|
||||
},
|
||||
{
|
||||
icon: 'icondodechild',
|
||||
name: 'Move down one level',
|
||||
value: 'Tab'
|
||||
},
|
||||
{
|
||||
icon: 'icondodeparent',
|
||||
name: 'Move up one level',
|
||||
value: 'Shift + Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: 'Create the content behind the cursor as a peer node',
|
||||
value: `Ctrl + ${enter}`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// 形状列表
|
||||
export const shapeList = [
|
||||
{
|
||||
name: 'Rectangle',
|
||||
|
|
@ -317,7 +495,6 @@ export const shapeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 侧边栏列表
|
||||
export const sidebarTriggerList = [
|
||||
{
|
||||
name: 'Node style',
|
||||
|
|
@ -351,7 +528,6 @@ export const sidebarTriggerList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 下载类型列表
|
||||
export const downTypeList = [
|
||||
{
|
||||
name: '思绪 file',
|
||||
|
|
@ -404,7 +580,6 @@ export const downTypeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 背景渐变方向
|
||||
export const linearGradientDirList = [
|
||||
{
|
||||
name: 'Left to right',
|
||||
|
|
@ -456,7 +631,6 @@ export const linearGradientDirList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 文本对齐方式
|
||||
export const alignList = [
|
||||
{
|
||||
name: 'Align left',
|
||||
|
|
@ -472,7 +646,6 @@ export const alignList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 结构列表
|
||||
export const layoutGroupList = [
|
||||
{
|
||||
name: 'Logical structure',
|
||||
|
|
@ -492,7 +665,11 @@ export const layoutGroupList = [
|
|||
},
|
||||
{
|
||||
name: 'Timeline',
|
||||
list: ['timeline', 'timeline2', 'verticalTimeline']
|
||||
list: [
|
||||
'timeline',
|
||||
'timeline2',
|
||||
'verticalTimeline'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Fishbone',
|
||||
|
|
@ -503,17 +680,15 @@ export const layoutGroupList = [
|
|||
export const iconNameMap = {
|
||||
back: 'Back',
|
||||
forward: 'Forward',
|
||||
painter: 'Format Painter',
|
||||
image: 'Insert Image',
|
||||
icon: 'Insert Icon',
|
||||
link: 'Insert Hyperlink',
|
||||
note: 'Insert Note',
|
||||
tag: 'Insert Tag',
|
||||
summary: 'Insert Summary',
|
||||
associativeLine: 'Add Connector Line',
|
||||
formula: 'Insert Math Formula',
|
||||
// "attachment": "Insert Attachment",
|
||||
outerFrame: 'Add Border Frame',
|
||||
annotation: 'Add Annotation',
|
||||
ai: 'AI Generate'
|
||||
}
|
||||
painter: 'Format painter',
|
||||
image: 'Insert image',
|
||||
icon: 'Insert icon',
|
||||
link: 'Insert hyperlink',
|
||||
note: 'Insert note',
|
||||
tag: 'Insert tag',
|
||||
summary: 'Insert summary',
|
||||
associativeLine: 'Add connector line',
|
||||
formula: 'Insert math formula',
|
||||
outerFrame: 'Add border frame',
|
||||
ai: 'AI generate'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,8 +129,8 @@ const weatherList = [
|
|||
|
||||
export default [
|
||||
{
|
||||
name: '多彩标记图标', // 分组名称
|
||||
type: 'sign2', // 分组的值
|
||||
name: '多彩标记图标',
|
||||
type: 'sign2',
|
||||
list: iconList.map((item, index) => {
|
||||
return {
|
||||
name: String(index + 1),
|
||||
|
|
@ -139,8 +139,8 @@ export default [
|
|||
})
|
||||
},
|
||||
{
|
||||
name: '星期', // 分组名称
|
||||
type: 'weekday', // 分组的值
|
||||
name: '星期',
|
||||
type: 'weekday',
|
||||
list: weekdayList.map((item, index) => {
|
||||
return {
|
||||
name: String(index + 1),
|
||||
|
|
@ -149,8 +149,8 @@ export default [
|
|||
})
|
||||
},
|
||||
{
|
||||
name: '头像', // 分组名称
|
||||
type: 'avatar', // 分组的值
|
||||
name: '头像',
|
||||
type: 'avatar',
|
||||
list: avatarList.map((item, index) => {
|
||||
return {
|
||||
name: String(index + 1),
|
||||
|
|
@ -159,8 +159,8 @@ export default [
|
|||
})
|
||||
},
|
||||
{
|
||||
name: '五角星', // 分组名称
|
||||
type: 'star', // 分组的值
|
||||
name: '五角星',
|
||||
type: 'star',
|
||||
list: starList.map((item, index) => {
|
||||
return {
|
||||
name: String(index + 1),
|
||||
|
|
@ -169,8 +169,8 @@ export default [
|
|||
})
|
||||
},
|
||||
{
|
||||
name: '旗帜', // 分组名称
|
||||
type: 'flag', // 分组的值
|
||||
name: '旗帜',
|
||||
type: 'flag',
|
||||
list: flagList.map((item, index) => {
|
||||
return {
|
||||
name: String(index + 1),
|
||||
|
|
@ -179,8 +179,8 @@ export default [
|
|||
})
|
||||
},
|
||||
{
|
||||
name: '天气', // 分组名称
|
||||
type: 'weather', // 分组的值
|
||||
name: '天气',
|
||||
type: 'weather',
|
||||
list: weatherList.map((item, index) => {
|
||||
return {
|
||||
name: String(index + 1),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
// 该文件请运行npm run createNodeImageList命令自动生成
|
||||
import business_ from '../assets/svg/business/-.svg'
|
||||
import business__1 from '../assets/svg/business/-_1.svg'
|
||||
import business__10 from '../assets/svg/business/-_10.svg'
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
borderDasharrayList as borderDasharrayListZh,
|
||||
lineStyleList as lineStyleListZh,
|
||||
rootLineKeepSameInCurveList as rootLineKeepSameInCurveListZh,
|
||||
rootLineKeepSameInCurveList2 as rootLineKeepSameInCurveList2Zh,
|
||||
backgroundRepeatList as backgroundRepeatListZh,
|
||||
backgroundPositionList as backgroundPositionListZh,
|
||||
shortcutKeyList as shortcutKeyListZh,
|
||||
|
|
@ -23,13 +24,14 @@ import {
|
|||
downTypeList as downTypeListZh,
|
||||
linearGradientDirList as linearGradientDirListZh,
|
||||
alignList as alignListZh,
|
||||
layoutGroupList as layoutGroupListZh
|
||||
layoutGroupList as layoutGroupListZh,
|
||||
} from './zh'
|
||||
import {
|
||||
fontFamilyList as fontFamilyListEn,
|
||||
borderDasharrayList as borderDasharrayListEn,
|
||||
lineStyleList as lineStyleListEn,
|
||||
rootLineKeepSameInCurveList as rootLineKeepSameInCurveListEn,
|
||||
rootLineKeepSameInCurveList2 as rootLineKeepSameInCurveList2En,
|
||||
backgroundRepeatList as backgroundRepeatListEn,
|
||||
backgroundPositionList as backgroundPositionListEn,
|
||||
shortcutKeyList as shortcutKeyListEn,
|
||||
|
|
@ -47,6 +49,7 @@ import {
|
|||
borderDasharrayList as borderDasharrayListZhtw,
|
||||
lineStyleList as lineStyleListZhtw,
|
||||
rootLineKeepSameInCurveList as rootLineKeepSameInCurveListZhtw,
|
||||
rootLineKeepSameInCurveList2 as rootLineKeepSameInCurveList2Zhtw,
|
||||
backgroundRepeatList as backgroundRepeatListZhtw,
|
||||
backgroundPositionList as backgroundPositionListZhtw,
|
||||
shortcutKeyList as shortcutKeyListZhtw,
|
||||
|
|
@ -64,6 +67,7 @@ import {
|
|||
borderDasharrayList as borderDasharrayListVi,
|
||||
lineStyleList as lineStyleListVi,
|
||||
rootLineKeepSameInCurveList as rootLineKeepSameInCurveListVi,
|
||||
rootLineKeepSameInCurveList2 as rootLineKeepSameInCurveList2Vi,
|
||||
backgroundRepeatList as backgroundRepeatListVi,
|
||||
backgroundPositionList as backgroundPositionListVi,
|
||||
shortcutKeyList as shortcutKeyListVi,
|
||||
|
|
@ -105,6 +109,13 @@ const rootLineKeepSameInCurveList = {
|
|||
vi: rootLineKeepSameInCurveListVi
|
||||
}
|
||||
|
||||
const rootLineKeepSameInCurveList2 = {
|
||||
zh: rootLineKeepSameInCurveList2Zh,
|
||||
en: rootLineKeepSameInCurveList2En,
|
||||
zhtw: rootLineKeepSameInCurveList2Zhtw,
|
||||
vi: rootLineKeepSameInCurveList2Vi
|
||||
}
|
||||
|
||||
const backgroundRepeatList = {
|
||||
zh: backgroundRepeatListZh,
|
||||
en: backgroundRepeatListEn,
|
||||
|
|
@ -196,6 +207,7 @@ export {
|
|||
lineStyleList,
|
||||
lineStyleMap,
|
||||
rootLineKeepSameInCurveList,
|
||||
rootLineKeepSameInCurveList2,
|
||||
backgroundRepeatList,
|
||||
backgroundPositionList,
|
||||
backgroundSizeList,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Danh sách phông chữ
|
||||
import { checkIsMac } from '@/utils/index.js'
|
||||
|
||||
export const fontFamilyList = [
|
||||
{
|
||||
name: 'Song Thân',
|
||||
|
|
@ -54,7 +55,6 @@ export const fontFamilyList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Kiểu viền
|
||||
export const borderDasharrayList = [
|
||||
{
|
||||
name: 'Dòng rắn',
|
||||
|
|
@ -90,7 +90,6 @@ export const borderDasharrayList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Kiểu kết nối
|
||||
export const lineStyleList = [
|
||||
{
|
||||
name: 'Đường thẳng',
|
||||
|
|
@ -100,13 +99,16 @@ export const lineStyleList = [
|
|||
name: 'Đường cong',
|
||||
value: 'curve'
|
||||
},
|
||||
{
|
||||
name: 'Đường cong2',
|
||||
value: 'curve2'
|
||||
},
|
||||
{
|
||||
name: 'Trực tiếp',
|
||||
value: 'direct'
|
||||
}
|
||||
]
|
||||
|
||||
// Trong một kiểu đường cong, kiểu nút gốc có giống với các nút khác hay không
|
||||
export const rootLineKeepSameInCurveList = [
|
||||
{
|
||||
name: 'ngoặc đơn',
|
||||
|
|
@ -118,7 +120,17 @@ export const rootLineKeepSameInCurveList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Cách lặp lại hình ảnh
|
||||
export const rootLineKeepSameInCurveList2 = [
|
||||
{
|
||||
name: 'ngoặc đơn',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: 'ngoặc nhọn',
|
||||
value: false
|
||||
}
|
||||
]
|
||||
|
||||
export const backgroundRepeatList = [
|
||||
{
|
||||
name: 'Không lặp lại',
|
||||
|
|
@ -138,7 +150,6 @@ export const backgroundRepeatList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Định vị ảnh nền
|
||||
export const backgroundPositionList = [
|
||||
{
|
||||
name: 'Mặc định',
|
||||
|
|
@ -182,7 +193,6 @@ export const backgroundPositionList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Cỡ ảnh nền
|
||||
export const backgroundSizeList = [
|
||||
{
|
||||
name: 'Tự động',
|
||||
|
|
@ -198,25 +208,168 @@ export const backgroundSizeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Lưu trữ dữ liệu
|
||||
export const store = {
|
||||
sidebarZIndex: 1 //Thanh bên zIndex
|
||||
sidebarZIndex: 1
|
||||
}
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
||||
const isMac = checkIsMac()
|
||||
const ctrl = isMac ? '⌘' : 'Ctrl'
|
||||
const enter = isMac ? 'Return' : 'Enter'
|
||||
const macFn = isMac ? 'fn + ' : ''
|
||||
|
||||
// Danh sách phím tắt
|
||||
export const shortcutKeyList = [
|
||||
{
|
||||
type: 'Obsidian đặc biệt',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconbaocun',
|
||||
name: 'Lưu và cập nhật dữ liệu hình ảnh',
|
||||
value: `${ctrl} + Shfit + S`
|
||||
},
|
||||
{
|
||||
icon: 'iconxinbiaoqianyedakai',
|
||||
name: 'Trang nhãn mới Mở siêu kết nối',
|
||||
value: `${ctrl} + Trái`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Hoạt động nút',
|
||||
list: [
|
||||
{
|
||||
icon: 'icontianjiazijiedian',
|
||||
name: 'Chèn nút thấp hơn',
|
||||
value: 'Tab | Insert'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: 'Chèn nút ngang hàng',
|
||||
value: enter
|
||||
},
|
||||
{
|
||||
icon: 'icondodeparent',
|
||||
name: 'Chèn nút cha',
|
||||
value: 'Shift + Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconshangyi',
|
||||
name: 'Chuyển nút lên',
|
||||
value: `${ctrl} + ↑`
|
||||
},
|
||||
{
|
||||
icon: 'iconxiayi',
|
||||
name: 'Di chuyển nút xuống',
|
||||
value: `${ctrl} + ↓`
|
||||
},
|
||||
{
|
||||
icon: 'icongaikuozonglan',
|
||||
name: 'Chèn tóm tắt',
|
||||
value: `${ctrl} + G`
|
||||
},
|
||||
{
|
||||
icon: 'iconzhankai',
|
||||
name: 'Mở rộng/thu gọn các nút',
|
||||
value: '/'
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchu',
|
||||
name: 'Xóa nút',
|
||||
value: 'Delete | Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchu',
|
||||
name: 'Chỉ xóa nút hiện tại',
|
||||
value: 'Shift + Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconbianji',
|
||||
name: 'Sửa nút',
|
||||
value: macFn + 'F2'
|
||||
},
|
||||
{
|
||||
icon: 'iconzhengli',
|
||||
name: 'Name',
|
||||
value: `${ctrl} + R`
|
||||
},
|
||||
{
|
||||
icon: 'iconzuoyouduiqi',
|
||||
name: 'Căn chỉnh một nút cùng cấps',
|
||||
value: `${ctrl} + E`
|
||||
},
|
||||
{
|
||||
icon: 'iconsousuo',
|
||||
name: 'Tìm kiếm và thay thế',
|
||||
value: `${ctrl} + F`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Sửa hành động',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconhoutui-shi',
|
||||
name: 'Lùi lại',
|
||||
value: `${ctrl} + Z`
|
||||
},
|
||||
{
|
||||
icon: 'iconqianjin1',
|
||||
name: 'Tiến lên!',
|
||||
value: `${ctrl} + Y`
|
||||
},
|
||||
{
|
||||
icon: 'iconhuanhang',
|
||||
name: 'Dòng mới',
|
||||
value: `Shift + ${enter}`
|
||||
},
|
||||
{
|
||||
icon: 'iconcase',
|
||||
name: 'Mở cỡ',
|
||||
value: `${ctrl} + Shift + +`
|
||||
},
|
||||
{
|
||||
icon: 'iconcase',
|
||||
name: 'Giảm cỡ',
|
||||
value: `${ctrl} + Shift + -`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitijiacu',
|
||||
name: 'Thô',
|
||||
value: `${ctrl} + B`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitixieti',
|
||||
name: 'Nghiêng',
|
||||
value: `${ctrl} + I`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitixiahuaxian',
|
||||
name: 'Thêm dòng xóa',
|
||||
value: `${ctrl} + U`
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchuxian',
|
||||
name: 'Thêm dòng xóa',
|
||||
value: `${ctrl} + [`
|
||||
},
|
||||
{
|
||||
icon: 'iconfuzhi',
|
||||
name: 'Sao chép nút',
|
||||
value: `${ctrl} + C`
|
||||
},
|
||||
{
|
||||
icon: 'iconjianqie',
|
||||
name: 'Cắt nút',
|
||||
value: `${ctrl} + X`
|
||||
},
|
||||
{
|
||||
icon: 'iconniantie',
|
||||
name: 'Dán nút',
|
||||
value: `${ctrl} + V`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanxuan',
|
||||
name: 'Chọn tất cả',
|
||||
value: `${ctrl} + A`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanxuan',
|
||||
name: 'Nhiều lựa chọn',
|
||||
|
|
@ -227,11 +380,31 @@ export const shortcutKeyList = [
|
|||
{
|
||||
type: 'Hoạt động Canvas',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconfangda',
|
||||
name: 'Phóng to',
|
||||
value: `${ctrl} + +`
|
||||
},
|
||||
{
|
||||
icon: 'iconsuoxiao',
|
||||
name: 'Thu nhỏ',
|
||||
value: `${ctrl} + -`
|
||||
},
|
||||
{
|
||||
icon: 'iconfangda',
|
||||
name: 'Phóng to/Thu nhỏ',
|
||||
value: `${ctrl} + Cuộn chuột`
|
||||
},
|
||||
{
|
||||
icon: 'icondingwei',
|
||||
name: 'Trở lại Root node',
|
||||
value: `${ctrl} + ${enter}`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanping1',
|
||||
name: 'Thích nghi với Canvas',
|
||||
value: `${ctrl} + Shift + I`
|
||||
},
|
||||
{
|
||||
icon: 'iconshubiaoyidong',
|
||||
name: 'Di chuyển Canvas',
|
||||
|
|
@ -262,26 +435,30 @@ export const shortcutKeyList = [
|
|||
name: 'Xóa nút',
|
||||
value: 'Delete'
|
||||
},
|
||||
{
|
||||
icon: 'icontianjiazijiedian',
|
||||
name: 'Chèn nút thấp hơn',
|
||||
value: 'Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: 'Chèn nút ngang hàng',
|
||||
value: enter
|
||||
},
|
||||
{
|
||||
icon: 'icondodechild',
|
||||
name: 'Di chuyển một bậc xuống',
|
||||
value: 'Tab'
|
||||
},
|
||||
{
|
||||
icon: 'icondodeparent',
|
||||
name: 'Di chuyển lên một cấp',
|
||||
value: 'Shift + Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: 'Tạo nội dung sau con trỏ là nút cùng cấp',
|
||||
value: `Ctrl + ${enter}`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// Danh sách hình dạng
|
||||
export const shapeList = [
|
||||
{
|
||||
name: 'Hình chữ nhật',
|
||||
|
|
@ -321,7 +498,6 @@ export const shapeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Danh sách thanh bên
|
||||
export const sidebarTriggerList = [
|
||||
{
|
||||
name: 'Kiểu nút',
|
||||
|
|
@ -355,7 +531,6 @@ export const sidebarTriggerList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Danh sách các loại tải xuống
|
||||
export const downTypeList = [
|
||||
{
|
||||
name: '思绪Tài liệu',
|
||||
|
|
@ -409,7 +584,6 @@ export const downTypeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Hướng gradient nền
|
||||
export const linearGradientDirList = [
|
||||
{
|
||||
name: 'Từ trái sang phải',
|
||||
|
|
@ -461,7 +635,6 @@ export const linearGradientDirList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Căn lề văn bản
|
||||
export const alignList = [
|
||||
{
|
||||
name: 'Trái:',
|
||||
|
|
@ -477,7 +650,6 @@ export const alignList = [
|
|||
}
|
||||
]
|
||||
|
||||
// Danh sách cấu trúc
|
||||
export const layoutGroupList = [
|
||||
{
|
||||
name: 'Sơ đồ cấu trúc logic',
|
||||
|
|
@ -497,7 +669,11 @@ export const layoutGroupList = [
|
|||
},
|
||||
{
|
||||
name: 'Dòng thời gian',
|
||||
list: ['timeline', 'timeline2', 'verticalTimeline']
|
||||
list: [
|
||||
'timeline',
|
||||
'timeline2',
|
||||
'verticalTimeline'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Bản đồ Fishbone',
|
||||
|
|
@ -517,8 +693,6 @@ export const iconNameMap = {
|
|||
summary: 'Chèn tóm tắt',
|
||||
associativeLine: 'Thêm đường liên kết',
|
||||
formula: 'Chèn công thức toán',
|
||||
// "attachment": "Chèn tệp đính kèm",
|
||||
outerFrame: 'Thêm khung viền',
|
||||
annotation: 'Thêm chú thích',
|
||||
ai: 'AI tạo tự động'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// 字体列表
|
||||
import { checkIsMac } from '@/utils/index.js'
|
||||
|
||||
export const fontFamilyList = [
|
||||
{
|
||||
name: '宋体',
|
||||
|
|
@ -54,10 +55,8 @@ export const fontFamilyList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 字号
|
||||
export const fontSizeList = [10, 12, 14, 16, 18, 24, 32, 48]
|
||||
|
||||
// 颜色
|
||||
export const colorList = [
|
||||
'#4D4D4D',
|
||||
'#999999',
|
||||
|
|
@ -94,14 +93,11 @@ export const colorList = [
|
|||
'#0C797D',
|
||||
'#0062B1',
|
||||
'#653294',
|
||||
// '#AB149E',
|
||||
'transparent'
|
||||
]
|
||||
|
||||
// 边框宽度
|
||||
export const borderWidthList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
// 边框样式
|
||||
export const borderDasharrayList = [
|
||||
{
|
||||
name: '实线',
|
||||
|
|
@ -137,22 +133,19 @@ export const borderDasharrayList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 圆角
|
||||
export const borderRadiusList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
// 线宽
|
||||
export const lineWidthList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
|
||||
// 行高
|
||||
export const lineHeightList = [1, 1.2, 1.5, 2, 2.5, 3]
|
||||
|
||||
export const lineStyleMap = {
|
||||
straight: `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="60" height="26"><path d="M18,14L30,14L30,5L42,5" fill="none" stroke="#000" stroke-width="2"></path><path d="M18,14L30,14L30,23L42,23" fill="none" stroke="#000" stroke-width="2"></path></svg>`,
|
||||
curve: `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="60" height="26"><path d="M18,14L30,14A12,-9 0 0 1 42,5" fill="none" stroke="#000" stroke-width="2"></path><path d="M18,14L30,14A12,9 0 0 0 42,23" fill="none" stroke="#000" stroke-width="2"></path></svg>`,
|
||||
curve: `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="60" height="26"><path d="M18,14C30,14 30,5 42,5" fill="none" stroke="#000" stroke-width="2"></path><path d="M18,14C30,14 30,23 42,23" fill="none" stroke="#000" stroke-width="2"></path></svg>`,
|
||||
curve2: `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="60" height="26"><path d="M18,14L30,14A12,-9 0 0 1 42,5" fill="none" stroke="#000" stroke-width="2"></path><path d="M18,14L30,14A12,9 0 0 0 42,23" fill="none" stroke="#000" stroke-width="2"></path></svg>`,
|
||||
direct: `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="60" height="26"><path d="M18,14L30,14L42,5" fill="none" stroke="#000" stroke-width="2"></path><path d="M18,14L30,14L42,23" fill="none" stroke="#000" stroke-width="2"></path></svg>`
|
||||
}
|
||||
|
||||
// 连线风格
|
||||
export const lineStyleList = [
|
||||
{
|
||||
name: '直线',
|
||||
|
|
@ -162,13 +155,16 @@ export const lineStyleList = [
|
|||
name: '曲线',
|
||||
value: 'curve'
|
||||
},
|
||||
{
|
||||
name: '曲线2',
|
||||
value: 'curve2'
|
||||
},
|
||||
{
|
||||
name: '直连',
|
||||
value: 'direct'
|
||||
}
|
||||
]
|
||||
|
||||
// 曲线风格中,根节点样式是否和其他节点保持一致
|
||||
export const rootLineKeepSameInCurveList = [
|
||||
{
|
||||
name: '括号',
|
||||
|
|
@ -180,7 +176,17 @@ export const rootLineKeepSameInCurveList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 图片重复方式
|
||||
export const rootLineKeepSameInCurveList2 = [
|
||||
{
|
||||
name: '括号',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: '大括号',
|
||||
value: false
|
||||
}
|
||||
]
|
||||
|
||||
export const backgroundRepeatList = [
|
||||
{
|
||||
name: '不重复',
|
||||
|
|
@ -200,7 +206,6 @@ export const backgroundRepeatList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 背景图片定位
|
||||
export const backgroundPositionList = [
|
||||
{
|
||||
name: '默认',
|
||||
|
|
@ -244,7 +249,6 @@ export const backgroundPositionList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 背景图片大小
|
||||
export const backgroundSizeList = [
|
||||
{
|
||||
name: '自动',
|
||||
|
|
@ -260,25 +264,163 @@ export const backgroundSizeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 数据存储
|
||||
export const store = {
|
||||
sidebarZIndex: 1 //侧边栏zIndex
|
||||
sidebarZIndex: 1
|
||||
}
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
||||
const isMac = checkIsMac()
|
||||
const ctrl = isMac ? '⌘' : 'Ctrl'
|
||||
const enter = isMac ? 'Return' : 'Enter'
|
||||
const macFn = isMac ? 'fn + ' : ''
|
||||
|
||||
// 快捷键列表
|
||||
export const shortcutKeyList = [
|
||||
{
|
||||
type: 'Obsidian特有',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconbaocun',
|
||||
name: '保存并更新图像数据',
|
||||
value: `${ctrl} + Shfit + S`
|
||||
},
|
||||
{
|
||||
icon: 'iconxinbiaoqianyedakai',
|
||||
name: '新标签页打开超链接',
|
||||
value: `${ctrl} + 左键`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: '节点操作',
|
||||
list: [
|
||||
{
|
||||
icon: 'icontianjiazijiedian',
|
||||
name: '插入下级节点',
|
||||
value: 'Tab | Insert'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: '插入同级节点',
|
||||
value: enter
|
||||
},
|
||||
{
|
||||
icon: 'icondodeparent',
|
||||
name: '插入父节点',
|
||||
value: 'Shift + Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconshangyi',
|
||||
name: '上移节点',
|
||||
value: `${ctrl} + ↑`
|
||||
},
|
||||
{
|
||||
icon: 'iconxiayi',
|
||||
name: '下移节点',
|
||||
value: `${ctrl} + ↓`
|
||||
},
|
||||
{
|
||||
icon: 'icongaikuozonglan',
|
||||
name: '插入概要',
|
||||
value: `${ctrl} + G`
|
||||
},
|
||||
{
|
||||
icon: 'iconzhankai',
|
||||
name: '展开/收起节点',
|
||||
value: '/'
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchu',
|
||||
name: '删除节点',
|
||||
value: 'Delete | Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchu',
|
||||
name: '仅删除当前节点',
|
||||
value: 'Shift + Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconbianji',
|
||||
name: '编辑节点',
|
||||
value: macFn + 'F2'
|
||||
},
|
||||
{
|
||||
icon: 'iconzuoyouduiqi',
|
||||
name: '一键同级节点对齐',
|
||||
value: `${ctrl} + E`
|
||||
},
|
||||
{
|
||||
icon: 'iconsousuo',
|
||||
name: '搜索和替换',
|
||||
value: `${ctrl} + F`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: '编辑操作',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconhoutui-shi',
|
||||
name: '回退',
|
||||
value: `${ctrl} + Z`
|
||||
},
|
||||
{
|
||||
icon: 'iconqianjin1',
|
||||
name: '前进',
|
||||
value: `${ctrl} + Y`
|
||||
},
|
||||
{
|
||||
icon: 'iconhuanhang',
|
||||
name: '文本换行',
|
||||
value: `Shift + ${enter}`
|
||||
},
|
||||
{
|
||||
icon: 'iconcase',
|
||||
name: '增大字号',
|
||||
value: `${ctrl} + Shift + +`
|
||||
},
|
||||
{
|
||||
icon: 'iconcase',
|
||||
name: '减小字号',
|
||||
value: `${ctrl} + Shift + -`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitijiacu',
|
||||
name: '加粗',
|
||||
value: `${ctrl} + B`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitixieti',
|
||||
name: '斜体',
|
||||
value: `${ctrl} + I`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitixiahuaxian',
|
||||
name: '添加下划线',
|
||||
value: `${ctrl} + U`
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchuxian',
|
||||
name: '添加删除线',
|
||||
value: `${ctrl} + [`
|
||||
},
|
||||
{
|
||||
icon: 'iconfuzhi',
|
||||
name: '复制节点',
|
||||
value: `${ctrl} + C`
|
||||
},
|
||||
{
|
||||
icon: 'iconjianqie',
|
||||
name: '剪切节点',
|
||||
value: `${ctrl} + X`
|
||||
},
|
||||
{
|
||||
icon: 'iconniantie',
|
||||
name: '粘贴节点',
|
||||
value: `${ctrl} + V`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanxuan',
|
||||
name: '全选',
|
||||
value: `${ctrl} + A`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanxuan',
|
||||
name: '多选',
|
||||
|
|
@ -289,11 +431,36 @@ export const shortcutKeyList = [
|
|||
{
|
||||
type: '画布操作',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconfangda',
|
||||
name: '放大',
|
||||
value: `${ctrl} + +`
|
||||
},
|
||||
{
|
||||
icon: 'iconsuoxiao',
|
||||
name: '缩小',
|
||||
value: `${ctrl} + -`
|
||||
},
|
||||
{
|
||||
icon: 'iconfangda',
|
||||
name: '放大/缩小',
|
||||
value: `${ctrl} + 鼠标滚动`
|
||||
},
|
||||
{
|
||||
icon: 'icondingwei',
|
||||
name: '回到根节点',
|
||||
value: `${ctrl} + ${enter}`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanping1',
|
||||
name: '适应画布',
|
||||
value: `${ctrl} + Shift + I`
|
||||
},
|
||||
{
|
||||
icon: 'iconzhengli',
|
||||
name: '一键整理布局',
|
||||
value: `${ctrl} + R`
|
||||
},
|
||||
{
|
||||
icon: 'iconshubiaoyidong',
|
||||
name: '移动画布',
|
||||
|
|
@ -324,20 +491,25 @@ export const shortcutKeyList = [
|
|||
name: '删除节点',
|
||||
value: 'Delete'
|
||||
},
|
||||
{
|
||||
icon: 'icontianjiazijiedian',
|
||||
name: '插入下级节点',
|
||||
value: 'Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: '插入同级节点',
|
||||
value: enter
|
||||
},
|
||||
{
|
||||
icon: 'icondodechild',
|
||||
name: '下移一个层级',
|
||||
value: 'Tab'
|
||||
},
|
||||
{
|
||||
icon: 'icondodeparent',
|
||||
name: '上移一个层级',
|
||||
value: 'Shift + Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: '将光标后的内容创建为同级节点',
|
||||
value: `Ctrl + ${enter}`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -359,7 +531,6 @@ export const shapeListMap = {
|
|||
circle: 'M 21 12 A 9 9 0, 1, 0 30 3 A 9 9 0, 0, 0 21 12 Z'
|
||||
}
|
||||
|
||||
// 形状列表
|
||||
export const shapeList = [
|
||||
{
|
||||
name: '矩形',
|
||||
|
|
@ -399,7 +570,6 @@ export const shapeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 多语言列表
|
||||
export const langList = [
|
||||
{
|
||||
value: 'zh',
|
||||
|
|
@ -420,7 +590,6 @@ export const langList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 侧边栏列表
|
||||
export const sidebarTriggerList = [
|
||||
{
|
||||
name: '节点样式',
|
||||
|
|
@ -454,7 +623,6 @@ export const sidebarTriggerList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 下载类型列表
|
||||
export const downTypeList = [
|
||||
{
|
||||
name: '思绪文件',
|
||||
|
|
@ -506,7 +674,6 @@ export const downTypeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 背景渐变方向
|
||||
export const linearGradientDirList = [
|
||||
{
|
||||
name: '从左到右',
|
||||
|
|
@ -558,7 +725,6 @@ export const linearGradientDirList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 文本对齐方式
|
||||
export const alignList = [
|
||||
{
|
||||
name: '左对齐',
|
||||
|
|
@ -574,7 +740,6 @@ export const alignList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 结构列表
|
||||
export const layoutGroupList = [
|
||||
{
|
||||
name: '逻辑结构图',
|
||||
|
|
@ -594,7 +759,11 @@ export const layoutGroupList = [
|
|||
},
|
||||
{
|
||||
name: '时间轴',
|
||||
list: ['timeline', 'timeline2', 'verticalTimeline']
|
||||
list: [
|
||||
'timeline',
|
||||
'timeline2',
|
||||
'verticalTimeline'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '鱼骨图',
|
||||
|
|
@ -614,8 +783,6 @@ export const iconNameMap = {
|
|||
summary: '插入概要',
|
||||
associativeLine: '添加关联线',
|
||||
formula: '插入数学公式',
|
||||
// 'attachment': '插入附件',
|
||||
outerFrame: '添加外框',
|
||||
annotation: '添加标记',
|
||||
ai: 'AI一键生成'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// 字型列表
|
||||
import { checkIsMac } from '@/utils/index.js'
|
||||
|
||||
export const fontFamilyList = [
|
||||
{
|
||||
name: '宋體',
|
||||
|
|
@ -54,7 +55,6 @@ export const fontFamilyList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 框線樣式
|
||||
export const borderDasharrayList = [
|
||||
{
|
||||
name: '實線',
|
||||
|
|
@ -90,7 +90,6 @@ export const borderDasharrayList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 連線樣式
|
||||
export const lineStyleList = [
|
||||
{
|
||||
name: '直線',
|
||||
|
|
@ -100,13 +99,16 @@ export const lineStyleList = [
|
|||
name: '曲線',
|
||||
value: 'curve'
|
||||
},
|
||||
{
|
||||
name: '曲線2',
|
||||
value: 'curve2'
|
||||
},
|
||||
{
|
||||
name: '直接連線',
|
||||
value: 'direct'
|
||||
}
|
||||
]
|
||||
|
||||
// 曲線樣式中,根節點樣式是否和其他節點保持一致
|
||||
export const rootLineKeepSameInCurveList = [
|
||||
{
|
||||
name: '括號',
|
||||
|
|
@ -118,7 +120,17 @@ export const rootLineKeepSameInCurveList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 圖片重複方式
|
||||
export const rootLineKeepSameInCurveList2 = [
|
||||
{
|
||||
name: '括號',
|
||||
value: true
|
||||
},
|
||||
{
|
||||
name: '大括號',
|
||||
value: false
|
||||
}
|
||||
]
|
||||
|
||||
export const backgroundRepeatList = [
|
||||
{
|
||||
name: '不重複',
|
||||
|
|
@ -138,7 +150,6 @@ export const backgroundRepeatList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 背景圖片位置
|
||||
export const backgroundPositionList = [
|
||||
{
|
||||
name: '預設',
|
||||
|
|
@ -182,12 +193,11 @@ export const backgroundPositionList = [
|
|||
}
|
||||
]
|
||||
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
||||
const isMac = checkIsMac()
|
||||
const ctrl = isMac ? '⌘' : 'Ctrl'
|
||||
const enter = isMac ? 'Return' : 'Enter'
|
||||
const macFn = isMac ? 'fn + ' : ''
|
||||
|
||||
// 背景圖片大小
|
||||
export const backgroundSizeList = [
|
||||
{
|
||||
name: '自動',
|
||||
|
|
@ -203,16 +213,160 @@ export const backgroundSizeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 快捷鍵列表
|
||||
export const shortcutKeyList = [
|
||||
{
|
||||
type: 'Obsidian特有',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconbaocun',
|
||||
name: '保存並更新影像數據',
|
||||
value: `${ctrl} + Shfit + S`
|
||||
},
|
||||
{
|
||||
icon: 'iconxinbiaoqianyedakai',
|
||||
name: '新標籤頁打開超連結',
|
||||
value: `${ctrl} + 左鍵`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: '節點操作',
|
||||
list: [
|
||||
{
|
||||
icon: 'icontianjiazijiedian',
|
||||
name: '插入子節點',
|
||||
value: 'Tab | Insert'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: '插入同層節點',
|
||||
value: enter
|
||||
},
|
||||
{
|
||||
icon: 'icondodeparent',
|
||||
name: '插入父節點',
|
||||
value: 'Shift + Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconshangyi',
|
||||
name: '上移節點',
|
||||
value: `${ctrl} + ↑`
|
||||
},
|
||||
{
|
||||
icon: 'iconxiayi',
|
||||
name: '下移節點',
|
||||
value: `${ctrl} + ↓`
|
||||
},
|
||||
{
|
||||
icon: 'icongaikuozonglan',
|
||||
name: '插入摘要',
|
||||
value: `${ctrl} + G`
|
||||
},
|
||||
{
|
||||
icon: 'iconzhankai',
|
||||
name: '展開/收合節點',
|
||||
value: '/'
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchu',
|
||||
name: '刪除節點',
|
||||
value: 'Delete | Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchu',
|
||||
name: '僅刪除目前節點',
|
||||
value: 'Shift + Backspace'
|
||||
},
|
||||
{
|
||||
icon: 'iconbianji',
|
||||
name: '編輯節點',
|
||||
value: macFn + 'F2'
|
||||
},
|
||||
{
|
||||
icon: 'iconzhengli',
|
||||
name: '一鍵整理版面配置',
|
||||
value: `${ctrl} + R`
|
||||
},
|
||||
{
|
||||
icon: 'iconzuoyouduiqi',
|
||||
name: '一鍵同級節點對齊',
|
||||
value: `${ctrl} + E`
|
||||
},
|
||||
{
|
||||
icon: 'iconsousuo',
|
||||
name: '搜尋與取代',
|
||||
value: `${ctrl} + F`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: '編輯操作',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconhoutui-shi',
|
||||
name: '復原',
|
||||
value: `${ctrl} + Z`
|
||||
},
|
||||
{
|
||||
icon: 'iconqianjin1',
|
||||
name: '重做',
|
||||
value: `${ctrl} + Y`
|
||||
},
|
||||
{
|
||||
icon: 'iconhuanhang',
|
||||
name: '文字換行',
|
||||
value: `Shift + ${enter}`
|
||||
},
|
||||
{
|
||||
icon: 'iconcase',
|
||||
name: '增大字型大小',
|
||||
value: `${ctrl} + Shift + +`
|
||||
},
|
||||
{
|
||||
icon: 'iconcase',
|
||||
name: '减小字型大小',
|
||||
value: `${ctrl} + Shift + -`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitijiacu',
|
||||
name: '加粗',
|
||||
value: `${ctrl} + B`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitixieti',
|
||||
name: '斜體',
|
||||
value: `${ctrl} + I`
|
||||
},
|
||||
{
|
||||
icon: 'iconzitixiahuaxian',
|
||||
name: '添加下划线',
|
||||
value: `${ctrl} + U`
|
||||
},
|
||||
{
|
||||
icon: 'iconshanchuxian',
|
||||
name: '添加删除線',
|
||||
value: `${ctrl} + [`
|
||||
},
|
||||
{
|
||||
icon: 'iconfuzhi',
|
||||
name: '複製節點',
|
||||
value: `${ctrl} + C`
|
||||
},
|
||||
{
|
||||
icon: 'iconjianqie',
|
||||
name: '剪下節點',
|
||||
value: `${ctrl} + X`
|
||||
},
|
||||
{
|
||||
icon: 'iconniantie',
|
||||
name: '貼上節點',
|
||||
value: `${ctrl} + V`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanxuan',
|
||||
name: '全選',
|
||||
value: `${ctrl} + A`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanxuan',
|
||||
name: '多重選擇',
|
||||
|
|
@ -223,11 +377,31 @@ export const shortcutKeyList = [
|
|||
{
|
||||
type: '畫布操作',
|
||||
list: [
|
||||
{
|
||||
icon: 'iconfangda',
|
||||
name: '放大',
|
||||
value: `${ctrl} + +`
|
||||
},
|
||||
{
|
||||
icon: 'iconsuoxiao',
|
||||
name: '縮小',
|
||||
value: `${ctrl} + -`
|
||||
},
|
||||
{
|
||||
icon: 'iconfangda',
|
||||
name: '放大/縮小',
|
||||
value: `${ctrl} + 滑鼠滾輪`
|
||||
},
|
||||
{
|
||||
icon: 'icondingwei',
|
||||
name: '回到根節點',
|
||||
value: `${ctrl} + ${enter}`
|
||||
},
|
||||
{
|
||||
icon: 'iconquanping1',
|
||||
name: '適應畫布',
|
||||
value: `${ctrl} + Shift + I`
|
||||
},
|
||||
{
|
||||
icon: 'iconshubiaoyidong',
|
||||
name: '移動畫布',
|
||||
|
|
@ -258,26 +432,30 @@ export const shortcutKeyList = [
|
|||
name: '刪除節點',
|
||||
value: 'Delete'
|
||||
},
|
||||
{
|
||||
icon: 'icontianjiazijiedian',
|
||||
name: '插入子節點',
|
||||
value: 'Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: '插入同層節點',
|
||||
value: enter
|
||||
},
|
||||
{
|
||||
icon: 'icondodechild',
|
||||
name: '下移一層',
|
||||
value: 'Tab'
|
||||
},
|
||||
{
|
||||
icon: 'icondodeparent',
|
||||
name: '上移一層',
|
||||
value: 'Shift + Tab'
|
||||
},
|
||||
{
|
||||
icon: 'iconjiedian',
|
||||
name: '將光標後的內容創建為同級節點',
|
||||
value: `Ctrl + ${enter}`
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// 形狀列表
|
||||
export const shapeList = [
|
||||
{
|
||||
name: '矩形',
|
||||
|
|
@ -317,7 +495,6 @@ export const shapeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 側邊欄列表
|
||||
export const sidebarTriggerList = [
|
||||
{
|
||||
name: '節點樣式',
|
||||
|
|
@ -351,7 +528,6 @@ export const sidebarTriggerList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 下載類型列表
|
||||
export const downTypeList = [
|
||||
{
|
||||
name: '思緒檔案',
|
||||
|
|
@ -403,7 +579,6 @@ export const downTypeList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 背景渐变方向
|
||||
export const linearGradientDirList = [
|
||||
{
|
||||
name: '从左到右',
|
||||
|
|
@ -455,7 +630,6 @@ export const linearGradientDirList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 文本对齐方式
|
||||
export const alignList = [
|
||||
{
|
||||
name: '左對齊',
|
||||
|
|
@ -471,7 +645,6 @@ export const alignList = [
|
|||
}
|
||||
]
|
||||
|
||||
// 结构列表
|
||||
export const layoutGroupList = [
|
||||
{
|
||||
name: '邏輯結構圖',
|
||||
|
|
@ -491,7 +664,11 @@ export const layoutGroupList = [
|
|||
},
|
||||
{
|
||||
name: '時間軸',
|
||||
list: ['timeline', 'timeline2', 'verticalTimeline']
|
||||
list: [
|
||||
'timeline',
|
||||
'timeline2',
|
||||
'verticalTimeline'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '魚骨圖',
|
||||
|
|
@ -511,8 +688,6 @@ export const iconNameMap = {
|
|||
summary: '插入概要',
|
||||
associativeLine: '添加關聯線',
|
||||
formula: '插入數學公式',
|
||||
// "attachment": "插入附件",
|
||||
outerFrame: '添加外框',
|
||||
annotation: '添加標記',
|
||||
ai: 'AI一鍵生成'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default {
|
|||
associativeLineColor: 'Color',
|
||||
associativeLineActiveWidth: 'Active width',
|
||||
associativeLineActiveColor: 'Active color',
|
||||
rootStyle: 'Root Node',
|
||||
rootStyle: 'Root node connection style',
|
||||
associativeLineText: 'Associative line text',
|
||||
fontFamily: 'Font family',
|
||||
fontSize: 'Font size',
|
||||
|
|
@ -43,7 +43,7 @@ export default {
|
|||
outerFramePadding: 'Outer frame padding',
|
||||
associativeLineStyle: 'Associative line style',
|
||||
builtInBackgroundImage: 'Built in background image',
|
||||
baseStyleConfig: 'BasicStyleConfigurationForMindMapping'
|
||||
baseStyleConfig: 'Basic style configuration for mind mapping'
|
||||
},
|
||||
setting: {
|
||||
title: 'Setting',
|
||||
|
|
@ -53,7 +53,7 @@ export default {
|
|||
mousewheelAction: 'Mouse wheel behavior',
|
||||
zoomView: 'Zoom view',
|
||||
moveViewUpDown: 'Move view up and down',
|
||||
mousewheelZoomActionReverse: 'Mouse Wheel Zoom',
|
||||
mousewheelZoomActionReverse: 'Mouse wheel zoom',
|
||||
mousewheelZoomActionReverse1: 'Zoom out forward and zoom in back',
|
||||
mousewheelZoomActionReverse2: 'Zoom in forward and zoom out back',
|
||||
createNewNodeBehavior: 'Behavior of creating new node',
|
||||
|
|
@ -63,9 +63,6 @@ export default {
|
|||
openRealtimeRenderOnNodeTextEdit:
|
||||
'Enable real-time rendering effect for text editing',
|
||||
isShowScrollbar: 'Is show scrollbar',
|
||||
isUseHandDrawnLikeStyle: 'Is use hand drawn like style',
|
||||
isUseMomentum: 'Is open drag momentum',
|
||||
isUseSameNodeAlign: 'Is use same node align',
|
||||
openBlankMode: 'Is open blank mode of the demonstrate',
|
||||
watermark: 'Watermark',
|
||||
showWatermark: 'Is show watermark',
|
||||
|
|
@ -87,29 +84,38 @@ export default {
|
|||
'This operation will clear all historical modification records and modify the mind map data. Do you want to continue?',
|
||||
changeRichTextTip2: 'Do you want to switch to rich text mode?',
|
||||
changeRichTextTip3: 'Do you want to switch to non rich text mode?',
|
||||
enableDragImport:
|
||||
'Is it allowed to directly drag and drop files to the page for import',
|
||||
imgTextMargin: 'Node image and text margin',
|
||||
textContentMargin: 'Node contents margin',
|
||||
enableInheritAncestorLineStyle:
|
||||
'Node connection style inherits the style of ancestor nodes',
|
||||
enableAi: 'Is enable AI function',
|
||||
isShowBottomToolbar: 'Display the bottom toolbar',
|
||||
rootEnableUnExpand: 'Does the root node allow the collapse of subordinates',
|
||||
settingConfig: 'MindmapSetting',
|
||||
settingConfig: 'Mind map setting',
|
||||
title1: 'Node',
|
||||
title2: 'Edit',
|
||||
title3: 'Display',
|
||||
title4: 'Other'
|
||||
title4: 'Other',
|
||||
sameNodeAlignInitWidth: 'Align same level nodes with initial width',
|
||||
enableFreeDragTip:
|
||||
'After disabling the setting, if you want to restore the default layout, please right-click on the canvas and select the context menu option - [Organize Layout Automatically]',
|
||||
isUseSameNodeAlignTip1:
|
||||
'1. Node width will not automatically align after editing; manually click the canvas right-click menu - [Align Sibling Nodes Automatically]',
|
||||
isUseSameNodeAlignTip2:
|
||||
'2. After disabling the setting, if you want to restore default width, please right-click on the canvas and select the context menu option - [Remove Custom Width from All Nodes]',
|
||||
openRealtimeRenderOnNodeTextEditTip:
|
||||
'Enabling this may cause performance degradation; it is recommended to disable it when there are many nodes',
|
||||
openPerformanceTip:
|
||||
'If there are many nodes, try enabling performance mode to speed up rendering'
|
||||
},
|
||||
color: {
|
||||
moreColor: 'More color'
|
||||
},
|
||||
contextmenu: {
|
||||
insertSiblingNode: 'Insert sibling node',
|
||||
insertChildNode: 'Insert child node',
|
||||
insertParentNode: 'Insert parent node',
|
||||
insertSummary: 'Insert summary',
|
||||
insertNode: 'Insert node',
|
||||
insertSiblingNode: 'Sibling node',
|
||||
insertChildNode: 'Child node',
|
||||
insertParentNode: 'Parent node',
|
||||
insertSummary: 'Summary',
|
||||
moveUpNode: 'Move up node',
|
||||
moveDownNode: 'Move down node',
|
||||
deleteNode: 'Delete node',
|
||||
|
|
@ -157,7 +163,8 @@ export default {
|
|||
removeCustomWidth: 'Remove custom width',
|
||||
copyAsInternalUrl: 'Copy as internal url',
|
||||
removeAllCustomWidth: 'One click removal of custom width for all nodes',
|
||||
download: 'Download to local'
|
||||
download: 'Download to local',
|
||||
copyNodeToTxt: 'Copy as plain text'
|
||||
},
|
||||
count: {
|
||||
words: 'Words',
|
||||
|
|
@ -172,11 +179,11 @@ export default {
|
|||
filename: 'Filename',
|
||||
include: 'Is include config like theme and structure',
|
||||
dedicatedFile: 'Dedicated file',
|
||||
jsonFile: 'json file',
|
||||
jsonFile: 'Json file',
|
||||
imageFile: 'Image file',
|
||||
svgFile: 'svg file',
|
||||
pdfFile: 'pdf file',
|
||||
markdownFile: 'markdown file',
|
||||
svgFile: 'Svg file',
|
||||
pdfFile: 'Pdf file',
|
||||
markdownFile: 'Markdown file',
|
||||
isTransparent: 'Background is transparent',
|
||||
transformingDomToImages: 'Converting nodes: ',
|
||||
notifyTitle: 'Info',
|
||||
|
|
@ -214,16 +221,19 @@ export default {
|
|||
importSuccess: 'Import success',
|
||||
fileParsingFailed: 'File parsing failed',
|
||||
xmindCanvasSelectDialogTitle: 'Select the canvas to import',
|
||||
mdImportDialogTitle: 'Paste Markdown content to import',
|
||||
mdPlaceholder: 'Please enter the content in Markdown format',
|
||||
mdImportDialogTitle: 'Paste markdown content to import',
|
||||
mdPlaceholder: 'Please enter the content in markdown format',
|
||||
mdEmptyTip: 'The content cannot be empty',
|
||||
mdImportFailTip: 'Import failed, no content was parsed for import. Only supports syntax such as titles and lists',
|
||||
rootNodeName: 'Root node'
|
||||
rootNodeName: 'Root node',
|
||||
importTip: 'Among them, .mm and .xlsx members are available',
|
||||
dragTip: 'Drag warehouse or local computer files here',
|
||||
warningTip: 'Attention: Importing will overwrite existing content and cannot be restored. Please make a backup first!'
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: 'Open mini map',
|
||||
closeMiniMap: 'Close mini map',
|
||||
readonly: 'Change to Readonly',
|
||||
readonly: 'Change to readonly',
|
||||
edit: 'Change to edit',
|
||||
backToRoot: 'Back to root node',
|
||||
changeSourceCodeEdit: 'Switch to source code editing mode',
|
||||
|
|
@ -236,7 +246,7 @@ export default {
|
|||
searchReplace: 'Search for replacement',
|
||||
exitDarkMode: 'Exit dark mode',
|
||||
darkMode: 'Switch to dark mode',
|
||||
darkModeFailTip: 'Switching failed, currently following Obsidian mode',
|
||||
darkModeFailTip: 'Switching failed, currently following obsidian mode',
|
||||
export: 'Export',
|
||||
help: 'Help'
|
||||
},
|
||||
|
|
@ -254,7 +264,7 @@ export default {
|
|||
tip1: 'Please enter the link address',
|
||||
tip2: 'Insertion failed, file does not exist',
|
||||
tip3: 'Please select a local file',
|
||||
tip4: 'File upload to Vault failed',
|
||||
tip4: 'File upload to vault failed',
|
||||
tip5: 'Insertion failed'
|
||||
},
|
||||
nodeIcon: {
|
||||
|
|
@ -270,7 +280,7 @@ export default {
|
|||
tip2: 'Please enter the image address',
|
||||
optional: 'Optional',
|
||||
selectLocal: 'Select locally',
|
||||
selectVault: 'Select from Vault',
|
||||
selectVault: 'Select from vault',
|
||||
selectUrl: 'Network images',
|
||||
imgTitle: 'Image title (optional)'
|
||||
},
|
||||
|
|
@ -279,7 +289,8 @@ export default {
|
|||
},
|
||||
nodeTag: {
|
||||
title: 'Tag',
|
||||
addTip: 'Please press Enter to add, up to five labels can be added'
|
||||
addTip: 'Please press Enter to add, up to five labels can be added',
|
||||
add: 'Add'
|
||||
},
|
||||
outline: {
|
||||
title: 'Outline',
|
||||
|
|
@ -287,7 +298,7 @@ export default {
|
|||
print: 'Print',
|
||||
fullscreen: 'Fullscreen',
|
||||
tip1: 'Insert sibling node:',
|
||||
tip2: 'Insert child node:',
|
||||
tip2: 'Node Downward Level:',
|
||||
tip3: 'Node up hierarchy:',
|
||||
tip4: 'Delete node:',
|
||||
tip5: 'Press and hold the node to drag and drop'
|
||||
|
|
@ -297,8 +308,7 @@ export default {
|
|||
zoomOut: 'Zoom out'
|
||||
},
|
||||
shortcutKey: {
|
||||
title: 'Shortcut key',
|
||||
tip: 'For other operations, please use commands, or you can set shortcut keys for commands yourself'
|
||||
title: 'Shortcut key'
|
||||
},
|
||||
strusture: {
|
||||
title: 'Strusture'
|
||||
|
|
@ -435,11 +445,14 @@ export default {
|
|||
savingTip3: 'Successfully saved',
|
||||
savingTip4: 'Save failed',
|
||||
defaultInsertSecondLevelNodeText: 'Secondary node',
|
||||
defaultInsertBelowSecondLevelNodeText: 'Branch Topic',
|
||||
defaultInsertBelowSecondLevelNodeText: 'Branch topic',
|
||||
defaultGeneralizationText: 'Summary',
|
||||
defaultAssociativeLineText: 'Association',
|
||||
defaultOuterFrameText: 'Outer frame',
|
||||
pasteImage: 'Paste-image'
|
||||
pasteImage: 'Paste-image',
|
||||
notSplit: 'Not split',
|
||||
keepLevel: 'Keep level',
|
||||
ignoreLevel: 'Ignore level'
|
||||
},
|
||||
mouseAction: {
|
||||
tip1:
|
||||
|
|
@ -476,7 +489,7 @@ export default {
|
|||
fontSize: 'Font size',
|
||||
color: 'Color',
|
||||
backgroundColor: 'Background color',
|
||||
removeFormat: 'Clear Style',
|
||||
removeFormat: 'Clear style',
|
||||
textAlign: 'Text align'
|
||||
},
|
||||
other: {
|
||||
|
|
@ -497,15 +510,6 @@ export default {
|
|||
deleteAttachment: 'Delete attachment',
|
||||
tip: 'The attachment function is only available on the client side'
|
||||
},
|
||||
annotation: {
|
||||
mark: 'Mark',
|
||||
show: 'Show mark',
|
||||
type: 'Type',
|
||||
color: 'Color',
|
||||
lineWidth: 'Line width',
|
||||
padding: 'Padding',
|
||||
animate: 'Animate'
|
||||
},
|
||||
nodeOuterFrame: {
|
||||
outerFrameSetting: 'Outer frame setting',
|
||||
deleteOuterFrame: 'Delete outer frame',
|
||||
|
|
@ -610,7 +614,7 @@ export default {
|
|||
tip5: 'The link node does not exist. Weather to delete the link?'
|
||||
},
|
||||
ciod: {
|
||||
outputConfig: 'Export Configuration',
|
||||
outputConfig: 'Export configuration',
|
||||
importConfig: 'Import configuration',
|
||||
tip1: 'Please select a JSON file',
|
||||
tip2: 'The file content format is incorrect',
|
||||
|
|
@ -635,7 +639,7 @@ export default {
|
|||
tip5: 'Here are some auxiliary functions (mini map, demo mode...)'
|
||||
},
|
||||
previewMode: {
|
||||
emptyTitle: 'No renderable data parsed',
|
||||
emptyTitle: 'No render able data parsed',
|
||||
emptyDesc:
|
||||
'Currently, only partial markdown syntax can be parsed, such as titles and lists',
|
||||
close: 'Close'
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default {
|
|||
associativeLineColor: 'Màu sắc',
|
||||
associativeLineActiveWidth: 'Độ rộng khi kích hoạt',
|
||||
associativeLineActiveColor: 'Màu sắc khi kích hoạt',
|
||||
rootStyle: 'Nút gốc',
|
||||
rootStyle: 'Kiểu kết nối nút gốc',
|
||||
associativeLineText: 'Văn bản đường liên kết',
|
||||
fontFamily: 'Phông chữ',
|
||||
fontSize: 'Cỡ chữ',
|
||||
|
|
@ -63,9 +63,6 @@ export default {
|
|||
openRealtimeRenderOnNodeTextEdit:
|
||||
'Bật hiệu ứng kết xuất thời gian thực cho chỉnh sửa văn bản',
|
||||
isShowScrollbar: 'Hiển thị thanh cuộn',
|
||||
isUseHandDrawnLikeStyle: 'Sử dụng kiểu vẽ tay',
|
||||
isUseMomentum: 'Mở động lượng kéo',
|
||||
isUseSameNodeAlign: 'Sử dụng căn chỉnh nút cùng cấp',
|
||||
openBlankMode: 'Mở chế độ trống cho trình diễn',
|
||||
watermark: 'Hình mờ',
|
||||
showWatermark: 'Hiển thị hình mờ',
|
||||
|
|
@ -89,12 +86,10 @@ export default {
|
|||
changeRichTextTip2: 'Bạn có muốn chuyển sang chế độ văn bản phong phú?',
|
||||
changeRichTextTip3:
|
||||
'Bạn có muốn chuyển sang chế độ không phải văn bản phong phú?',
|
||||
enableDragImport: 'Cho phép kéo và thả tệp trực tiếp vào trang để nhập',
|
||||
imgTextMargin: 'Khoảng cách giữa hình ảnh và văn bản nút',
|
||||
textContentMargin: 'Khoảng cách nội dung nút',
|
||||
enableInheritAncestorLineStyle:
|
||||
'Kiểu kết nối nút kế thừa kiểu của nút tổ tiên',
|
||||
enableAi: 'Bật chức năng AI',
|
||||
isShowBottomToolbar: 'Hiện thanh công cụ dưới cùng',
|
||||
rootEnableUnExpand:
|
||||
'Liệu root node có cho phép thu hồi cấp dưới hay không?',
|
||||
|
|
@ -102,16 +97,28 @@ export default {
|
|||
title1: 'Nút',
|
||||
title2: 'Chỉnh sửa',
|
||||
title3: 'Hiển thị',
|
||||
title4: 'Khác'
|
||||
title4: 'Khác',
|
||||
sameNodeAlignInitWidth: 'Các nút cùng cấp căn chỉnh chiều rộng ban đầu',
|
||||
enableFreeDragTip:
|
||||
'Sau khi tắt cài đặt, nếu muốn khôi phục bố cục mặc định, vui lòng nhấp chuột phải vào vùng vẽ và chọn menu - [Tổ chức lại bố cục tự động]',
|
||||
isUseSameNodeAlignTip1:
|
||||
'1. Sau khi chỉnh sửa nút, chiều rộng sẽ không tự động căn chỉnh; cần thủ công nhấp vào menu chuột phải vùng vẽ - [Căn chỉnh tự động các nút cùng cấp]',
|
||||
isUseSameNodeAlignTip2:
|
||||
'2. Sau khi tắt cài đặt, nếu muốn khôi phục chiều rộng mặc định, vui lòng nhấp chuột phải vào vùng vẽ và chọn menu - [Xóa toàn bộ chiều rộng tùy chỉnh của các nút]',
|
||||
openRealtimeRenderOnNodeTextEditTip:
|
||||
'Bật tính năng này có thể gây giảm hiệu suất; khuyến nghị tắt khi số lượng nút nhiều',
|
||||
openPerformanceTip:
|
||||
'Nếu số lượng nút nhiều, có thể thử bật chế độ hiệu suất để tăng tốc độ hiển thị'
|
||||
},
|
||||
color: {
|
||||
moreColor: 'Thêm màu'
|
||||
},
|
||||
contextmenu: {
|
||||
insertSiblingNode: 'Chèn nút cùng cấp',
|
||||
insertChildNode: 'Chèn nút con',
|
||||
insertParentNode: 'Chèn nút cha',
|
||||
insertSummary: 'Chèn tóm tắt',
|
||||
insertNode: 'Chèn nút',
|
||||
insertSiblingNode: 'Node tương đương',
|
||||
insertChildNode: 'Các nút con',
|
||||
insertParentNode: 'Nút cha',
|
||||
insertSummary: 'Giới thiệu',
|
||||
moveUpNode: 'Di chuyển nút lên',
|
||||
moveDownNode: 'Di chuyển nút xuống',
|
||||
deleteNode: 'Xóa nút',
|
||||
|
|
@ -160,7 +167,8 @@ export default {
|
|||
copyAsInternalUrl: 'Sao chép như một chuỗi bên trong',
|
||||
removeAllCustomWidth:
|
||||
'Loại bỏ tất cả các nút chiều rộng tùy chỉnh với một cú nhấp chuột',
|
||||
download: 'Tải xuống'
|
||||
download: 'Tải xuống',
|
||||
copyNodeToTxt: 'Sao chép thành văn bản thuần túy'
|
||||
},
|
||||
count: {
|
||||
words: 'Từ',
|
||||
|
|
@ -219,7 +227,10 @@ export default {
|
|||
mdPlaceholder: 'Vui lòng nhập nội dung ở định dạng Markdown',
|
||||
mdEmptyTip: 'Nội dung không được trống',
|
||||
mdImportFailTip: 'Nhập thất bại, không phân tích cú pháp để nhập nội dung. Chỉ hỗ trợ cú pháp như tiêu đề, danh sách, v.v.',
|
||||
rootNodeName: 'Nút gốc'
|
||||
rootNodeName: 'Nút gốc',
|
||||
importTip: 'Trong đó .mm, .xlsx thành viên có sẵn',
|
||||
dragTip: 'Kéo tập tin cục bộ kho hoặc máy tính đến đây',
|
||||
warningTip: 'Lưu ý: Việc nhập sẽ ghi đè lên nội dung hiện có và không thể khôi phục, hãy sao lưu trước!'
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: 'Mở bản đồ thu nhỏ',
|
||||
|
|
@ -281,7 +292,8 @@ export default {
|
|||
},
|
||||
nodeTag: {
|
||||
title: 'Thẻ',
|
||||
addTip: 'Vui lòng nhấn Enter để thêm tối đa 5 tab'
|
||||
addTip: 'Vui lòng nhấn Enter để thêm tối đa 5 tab',
|
||||
add: 'Thêm'
|
||||
},
|
||||
outline: {
|
||||
title: 'Dàn bài',
|
||||
|
|
@ -289,7 +301,7 @@ export default {
|
|||
print: 'In',
|
||||
fullscreen: 'Toàn màn hình',
|
||||
tip1: 'Chèn nút huynh đệ:',
|
||||
tip2: 'Chèn nút con:',
|
||||
tip2: 'Nút xuống cấp:',
|
||||
tip3: 'Nâng cấp nút:',
|
||||
tip4: 'Xóa nút:',
|
||||
tip5: 'Giữ nút để kéo'
|
||||
|
|
@ -299,8 +311,7 @@ export default {
|
|||
zoomOut: 'Thu nhỏ'
|
||||
},
|
||||
shortcutKey: {
|
||||
title: 'Phím tắt',
|
||||
tip: 'Các thao tác khác xin vui lòng thông qua lệnh, cũng có thể đặt lối tắt cho lệnh của bạn'
|
||||
title: 'Phím tắt'
|
||||
},
|
||||
strusture: {
|
||||
title: 'Cấu trúc'
|
||||
|
|
@ -438,7 +449,10 @@ export default {
|
|||
defaultGeneralizationText: 'Giới thiệu',
|
||||
defaultAssociativeLineText: 'Liên kết',
|
||||
defaultOuterFrameText: 'Khung ngoài',
|
||||
pasteImage: 'Dán-hình-ảnh'
|
||||
pasteImage: 'Dán-hình-ảnh',
|
||||
notSplit: 'Không phân chia',
|
||||
keepLevel: 'Cấp độ dự trữ',
|
||||
ignoreLevel: 'Bỏ qua phân cấp'
|
||||
},
|
||||
mouseAction: {
|
||||
tip1:
|
||||
|
|
@ -497,15 +511,6 @@ export default {
|
|||
deleteAttachment: 'Xóa đính kèm',
|
||||
tip: 'Chức năng đính kèm chỉ có sẵn ở phía máy khách'
|
||||
},
|
||||
annotation: {
|
||||
mark: 'Đánh dấu',
|
||||
show: 'Hiển thị đánh dấu',
|
||||
type: 'Loại',
|
||||
color: 'Màu sắc',
|
||||
lineWidth: 'Độ rộng đường kẻ',
|
||||
padding: 'Đệm',
|
||||
animate: 'Hoạt ảnh'
|
||||
},
|
||||
nodeOuterFrame: {
|
||||
outerFrameSetting: 'Cài đặt khung ngoài',
|
||||
deleteOuterFrame: 'Xóa khung ngoài',
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default {
|
|||
associativeLineColor: '颜色',
|
||||
associativeLineActiveWidth: '激活粗细',
|
||||
associativeLineActiveColor: '激活颜色',
|
||||
rootStyle: '根节点',
|
||||
rootStyle: '根节点连线风格',
|
||||
associativeLineText: '关联线文字',
|
||||
fontFamily: '字体',
|
||||
fontSize: '字号',
|
||||
|
|
@ -61,9 +61,6 @@ export default {
|
|||
activeOnly: '只激活新节点,不进入编辑',
|
||||
openRealtimeRenderOnNodeTextEdit: '开启文本编辑实时渲染效果',
|
||||
isShowScrollbar: '显示滚动条',
|
||||
isUseHandDrawnLikeStyle: '开启手绘风格',
|
||||
isUseMomentum: '开启拖动画布的动量效果',
|
||||
isUseSameNodeAlign: '开启同级节点对齐',
|
||||
openBlankMode: '开启演示模式的填空功能',
|
||||
watermark: '水印',
|
||||
showWatermark: '显示水印',
|
||||
|
|
@ -86,26 +83,35 @@ export default {
|
|||
'该操作会清空所有历史修改记录,并且修改思维导图数据,是否继续?',
|
||||
changeRichTextTip2: '是否切换为富文本模式?',
|
||||
changeRichTextTip3: '是否切换为非富文本模式?',
|
||||
enableDragImport: '允许直接拖拽文件到页面进行导入',
|
||||
imgTextMargin: '节点图片和文本间隔',
|
||||
textContentMargin: '节点各种内容间隔',
|
||||
enableAi: '显示AI功能入口',
|
||||
isShowBottomToolbar: '显示底部工具栏',
|
||||
rootEnableUnExpand: '根节点允许收起下级',
|
||||
settingConfig: '思绪思维导图设置',
|
||||
title1: '节点',
|
||||
title2: '编辑',
|
||||
title3: '显示',
|
||||
title4: '其他'
|
||||
title4: '其他',
|
||||
sameNodeAlignInitWidth: '同级节点对齐初始宽度',
|
||||
enableFreeDragTip:
|
||||
'关闭设置后,如果要恢复默认布局,请点击画布右键菜单-【一键整理布局】',
|
||||
isUseSameNodeAlignTip1:
|
||||
'1.节点编辑后宽度不会自动对齐,需手动点击画布右键菜单-【一键同级节点对齐】',
|
||||
isUseSameNodeAlignTip2:
|
||||
'2.关闭设置后,如果要恢复默认宽度,请点击画布右键菜单-【一键去除所有节点自定义宽度】',
|
||||
openRealtimeRenderOnNodeTextEditTip:
|
||||
'开启后可能会导致性能下降,建议在节点数量比较多时关闭',
|
||||
openPerformanceTip: '如果节点数量比较多,可以尝试开启性能模式加快渲染'
|
||||
},
|
||||
color: {
|
||||
moreColor: '更多颜色'
|
||||
},
|
||||
contextmenu: {
|
||||
insertSiblingNode: '插入同级节点',
|
||||
insertChildNode: '插入子级节点',
|
||||
insertParentNode: '插入父节点',
|
||||
insertSummary: '插入概要',
|
||||
insertNode: '插入节点',
|
||||
insertSiblingNode: '同级节点',
|
||||
insertChildNode: '子节点',
|
||||
insertParentNode: '父节点',
|
||||
insertSummary: '概要',
|
||||
moveUpNode: '上移节点',
|
||||
moveDownNode: '下移节点',
|
||||
deleteNode: '删除节点',
|
||||
|
|
@ -153,7 +159,8 @@ export default {
|
|||
removeCustomWidth: '去除自定义宽度',
|
||||
copyAsInternalUrl: '复制为ob内链',
|
||||
removeAllCustomWidth: '一键去除所有节点自定义宽度',
|
||||
download: '下载到本地'
|
||||
download: '下载到本地',
|
||||
copyNodeToTxt: '复制为纯文本'
|
||||
},
|
||||
count: {
|
||||
words: '字数',
|
||||
|
|
@ -212,7 +219,10 @@ export default {
|
|||
mdPlaceholder: '请输入Markdown格式的内容',
|
||||
mdEmptyTip: '内容不能为空',
|
||||
mdImportFailTip: '导入失败,没有解析到可导入内容。只支持标题、列表等语法',
|
||||
rootNodeName: '根节点'
|
||||
rootNodeName: '根节点',
|
||||
importTip: '其中.mm、.xlsx会员可用',
|
||||
dragTip: '拖动仓库或电脑本地文件到此',
|
||||
warningTip: '注意:导入会覆盖现有内容,且无法恢复,请先做好备份!'
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: '开启小地图',
|
||||
|
|
@ -273,7 +283,8 @@ export default {
|
|||
},
|
||||
nodeTag: {
|
||||
title: '标签',
|
||||
addTip: '请按回车键添加,最多可添加五个标签'
|
||||
addTip: '请按回车键添加,最多可添加五个标签',
|
||||
add: '添加'
|
||||
},
|
||||
outline: {
|
||||
title: '大纲',
|
||||
|
|
@ -281,7 +292,7 @@ export default {
|
|||
print: '打印',
|
||||
fullscreen: '全屏',
|
||||
tip1: '插入兄弟节点:',
|
||||
tip2: '插入子节点:',
|
||||
tip2: '节点下移层级:',
|
||||
tip3: '节点上移层级:',
|
||||
tip4: '删除节点:',
|
||||
tip5: '按住节点可拖拽'
|
||||
|
|
@ -291,8 +302,7 @@ export default {
|
|||
zoomOut: '缩小'
|
||||
},
|
||||
shortcutKey: {
|
||||
title: '快捷键',
|
||||
tip: '其他操作请通过命令,也可自行设置命令的快捷键'
|
||||
title: '快捷键'
|
||||
},
|
||||
strusture: {
|
||||
title: '结构'
|
||||
|
|
@ -425,7 +435,10 @@ export default {
|
|||
defaultGeneralizationText: '概要',
|
||||
defaultAssociativeLineText: '关联',
|
||||
defaultOuterFrameText: '外框',
|
||||
pasteImage: '粘贴图片'
|
||||
pasteImage: '粘贴图片',
|
||||
notSplit: '不分割',
|
||||
keepLevel: '保留层级',
|
||||
ignoreLevel: '忽略层级'
|
||||
},
|
||||
mouseAction: {
|
||||
tip1: '当前:左键拖动画布,右键框选节点。点击可切换',
|
||||
|
|
@ -481,15 +494,6 @@ export default {
|
|||
deleteAttachment: '删除附件',
|
||||
tip: '附件功能仅在客户端可用'
|
||||
},
|
||||
annotation: {
|
||||
mark: '标记',
|
||||
show: '显示标记',
|
||||
type: '类型',
|
||||
color: '颜色',
|
||||
lineWidth: '线宽',
|
||||
padding: '内边距',
|
||||
animate: '开启动画'
|
||||
},
|
||||
nodeOuterFrame: {
|
||||
nodeOuterFrameStyle: '外框样式',
|
||||
outerFrameSetting: '外框',
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default {
|
|||
associativeLineColor: '顏色',
|
||||
associativeLineActiveWidth: '啟用時寬度',
|
||||
associativeLineActiveColor: '啟用時顏色',
|
||||
rootStyle: '根節點',
|
||||
rootStyle: '根節點連線風格',
|
||||
associativeLineText: '關聯線文字',
|
||||
fontFamily: '字型',
|
||||
fontSize: '字型大小',
|
||||
|
|
@ -62,9 +62,6 @@ export default {
|
|||
activeOnly: '僅啟用新節點,不進入編輯',
|
||||
openRealtimeRenderOnNodeTextEdit: '開啟文本編輯實時渲染效果',
|
||||
isShowScrollbar: '顯示捲軸',
|
||||
isUseHandDrawnLikeStyle: '使用手繪風格',
|
||||
isUseMomentum: '是否開啓拖動畫布的動量效果',
|
||||
isUseSameNodeAlign: '是否使用相同節點對齊',
|
||||
openBlankMode: '是否開啓演示模式的填空功能',
|
||||
watermark: '浮水印',
|
||||
showWatermark: '顯示浮水印',
|
||||
|
|
@ -84,29 +81,38 @@ export default {
|
|||
'該操作會清空所有曆史修改記錄,並且修改思維導圖數據,是否繼續?',
|
||||
changeRichTextTip2: '是否切換爲富文本模式?',
|
||||
changeRichTextTip3: '是否切換爲非富文本模式?',
|
||||
enableDragImport: '是否允許直接拖拽文件到頁面進行導入',
|
||||
imgTextMargin: '節點圖片和文本間隔',
|
||||
textContentMargin: '節點各種內容間隔',
|
||||
enableAutoEnterTextEditWhenKeydown: '鍵盤輸入時自動進入文本編輯',
|
||||
enableInheritAncestorLineStyle: '節點連線樣式繼承祖先節點的樣式',
|
||||
alwaysShowExpandBtn: '是否壹直顯示展開收起按鈕',
|
||||
enableAi: '是否開啓AI功能',
|
||||
isShowBottomToolbar: '顯示底部工具列',
|
||||
rootEnableUnExpand: '根節點是否允許收起下級',
|
||||
settingConfig: '思緒心智圖設定',
|
||||
title1: '節點',
|
||||
title2: '編輯',
|
||||
title3: '顯示',
|
||||
title4: '其他'
|
||||
title4: '其他',
|
||||
sameNodeAlignInitWidth: '同級節點對齊初始寬度',
|
||||
enableFreeDragTip:
|
||||
'關閉設定後,如果要恢復預設佈局,請點擊畫布右鍵選單-【一鍵整理佈局】',
|
||||
isUseSameNodeAlignTip1:
|
||||
'1.節點編輯後寬度不會自動對齊,需手動點擊畫布右鍵選單-【一鍵同級節點對齊】',
|
||||
isUseSameNodeAlignTip2:
|
||||
'2.關閉設定後,如果要恢復預設寬度,請點擊畫布右鍵選單-【一鍵去除所有節點自訂寬度】',
|
||||
openRealtimeRenderOnNodeTextEditTip:
|
||||
'開啟後可能會導致效能下降,建議在節點數量比較多時關閉',
|
||||
openPerformanceTip: '如果節點數量比較多,可以嘗試開啟效能模式加快渲染'
|
||||
},
|
||||
color: {
|
||||
moreColor: '更多顏色'
|
||||
},
|
||||
contextmenu: {
|
||||
insertSiblingNode: '插入同層節點',
|
||||
insertChildNode: '插入子節點',
|
||||
insertParentNode: '插入父節點',
|
||||
insertSummary: '插入概要',
|
||||
insertNode: '插入節點',
|
||||
insertSiblingNode: '同層節點',
|
||||
insertChildNode: '子節點',
|
||||
insertParentNode: '父節點',
|
||||
insertSummary: '概要',
|
||||
moveUpNode: '上移節點',
|
||||
moveDownNode: '下移節點',
|
||||
deleteNode: '刪除節點',
|
||||
|
|
@ -154,7 +160,8 @@ export default {
|
|||
removeCustomWidth: '去除自定義寬度',
|
||||
copyAsInternalUrl: '複製為內鏈',
|
||||
removeAllCustomWidth: '一鍵去除所有節點自定義寬度',
|
||||
download: '下載到本地'
|
||||
download: '下載到本地',
|
||||
copyNodeToTxt: '複製為純文字'
|
||||
},
|
||||
count: {
|
||||
words: '字數',
|
||||
|
|
@ -213,7 +220,10 @@ export default {
|
|||
mdPlaceholder: '請輸入Markdown格式的內容',
|
||||
mdEmptyTip: '內容不能爲空',
|
||||
mdImportFailTip: '導入失敗,沒有解析到可導入內容。 只支持標題、清單等語法',
|
||||
rootNodeName: '根節點'
|
||||
rootNodeName: '根節點',
|
||||
importTip: '其中.mm、.xlsx會員可用',
|
||||
dragTip: '拖動倉庫或電腦本地檔案到此',
|
||||
warningTip: '注意:導入會覆蓋現有內容,且無法恢復,請先做好備份!'
|
||||
},
|
||||
navigatorToolbar: {
|
||||
openMiniMap: '開啟小地圖',
|
||||
|
|
@ -274,7 +284,8 @@ export default {
|
|||
},
|
||||
nodeTag: {
|
||||
title: '標籤',
|
||||
addTip: '請按回車鍵添加,最多可添加五個標籤'
|
||||
addTip: '請按回車鍵添加,最多可添加五個標籤',
|
||||
add: '添加'
|
||||
},
|
||||
outline: {
|
||||
title: '大綱',
|
||||
|
|
@ -282,7 +293,7 @@ export default {
|
|||
print: '打印',
|
||||
fullscreen: '全屏',
|
||||
tip1: '插入兄弟節點:',
|
||||
tip2: '插入子節點:',
|
||||
tip2: '節點下移層級:',
|
||||
tip3: '節點上移層級:',
|
||||
tip4: '删除節點:',
|
||||
tip5: '按住節點可拖拽'
|
||||
|
|
@ -292,8 +303,7 @@ export default {
|
|||
zoomOut: '縮小'
|
||||
},
|
||||
shortcutKey: {
|
||||
title: '快速鍵',
|
||||
tip: '其他操作請通過命令,也可自行設定命令的快速鍵'
|
||||
title: '快速鍵'
|
||||
},
|
||||
strusture: {
|
||||
title: '結構'
|
||||
|
|
@ -425,7 +435,10 @@ export default {
|
|||
defaultGeneralizationText: '概要',
|
||||
defaultAssociativeLineText: '關聯',
|
||||
defaultOuterFrameText: '外框',
|
||||
pasteImage: '粘貼圖片'
|
||||
pasteImage: '粘貼圖片',
|
||||
notSplit: '不分割',
|
||||
keepLevel: '保留層級',
|
||||
ignoreLevel: '忽略層級'
|
||||
},
|
||||
mouseAction: {
|
||||
tip1: '目前:左鍵拖曳畫布,右鍵框選節點。點擊可切換',
|
||||
|
|
@ -481,15 +494,6 @@ export default {
|
|||
deleteAttachment: '刪除附件',
|
||||
tip: '附件功能僅在用戶端可用'
|
||||
},
|
||||
annotation: {
|
||||
mark: '標記',
|
||||
show: '顯示標記',
|
||||
type: '類型',
|
||||
color: '顏色',
|
||||
lineWidth: '線寬',
|
||||
padding: '內距',
|
||||
animate: '動畫'
|
||||
},
|
||||
nodeOuterFrame: {
|
||||
outerFrameSetting: '外框',
|
||||
deleteOuterFrame: '刪除外框',
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import '@/assets/icon-font/iconfont.css'
|
|||
import 'viewerjs/dist/viewer.css'
|
||||
import VueViewer from 'v-viewer'
|
||||
import i18n from './i18n'
|
||||
import PreviewMode from './pages/PreviewMode/Index.vue'
|
||||
import '@/style/common.less'
|
||||
import '@/style/sidebar.less'
|
||||
import '@toast-ui/editor/dist/toastui-editor.css'
|
||||
import '@toast-ui/editor/dist/theme/toastui-editor-dark.css'
|
||||
import PreviewMode from './pages/PreviewMode/Index.vue'
|
||||
import {
|
||||
Dialog,
|
||||
Dropdown,
|
||||
|
|
@ -33,7 +33,8 @@ import {
|
|||
Tree,
|
||||
InputNumber,
|
||||
MessageBox,
|
||||
Loading
|
||||
Loading,
|
||||
Switch
|
||||
} from 'element-ui'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
|
@ -58,11 +59,11 @@ Vue.use(Popover)
|
|||
Vue.use(Dropdown)
|
||||
Vue.use(DropdownMenu)
|
||||
Vue.use(DropdownItem)
|
||||
Vue.use(Switch)
|
||||
Vue.use(Loading.directive)
|
||||
Vue.prototype.$loading = Loading.service
|
||||
Vue.prototype.$confirm = MessageBox.confirm
|
||||
|
||||
// 初始化独立窗口编辑应用
|
||||
export const initApp = (el, obsidianAPI = {}) => {
|
||||
i18n.locale = obsidianAPI.getSettings().lang || 'en'
|
||||
const app = new Vue({
|
||||
|
|
@ -71,9 +72,7 @@ export const initApp = (el, obsidianAPI = {}) => {
|
|||
i18n
|
||||
})
|
||||
const bus = new Vue()
|
||||
// 注册全局事件总线
|
||||
app.$bus = bus
|
||||
// 派发更新思维导图尺寸事件
|
||||
let resizeTimer = null
|
||||
app.$updateMindMapSize = () => {
|
||||
clearTimeout(resizeTimer)
|
||||
|
|
@ -85,7 +84,6 @@ export const initApp = (el, obsidianAPI = {}) => {
|
|||
clearTimeout(resizeTimer)
|
||||
resizeTimer = null
|
||||
}
|
||||
// 挂载一些API
|
||||
app.$obsidianAPI = obsidianAPI
|
||||
app.$mount(el)
|
||||
return app
|
||||
|
|
@ -99,9 +97,7 @@ const initCommonApp = (el, obsidianAPI = {}, component) => {
|
|||
i18n
|
||||
})
|
||||
const bus = new Vue()
|
||||
// 注册全局事件总线
|
||||
app.$bus = bus
|
||||
// 挂载一些API
|
||||
app.$obsidianAPI = obsidianAPI
|
||||
app.$mount(el)
|
||||
return app
|
||||
|
|
|
|||
9
plugin/src/mixins/fontFamily.js
Normal file
9
plugin/src/mixins/fontFamily.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { fontFamilyList } from '@/config'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
fontFamilyList() {
|
||||
return [...(fontFamilyList[this.$i18n.locale] || fontFamilyList.en)]
|
||||
}
|
||||
}
|
||||
}
|
||||
63
plugin/src/mixins/imgHostingUpload.js
Normal file
63
plugin/src/mixins/imgHostingUpload.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { compressImage } from '@/utils'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
async compressAndUploadImg(file) {
|
||||
const {
|
||||
compressImage: isCompress,
|
||||
compressImageOptionsMaxWidth,
|
||||
compressImageOptionsMaxHeight,
|
||||
compressImageOptionsQuality,
|
||||
useImgHosting
|
||||
} = this.$root.$obsidianAPI.getSettings()
|
||||
if (isCompress) {
|
||||
file = await compressImage(file, {
|
||||
exportType: 'file',
|
||||
maxWidth: compressImageOptionsMaxWidth,
|
||||
maxHeight: compressImageOptionsMaxHeight,
|
||||
quality: compressImageOptionsQuality
|
||||
})
|
||||
}
|
||||
let result = ''
|
||||
if (useImgHosting) {
|
||||
result = await this.uploadImgToHosting(file)
|
||||
} else {
|
||||
result = await this.$root.$obsidianAPI.saveFileToVault(file)
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
uploadImgToHosting(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const {
|
||||
imgHostingUrl,
|
||||
imgHostingFormField,
|
||||
imgHostingResponseField
|
||||
} = this.$root.$obsidianAPI.getSettings()
|
||||
const formData = new FormData()
|
||||
formData.append(imgHostingFormField, file)
|
||||
fetch(imgHostingUrl, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data) {
|
||||
const imageUrl = eval(imgHostingResponseField)
|
||||
if (imageUrl) {
|
||||
resolve(imageUrl)
|
||||
} else {
|
||||
reject(new Error('上传图片失败,未从响应中提取到图片URL'))
|
||||
}
|
||||
} else {
|
||||
reject(new Error('上传图片失败'))
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
reject('上传失败')
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
plugin/src/pages/.DS_Store
vendored
BIN
plugin/src/pages/.DS_Store
vendored
Binary file not shown.
BIN
plugin/src/pages/Edit/.DS_Store
vendored
BIN
plugin/src/pages/Edit/.DS_Store
vendored
Binary file not shown.
|
|
@ -23,6 +23,7 @@ import { mapState, mapMutations } from 'vuex'
|
|||
import OutlineEdit from './components/OutlineEdit.vue'
|
||||
import Guide from './components/Guide.vue'
|
||||
import darkMixin from '@/mixins/dark'
|
||||
import Export from './components/Export.vue'
|
||||
|
||||
export default {
|
||||
mixins: [darkMixin],
|
||||
|
|
@ -30,7 +31,8 @@ export default {
|
|||
Toolbar,
|
||||
Edit,
|
||||
OutlineEdit,
|
||||
Guide
|
||||
Guide,
|
||||
Export
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -86,6 +88,7 @@ export default {
|
|||
|
||||
onMindMapLoadend() {
|
||||
this.loadToolbar = true
|
||||
this.$root.$bus.$emit('mindMapFirstLoadend')
|
||||
},
|
||||
|
||||
onToggleReadonly(isReadonly) {
|
||||
|
|
@ -107,7 +110,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 初始化本地配置
|
||||
initLocalConfig() {
|
||||
const config = this.$root.$obsidianAPI.getMindMapLocalConfig()
|
||||
if (config) {
|
||||
|
|
@ -118,7 +120,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 保存本地配置
|
||||
saveLocalConfig() {
|
||||
this.$root.$obsidianAPI.saveMindMapLocalConfig({
|
||||
...this.localConfig
|
||||
|
|
@ -230,7 +231,10 @@ export default {
|
|||
|
||||
body {
|
||||
&.isDark {
|
||||
/* el-button */
|
||||
.el-loading-mask {
|
||||
background-color: #363b3f;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
background-color: #363b3f;
|
||||
color: hsla(0, 0%, 100%, 0.9);
|
||||
|
|
@ -242,7 +246,6 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
/* el-input */
|
||||
.el-input__inner {
|
||||
background-color: #363b3f;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
|
|
@ -255,7 +258,6 @@ body {
|
|||
color: hsla(0, 0%, 100%, 0.3);
|
||||
}
|
||||
|
||||
// 数字输入框
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase {
|
||||
background-color: transparent;
|
||||
|
|
@ -273,7 +275,6 @@ body {
|
|||
color: hsla(0, 0%, 100%, 0.9);
|
||||
}
|
||||
|
||||
/* el-select */
|
||||
.el-select-dropdown {
|
||||
background-color: #36393d;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
|
|
@ -296,7 +297,6 @@ body {
|
|||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
}
|
||||
|
||||
/* el-popper*/
|
||||
.el-popper {
|
||||
background-color: #36393d;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
|
|
@ -318,7 +318,6 @@ body {
|
|||
border-top-color: #36393d;
|
||||
}
|
||||
|
||||
/* el-tabs */
|
||||
.el-tabs__item {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
|
||||
|
|
@ -332,12 +331,10 @@ body {
|
|||
background-color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
/* el-slider */
|
||||
.el-slider__runway {
|
||||
background-color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
/* el-radio-group */
|
||||
.el-radio-group {
|
||||
.el-radio-button__inner {
|
||||
background-color: #36393d;
|
||||
|
|
@ -350,7 +347,6 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
/* el-dialog */
|
||||
.el-dialog {
|
||||
background-color: #262a2e;
|
||||
|
||||
|
|
@ -371,12 +367,10 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
/* el-upload */
|
||||
.el-upload__tip {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
// 勾选框
|
||||
.el-checkbox__inner {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
|
@ -385,7 +379,6 @@ body {
|
|||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
// 下拉菜单
|
||||
.el-dropdown-menu__item {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
|
@ -396,7 +389,6 @@ body {
|
|||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
// 数字输入框
|
||||
.el-input-number__decrease,
|
||||
.el-input-number__increase {
|
||||
background-color: transparent;
|
||||
|
|
@ -404,19 +396,16 @@ body {
|
|||
border-color: hsla(0, 0%, 100%, 0.2);
|
||||
}
|
||||
|
||||
// 单选框
|
||||
.el-radio__label {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
// 文本域
|
||||
.el-textarea__inner {
|
||||
background-color: #363b3f;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
color: hsla(0, 0%, 100%, 0.9);
|
||||
}
|
||||
|
||||
// 确认框
|
||||
.smmCustomElConfirm {
|
||||
background-color: #262a2e;
|
||||
border-color: hsla(0, 0%, 100%, 0.2);
|
||||
|
|
@ -430,7 +419,6 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
// 表单
|
||||
.el-form-item__label {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
|
@ -453,33 +441,30 @@ body {
|
|||
.el-tree-node:focus > .el-tree-node__content {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// 确认对话框
|
||||
.darkElMessageBox {
|
||||
background-color: #262a2e;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
.darkElMessageBox {
|
||||
background-color: #262a2e;
|
||||
border-color: hsla(0, 0%, 100%, 0.1);
|
||||
|
||||
.el-message-box__header {
|
||||
.el-message-box__title {
|
||||
color: hsla(0, 0%, 100%, 0.9);
|
||||
.el-message-box__header {
|
||||
.el-message-box__title {
|
||||
color: hsla(0, 0%, 100%, 0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-message-box__content {
|
||||
color: #999;
|
||||
.el-message-box__content {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 小号按钮
|
||||
.el-button.smmElButtonSmall {
|
||||
height: 25px;
|
||||
padding: 0 30px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
// 确认框
|
||||
.smmCustomElConfirm {
|
||||
.el-message-box__header {
|
||||
.el-message-box__headerbtn {
|
||||
|
|
|
|||
|
|
@ -221,11 +221,11 @@ import Sidebar from './Sidebar.vue'
|
|||
import Color from './Color.vue'
|
||||
import {
|
||||
lineWidthList,
|
||||
fontFamilyList,
|
||||
fontSizeList,
|
||||
borderDasharrayList
|
||||
} from '@/config'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import fontFamilyMixin from '@/mixins/fontFamily'
|
||||
|
||||
const defaultStyle = {
|
||||
associativeLineColor: '',
|
||||
|
|
@ -239,6 +239,7 @@ const defaultStyle = {
|
|||
}
|
||||
|
||||
export default {
|
||||
mixins: [fontFamilyMixin],
|
||||
components: {
|
||||
Sidebar,
|
||||
Color
|
||||
|
|
@ -265,10 +266,6 @@ export default {
|
|||
isDark: state => state.localConfig.isDark
|
||||
}),
|
||||
|
||||
fontFamilyList() {
|
||||
return fontFamilyList[this.$i18n.locale] || fontFamilyList.en
|
||||
},
|
||||
|
||||
borderDasharrayList() {
|
||||
return borderDasharrayList[this.$i18n.locale] || borderDasharrayList.en
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
:class="{ isDark: isDark }"
|
||||
v-if="data"
|
||||
>
|
||||
<!-- 背景 -->
|
||||
<div class="smmSidebarGroupTitle noTop">
|
||||
{{ $t('baseStyle.background') }}
|
||||
</div>
|
||||
|
|
@ -39,7 +38,6 @@
|
|||
}
|
||||
"
|
||||
></ImgUpload>
|
||||
<!-- 图片重复方式 -->
|
||||
<div class="rowItem mb">
|
||||
<span class="name">{{ $t('baseStyle.imageRepeat') }}</span>
|
||||
<el-select
|
||||
|
|
@ -62,7 +60,6 @@
|
|||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<!-- 图片位置 -->
|
||||
<div class="rowItem mb">
|
||||
<span class="name">{{ $t('baseStyle.imagePosition') }}</span>
|
||||
<el-select
|
||||
|
|
@ -85,7 +82,6 @@
|
|||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<!-- 图片大小 -->
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.imageSize') }}</span>
|
||||
<el-select
|
||||
|
|
@ -111,7 +107,6 @@
|
|||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<!-- 连线 -->
|
||||
<div class="smmSidebarGroupTitle">{{ $t('baseStyle.line') }}</div>
|
||||
<div class="row">
|
||||
<div class="rowItem mr">
|
||||
|
|
@ -173,7 +168,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- 线宽 -->
|
||||
<div class="rowItem mr">
|
||||
<span class="name">{{ $t('baseStyle.width') }}</span>
|
||||
<el-select
|
||||
|
|
@ -202,7 +196,7 @@
|
|||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="rowItem" v-if="lineStyleListShow.length > 1">
|
||||
<div class="rowItem" v-if="showLineStyleList">
|
||||
<span class="name">{{ $t('baseStyle.style') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
|
|
@ -216,7 +210,7 @@
|
|||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in lineStyleListShow"
|
||||
v-for="item in lineStyleList"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
|
|
@ -234,11 +228,11 @@
|
|||
<div
|
||||
class="row"
|
||||
v-if="
|
||||
(style.lineStyle === 'curve' && showRootLineKeepSameInCurveLayouts) ||
|
||||
(['curve', 'curve2'].includes(style.lineStyle) &&
|
||||
showRootLineKeepSameInCurveLayouts) ||
|
||||
showLineRadius
|
||||
"
|
||||
>
|
||||
<!-- 根节点连线样式 -->
|
||||
<div
|
||||
class="rowItem"
|
||||
v-if="
|
||||
|
|
@ -266,8 +260,34 @@
|
|||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div
|
||||
class="rowItem"
|
||||
v-if="
|
||||
style.lineStyle === 'curve2' && showRootLineKeepSameInCurveLayouts
|
||||
"
|
||||
>
|
||||
<span class="name">{{ $t('baseStyle.rootStyle') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.rootLineKeepSameInCurve2"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
update('rootLineKeepSameInCurve2', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in rootLineKeepSameInCurveList2"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="rowItem" v-if="showLineRadius">
|
||||
<!-- 连线圆角大小 -->
|
||||
<span class="name">{{ $t('baseStyle.lineRadius') }}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
|
|
@ -292,9 +312,11 @@
|
|||
</div>
|
||||
<div
|
||||
class="row"
|
||||
v-if="style.lineStyle === 'curve' && showRootLineKeepSameInCurveLayouts"
|
||||
v-if="
|
||||
['curve', 'curve2'].includes(style.lineStyle) &&
|
||||
showRootLineKeepSameInCurveLayouts
|
||||
"
|
||||
>
|
||||
<!-- 根节点连线起始位置 -->
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('baseStyle.rootLineStartPos') }}</span>
|
||||
<el-select
|
||||
|
|
@ -331,8 +353,34 @@
|
|||
>{{ $t('baseStyle.showArrow') }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name" style="margin-right: 0">{{
|
||||
$t('style.arrowDir')
|
||||
}}</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.lineMarkerDir"
|
||||
placeholder=""
|
||||
@change="
|
||||
value => {
|
||||
update('lineMarkerDir', value)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
key="start"
|
||||
:label="$t('style.arrowDirStart')"
|
||||
value="start"
|
||||
></el-option>
|
||||
<el-option
|
||||
key="end"
|
||||
:label="$t('style.arrowDirEnd')"
|
||||
value="end"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 彩虹线条 -->
|
||||
<div class="smmSidebarGroupTitle">{{ $t('baseStyle.rainbowLines') }}</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
|
|
@ -366,10 +414,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div slot="reference" class="curRainbowLine">
|
||||
<div class="smmColorsBar" v-if="curRainbowLineColorList">
|
||||
<div
|
||||
class="smmColorsBar"
|
||||
v-if="style.lineRainbow && style.lineRainbowColorsList"
|
||||
>
|
||||
<span
|
||||
class="colorItem"
|
||||
v-for="color in curRainbowLineColorList"
|
||||
v-for="color in style.lineRainbowColorsList"
|
||||
:key="color"
|
||||
:style="{ backgroundColor: color }"
|
||||
></span>
|
||||
|
|
@ -379,7 +430,6 @@
|
|||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 概要连线 -->
|
||||
<div class="smmSidebarGroupTitle">
|
||||
{{ $t('baseStyle.lineOfOutline') }}
|
||||
</div>
|
||||
|
|
@ -431,7 +481,6 @@
|
|||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 关联线 -->
|
||||
<div class="smmSidebarGroupTitle">
|
||||
{{ $t('baseStyle.associativeLine') }}
|
||||
</div>
|
||||
|
|
@ -576,7 +625,6 @@
|
|||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 关联线文字 -->
|
||||
<div class="smmSidebarGroupTitle">
|
||||
{{ $t('baseStyle.associativeLineText') }}
|
||||
</div>
|
||||
|
|
@ -640,7 +688,6 @@
|
|||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 节点边框风格 -->
|
||||
<template v-if="showNodeUseLineStyle">
|
||||
<div class="smmSidebarGroupTitle">
|
||||
{{ $t('baseStyle.nodeBorderType') }}
|
||||
|
|
@ -659,7 +706,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 内边距 -->
|
||||
<div class="smmSidebarGroupTitle">{{ $t('baseStyle.nodePadding') }}</div>
|
||||
<div class="row noBottom">
|
||||
<div class="rowItem">
|
||||
|
|
@ -689,7 +735,6 @@
|
|||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 图片 -->
|
||||
<div class="smmSidebarGroupTitle">{{ $t('baseStyle.image') }}</div>
|
||||
<div class="row noBottom">
|
||||
<div class="rowItem">
|
||||
|
|
@ -723,7 +768,6 @@
|
|||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 图标 -->
|
||||
<div class="smmSidebarGroupTitle">{{ $t('baseStyle.icon') }}</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
|
|
@ -741,7 +785,6 @@
|
|||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 二级节点外边距 -->
|
||||
<div class="smmSidebarGroupTitle">{{ $t('baseStyle.nodeMargin') }}</div>
|
||||
<div class="row column noBottom">
|
||||
<el-tabs
|
||||
|
|
@ -785,7 +828,6 @@
|
|||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 外框内边距 -->
|
||||
<div class="smmSidebarGroupTitle">
|
||||
{{ $t('baseStyle.outerFramePadding') }}
|
||||
</div>
|
||||
|
|
@ -830,25 +872,26 @@ import {
|
|||
backgroundRepeatList,
|
||||
backgroundPositionList,
|
||||
backgroundSizeList,
|
||||
fontFamilyList,
|
||||
fontSizeList,
|
||||
rootLineKeepSameInCurveList,
|
||||
rootLineKeepSameInCurveList2,
|
||||
lineStyleMap,
|
||||
borderDasharrayList
|
||||
} from '@/config'
|
||||
import ImgUpload from '@/components/ImgUpload/index.vue'
|
||||
import { mapState } from 'vuex'
|
||||
import {
|
||||
supportLineStyleLayoutsMap,
|
||||
supportChangeLineStyleLayouts,
|
||||
supportLineRadiusLayouts,
|
||||
supportNodeUseLineStyleLayouts,
|
||||
supportRootLineKeepSameInCurveLayouts,
|
||||
rainbowLinesOptions
|
||||
} from '@/config/constant'
|
||||
import ConfigImportOutputDropdown from './ConfigImportOutputDropdown.vue'
|
||||
import fontFamilyMixin from '@/mixins/fontFamily'
|
||||
|
||||
// 基础样式
|
||||
export default {
|
||||
mixins: [fontFamilyMixin],
|
||||
components: {
|
||||
Sidebar,
|
||||
Color,
|
||||
|
|
@ -881,7 +924,9 @@ export default {
|
|||
lineWidth: '',
|
||||
lineStyle: '',
|
||||
showLineMarker: '',
|
||||
lineMarkerDir: '',
|
||||
rootLineKeepSameInCurve: '',
|
||||
rootLineKeepSameInCurve2: '',
|
||||
rootLineStartPositionKeepSameInCurve: '',
|
||||
lineRadius: 0,
|
||||
lineFlow: false,
|
||||
|
|
@ -908,11 +953,12 @@ export default {
|
|||
backgroundSize: '',
|
||||
marginX: 0,
|
||||
marginY: 0,
|
||||
nodeUseLineStyle: false
|
||||
nodeUseLineStyle: false,
|
||||
lineRainbow: false,
|
||||
lineRainbowColorsList: []
|
||||
},
|
||||
rainbowLinesPopoverVisible: false,
|
||||
curRainbowLineColorList: null,
|
||||
currentLayout: '', // 当前结构
|
||||
currentLayout: '',
|
||||
outerFramePadding: {
|
||||
outerFramePaddingX: 0,
|
||||
outerFramePaddingY: 0
|
||||
|
|
@ -934,6 +980,12 @@ export default {
|
|||
rootLineKeepSameInCurveList.en
|
||||
)
|
||||
},
|
||||
rootLineKeepSameInCurveList2() {
|
||||
return (
|
||||
rootLineKeepSameInCurveList2[this.$i18n.locale] ||
|
||||
rootLineKeepSameInCurveList2.zh
|
||||
)
|
||||
},
|
||||
backgroundRepeatList() {
|
||||
return backgroundRepeatList[this.$i18n.locale] || backgroundRepeatList.en
|
||||
},
|
||||
|
|
@ -945,9 +997,6 @@ export default {
|
|||
backgroundSizeList() {
|
||||
return backgroundSizeList[this.$i18n.locale] || backgroundSizeList.en
|
||||
},
|
||||
fontFamilyList() {
|
||||
return fontFamilyList[this.$i18n.locale] || fontFamilyList.en
|
||||
},
|
||||
showNodeUseLineStyle() {
|
||||
return supportNodeUseLineStyleLayouts.includes(this.currentLayout)
|
||||
},
|
||||
|
|
@ -957,19 +1006,8 @@ export default {
|
|||
supportLineRadiusLayouts.includes(this.currentLayout)
|
||||
)
|
||||
},
|
||||
lineStyleListShow() {
|
||||
const res = []
|
||||
this.lineStyleList.forEach(item => {
|
||||
const list = supportLineStyleLayoutsMap[item.value]
|
||||
if (list) {
|
||||
if (list.includes(this.currentLayout)) {
|
||||
res.push(item)
|
||||
}
|
||||
} else {
|
||||
res.push(item)
|
||||
}
|
||||
})
|
||||
return res
|
||||
showLineStyleList() {
|
||||
return supportChangeLineStyleLayouts.includes(this.currentLayout)
|
||||
},
|
||||
showRootLineKeepSameInCurveLayouts() {
|
||||
return supportRootLineKeepSameInCurveLayouts.includes(this.currentLayout)
|
||||
|
|
@ -983,23 +1021,11 @@ export default {
|
|||
if (val === 'baseStyle') {
|
||||
this.$refs.sidebar.show = true
|
||||
this.initStyle()
|
||||
this.initRainbowLines()
|
||||
this.initOuterFramePadding()
|
||||
this.currentLayout = this.mindMap.getLayout()
|
||||
} else {
|
||||
this.$refs.sidebar.show = false
|
||||
}
|
||||
},
|
||||
lineStyleListShow: {
|
||||
deep: true,
|
||||
handler() {
|
||||
const has = this.lineStyleListShow.find(item => {
|
||||
return item.value === this.style.lineStyle
|
||||
})
|
||||
if (!has) {
|
||||
this.style.lineStyle = this.lineStyleListShow[0].value
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
|
@ -1016,7 +1042,6 @@ export default {
|
|||
}, 0)
|
||||
},
|
||||
|
||||
// 初始样式
|
||||
initStyle() {
|
||||
Object.keys(this.style).forEach(key => {
|
||||
this.style[key] = this.mindMap.getThemeConfig(key)
|
||||
|
|
@ -1027,17 +1052,6 @@ export default {
|
|||
this.initMarginStyle()
|
||||
},
|
||||
|
||||
// 初始化彩虹线条配置
|
||||
initRainbowLines() {
|
||||
const config = this.mindMap.getConfig('rainbowLinesConfig') || {}
|
||||
this.curRainbowLineColorList = config.open
|
||||
? this.mindMap.rainbowLines
|
||||
? this.mindMap.rainbowLines.getColorsList()
|
||||
: null
|
||||
: null
|
||||
},
|
||||
|
||||
// 外框
|
||||
initOuterFramePadding() {
|
||||
this.outerFramePadding.outerFramePaddingX = this.mindMap.getConfig(
|
||||
'outerFramePaddingX'
|
||||
|
|
@ -1047,7 +1061,6 @@ export default {
|
|||
)
|
||||
},
|
||||
|
||||
// margin初始值
|
||||
initMarginStyle() {
|
||||
;['marginX', 'marginY'].forEach(key => {
|
||||
this.style[key] = this.mindMap.getThemeConfig()[this.marginActiveTab][
|
||||
|
|
@ -1056,7 +1069,6 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 更新配置
|
||||
update(key, value) {
|
||||
if (key === 'backgroundImage' && value === 'none') {
|
||||
this.style[key] = ''
|
||||
|
|
@ -1074,27 +1086,28 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 更新彩虹线条配置
|
||||
updateRainbowLinesConfig(item) {
|
||||
this.rainbowLinesPopoverVisible = false
|
||||
this.curRainbowLineColorList = item.list || null
|
||||
let newConfig = null
|
||||
if (item.list) {
|
||||
newConfig = {
|
||||
open: true,
|
||||
colorsList: item.list
|
||||
}
|
||||
this.style.lineRainbow = true
|
||||
this.style.lineRainbowColorsList = item.list
|
||||
this.data.theme.config.lineRainbow = true
|
||||
this.data.theme.config.lineRainbowColorsList = item.list
|
||||
} else {
|
||||
newConfig = {
|
||||
open: false
|
||||
}
|
||||
this.style.lineRainbow = false
|
||||
this.data.theme.config.lineRainbow = false
|
||||
}
|
||||
this.configData.rainbowLinesConfig = newConfig
|
||||
this.mindMap.rainbowLines.updateRainLinesConfig(newConfig)
|
||||
this.$root.$obsidianAPI.saveMindMapConfig(this.configData)
|
||||
this.$root.$bus.$emit('showLoading')
|
||||
this.mindMap.setThemeConfig(this.data.theme.config)
|
||||
this.mindMap.rainbowLines.updateRainLinesRender()
|
||||
this.$root.$bus.$emit('storeData', {
|
||||
theme: {
|
||||
template: this.mindMap.getTheme(),
|
||||
config: this.data.theme.config
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 更新外框
|
||||
updateOuterFramePadding(prop, value) {
|
||||
this.outerFramePadding[prop] = value
|
||||
this.configData[prop] = value
|
||||
|
|
@ -1105,7 +1118,6 @@ export default {
|
|||
this.mindMap.render()
|
||||
},
|
||||
|
||||
// 设置margin
|
||||
updateMargin(type, value) {
|
||||
this.style[type] = value
|
||||
if (!this.data.theme.config[this.marginActiveTab]) {
|
||||
|
|
@ -1121,7 +1133,6 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 导出配置
|
||||
getOutputConfig() {
|
||||
return {
|
||||
themeStyle: this.style || {},
|
||||
|
|
@ -1129,7 +1140,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 导入配置
|
||||
setConfig({ themeStyle, config }) {
|
||||
Object.keys(themeStyle).forEach(key => {
|
||||
if (['marginX', 'marginY'].includes(key)) {
|
||||
|
|
@ -1138,16 +1148,12 @@ export default {
|
|||
this.update(key, themeStyle[key])
|
||||
}
|
||||
})
|
||||
if (this.style.lineRainbow) {
|
||||
this.mindMap.rainbowLines.updateRainLinesRender()
|
||||
}
|
||||
Object.keys(config).forEach(key => {
|
||||
if (['outerFramePaddingX', 'outerFramePaddingY'].includes(key)) {
|
||||
this.updateOuterFramePadding(key, config[key])
|
||||
} else if (key === 'rainbowLinesConfig') {
|
||||
const value = config[key]
|
||||
const data = {}
|
||||
if (value.open) {
|
||||
data.list = value.colorsList
|
||||
}
|
||||
this.updateRainbowLinesConfig(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
import { colorList } from '@/config'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
// 颜色选择器
|
||||
export default {
|
||||
props: {
|
||||
color: {
|
||||
|
|
@ -55,12 +54,10 @@ export default {
|
|||
this.selectColor = this.color
|
||||
},
|
||||
methods: {
|
||||
// 点击预设颜色
|
||||
clickColorItem(color) {
|
||||
this.$emit('change', color)
|
||||
},
|
||||
|
||||
// 修改颜色
|
||||
changeColor() {
|
||||
this.$emit('change', this.selectColor)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,46 +7,56 @@
|
|||
:class="{ isDark: isDark, isMobile: isMobile }"
|
||||
>
|
||||
<template v-if="type === 'node'">
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('INSERT_NODE', insertNodeBtnDisabled)"
|
||||
:class="{ disabled: insertNodeBtnDisabled }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.insertSiblingNode') }}</span>
|
||||
<span class="desc"></span>
|
||||
<div class="item">
|
||||
<span class="name">{{ $t('contextmenu.insertNode') }}</span>
|
||||
<span class="el-icon-arrow-right"></span>
|
||||
<div
|
||||
class="subItems listBox"
|
||||
:class="{ isDark: isDark }"
|
||||
style="width: 200px; top: -10px;"
|
||||
:style="{ left: subItemsShowLeft ? '-200px' : '100%' }"
|
||||
>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('INSERT_NODE', insertNodeBtnDisabled)"
|
||||
:class="{ disabled: insertNodeBtnDisabled }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.insertSiblingNode') }}</span>
|
||||
<span class="desc">Enter</span>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('INSERT_CHILD_NODE')"
|
||||
:class="{ disabled: isGeneralization }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.insertChildNode') }}</span>
|
||||
<span class="desc">Tab</span>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('INSERT_PARENT_NODE')"
|
||||
:class="{ disabled: insertNodeBtnDisabled }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.insertParentNode') }}</span>
|
||||
<span class="desc">Shift + Tab</span>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('ADD_GENERALIZATION')"
|
||||
:class="{ disabled: insertNodeBtnDisabled }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.insertSummary') }}</span>
|
||||
<span class="desc">Ctrl + G</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('INSERT_CHILD_NODE')"
|
||||
:class="{ disabled: isGeneralization }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.insertChildNode') }}</span>
|
||||
<span class="desc"></span>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('INSERT_PARENT_NODE')"
|
||||
:class="{ disabled: insertNodeBtnDisabled }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.insertParentNode') }}</span>
|
||||
<span class="desc"></span>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('ADD_GENERALIZATION')"
|
||||
:class="{ disabled: insertNodeBtnDisabled }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.insertSummary') }}</span>
|
||||
<span class="desc"></span>
|
||||
</div>
|
||||
<div class="splitLine"></div>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('UP_NODE')"
|
||||
:class="{ disabled: upNodeBtnDisabled }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.moveUpNode') }}</span>
|
||||
<span class="desc"></span>
|
||||
<span class="desc">Ctrl + ↑</span>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
|
|
@ -54,7 +64,7 @@
|
|||
:class="{ disabled: downNodeBtnDisabled }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.moveDownNode') }}</span>
|
||||
<span class="desc"></span>
|
||||
<span class="desc">Ctrl + ↓</span>
|
||||
</div>
|
||||
<div class="item" @click="exec('UNEXPAND_ALL')">
|
||||
<span class="name">{{ $t('contextmenu.unExpandNodeChild') }}</span>
|
||||
|
|
@ -62,35 +72,61 @@
|
|||
<div class="item" @click="exec('EXPAND_ALL')">
|
||||
<span class="name">{{ $t('contextmenu.expandNodeChild') }}</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="name">选择</span>
|
||||
<span class="el-icon-arrow-right"></span>
|
||||
<div
|
||||
class="subItems listBox"
|
||||
:class="{ isDark: isDark, showLeft: subItemsShowLeft }"
|
||||
style="top: -10px"
|
||||
>
|
||||
<div class="item" @click="selectNodes('brother')">同层级所有节点</div>
|
||||
<div class="item" @click="selectNodes('selfAll')">自身及下级节点</div>
|
||||
<div class="item" @click="selectNodes('children')">子节点</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="splitLine"></div>
|
||||
<div class="item danger" @click="exec('REMOVE_NODE')">
|
||||
<span class="name">{{ $t('contextmenu.deleteNode') }}</span>
|
||||
<span class="desc"></span>
|
||||
<span class="desc">Delete</span>
|
||||
</div>
|
||||
<div class="item danger" @click="exec('REMOVE_CURRENT_NODE')">
|
||||
<span class="name">{{ $t('contextmenu.deleteCurrentNode') }}</span>
|
||||
<span class="desc"></span>
|
||||
<span class="desc">Shift + Backspace</span>
|
||||
</div>
|
||||
<div class="splitLine"></div>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('COPY_NODE')"
|
||||
:class="{ disabled: isGeneralization }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.copyNode') }}</span>
|
||||
<span class="desc"></span>
|
||||
<div class="item">
|
||||
<span class="name">复制粘贴</span>
|
||||
<span class="el-icon-arrow-right"></span>
|
||||
<div
|
||||
class="subItems listBox"
|
||||
:class="{ isDark: isDark, showLeft: subItemsShowLeft }"
|
||||
style="top: -10px"
|
||||
>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('COPY_NODE')"
|
||||
:class="{ disabled: isGeneralization }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.copyNode') }}</span>
|
||||
<span class="desc">Ctrl + C</span>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('CUT_NODE')"
|
||||
:class="{ disabled: isGeneralization }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.cutNode') }}</span>
|
||||
<span class="desc">Ctrl + X</span>
|
||||
</div>
|
||||
<div class="item" @click="exec('PASTE_NODE')">
|
||||
<span class="name">{{ $t('contextmenu.pasteNode') }}</span>
|
||||
<span class="desc">Ctrl + V</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@click="exec('CUT_NODE')"
|
||||
:class="{ disabled: isGeneralization }"
|
||||
>
|
||||
<span class="name">{{ $t('contextmenu.cutNode') }}</span>
|
||||
<span class="desc"></span>
|
||||
</div>
|
||||
<div class="item" @click="exec('PASTE_NODE')">
|
||||
<span class="name">{{ $t('contextmenu.pasteNode') }}</span>
|
||||
<span class="desc"></span>
|
||||
<div class="item" @click="copyNodeToTxt">
|
||||
<span class="name">{{ $t('contextmenu.copyNodeToTxt') }}</span>
|
||||
</div>
|
||||
<div class="splitLine"></div>
|
||||
<div class="item" @click="exec('REMOVE_HYPERLINK')" v-if="hasHyperlink">
|
||||
|
|
@ -134,7 +170,7 @@
|
|||
<template v-if="type === 'svg'">
|
||||
<div class="item" @click="exec('RETURN_CENTER')">
|
||||
<span class="name">{{ $t('contextmenu.backCenter') }}</span>
|
||||
<span class="desc"></span>
|
||||
<span class="desc">Ctrl + Enter</span>
|
||||
</div>
|
||||
<div class="splitLine"></div>
|
||||
<div class="item" @click="exec('EXPAND_ALL')">
|
||||
|
|
@ -163,18 +199,31 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="name">选择</span>
|
||||
<span class="el-icon-arrow-right"></span>
|
||||
<div
|
||||
class="subItems listBox"
|
||||
:class="{ isDark: isDark, showLeft: subItemsShowLeft }"
|
||||
style="top: -10px"
|
||||
>
|
||||
<div class="item" @click="selectNodes('all')">所有节点</div>
|
||||
<div class="item" @click="selectNodes('second')">二级节点</div>
|
||||
<div class="item" @click="selectNodes('third')">三级节点</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="splitLine"></div>
|
||||
<div class="item" @click="exec('RESET_LAYOUT')" v-if="!isReadonly">
|
||||
<span class="name">{{ $t('contextmenu.arrangeLayout') }}</span>
|
||||
<span class="desc"></span>
|
||||
<span class="desc">Ctrl + R</span>
|
||||
</div>
|
||||
<div class="item" @click="exec('FIT_CANVAS')">
|
||||
<span class="name">{{ $t('contextmenu.fitCanvas') }}</span>
|
||||
<span class="desc"></span>
|
||||
<span class="desc">Ctrl + Shift + i</span>
|
||||
</div>
|
||||
<div class="item" @click="exec('TOGGLE_ZEN_MODE')">
|
||||
<span class="name">{{ $t('contextmenu.zenMode') }}</span>
|
||||
{{ isZenMode ? '√' : '' }}
|
||||
<span class="el-icon-check" v-if="isZenMode"></span>
|
||||
</div>
|
||||
<div class="splitLine"></div>
|
||||
<div
|
||||
|
|
@ -224,12 +273,15 @@
|
|||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { getTextFromHtml, imgToDataUrl } from 'simple-mind-map/src/utils'
|
||||
import {
|
||||
getTextFromHtml,
|
||||
imgToDataUrl,
|
||||
copyNodeTree
|
||||
} from 'simple-mind-map/src/utils'
|
||||
import { transformToMarkdown } from 'simple-mind-map/src/parse/toMarkdown'
|
||||
import { transformToTxt } from 'simple-mind-map/src/parse/toTxt'
|
||||
import txt from 'simple-mind-map/src/parse/txt'
|
||||
import { setDataToClipboard, setImgToClipboard, copy } from '@/utils'
|
||||
|
||||
// 右键菜单
|
||||
export default {
|
||||
props: {
|
||||
mindMap: {
|
||||
|
|
@ -247,6 +299,8 @@ export default {
|
|||
mosuedownX: 0,
|
||||
mosuedownY: 0,
|
||||
enableCopyToClipboardApi: navigator.clipboard,
|
||||
numberType: '',
|
||||
numberLevel: '',
|
||||
subItemsShowLeft: false,
|
||||
isNodeMousedown: false,
|
||||
listBoxWidth: '250px'
|
||||
|
|
@ -376,7 +430,6 @@ export default {
|
|||
methods: {
|
||||
...mapMutations(['setLocalConfig']),
|
||||
|
||||
// 计算右键菜单元素的显示位置
|
||||
getShowPosition(x, y) {
|
||||
if (this.isMobile) {
|
||||
this.listBoxWidth =
|
||||
|
|
@ -400,11 +453,15 @@ export default {
|
|||
return { x, y }
|
||||
},
|
||||
|
||||
// 节点右键显示
|
||||
show(e, node) {
|
||||
this.type = 'node'
|
||||
this.isShow = true
|
||||
this.node = node
|
||||
const number = this.node.getData('number')
|
||||
if (number) {
|
||||
this.numberType = number.type || 1
|
||||
this.numberLevel = number.level === '' ? 1 : number.level
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
const { x, y } = this.getShowPosition(e.clientX + 10, e.clientY + 10)
|
||||
this.left = x
|
||||
|
|
@ -416,7 +473,6 @@ export default {
|
|||
this.isNodeMousedown = true
|
||||
},
|
||||
|
||||
// 鼠标按下事件
|
||||
onMousedown(e) {
|
||||
if (!e || e.which !== 3) {
|
||||
return
|
||||
|
|
@ -426,7 +482,6 @@ export default {
|
|||
this.isMousedown = true
|
||||
},
|
||||
|
||||
// 鼠标松开事件
|
||||
onMouseup(e) {
|
||||
if (!this.isMousedown) {
|
||||
return
|
||||
|
|
@ -446,7 +501,6 @@ export default {
|
|||
this.show2(e)
|
||||
},
|
||||
|
||||
// 画布右键显示
|
||||
show2(e) {
|
||||
this.type = 'svg'
|
||||
this.isShow = true
|
||||
|
|
@ -457,16 +511,16 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 隐藏
|
||||
hide() {
|
||||
this.isShow = false
|
||||
this.left = -9999
|
||||
this.top = -9999
|
||||
this.type = ''
|
||||
this.node = ''
|
||||
this.numberType = ''
|
||||
this.numberLevel = ''
|
||||
},
|
||||
|
||||
// 执行命令
|
||||
exec(key, disabled, ...args) {
|
||||
if (disabled) {
|
||||
return
|
||||
|
|
@ -532,7 +586,6 @@ export default {
|
|||
break
|
||||
case 'COPY_NODE_TO_OB_LINK':
|
||||
const link2 = this.$root.$obsidianAPI.getObInternalLink(
|
||||
'',
|
||||
'#^' + this.node.getData('uid')
|
||||
)
|
||||
copy(link2)
|
||||
|
|
@ -559,12 +612,28 @@ export default {
|
|||
this.$root.$obsidianAPI.showTip(this.$t('contextmenu.copySuccess'))
|
||||
this.hide()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$root.$obsidianAPI.showTip(this.$t('contextmenu.copyFail'))
|
||||
}
|
||||
},
|
||||
|
||||
// 复制到剪贴板
|
||||
copyNodeToTxt() {
|
||||
const text = txt.toTxt(
|
||||
copyNodeTree({
|
||||
tree: {},
|
||||
root: this.node,
|
||||
removeActiveState: true
|
||||
})
|
||||
)
|
||||
if (text) {
|
||||
if (this.enableCopyToClipboardApi) {
|
||||
setDataToClipboard(text)
|
||||
} else {
|
||||
copy(text)
|
||||
}
|
||||
}
|
||||
this.hide()
|
||||
},
|
||||
|
||||
async copyToClipboard(type) {
|
||||
try {
|
||||
this.hide()
|
||||
|
|
@ -577,12 +646,15 @@ export default {
|
|||
str = JSON.stringify(data)
|
||||
break
|
||||
case 'md':
|
||||
const {
|
||||
nodeTextToMarkdownTitleMaxLevel
|
||||
} = this.$root.$obsidianAPI.getSettings()
|
||||
data = this.mindMap.getData()
|
||||
str = transformToMarkdown(data)
|
||||
str = transformToMarkdown(data, nodeTextToMarkdownTitleMaxLevel)
|
||||
break
|
||||
case 'txt':
|
||||
data = this.mindMap.getData()
|
||||
str = transformToTxt(data)
|
||||
str = txt.toTxt(data)
|
||||
break
|
||||
case 'png':
|
||||
const png = await this.mindMap.export('png', false)
|
||||
|
|
@ -601,9 +673,30 @@ export default {
|
|||
}
|
||||
this.$root.$obsidianAPI.showTip(this.$t('contextmenu.copySuccess'))
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$root.$obsidianAPI.showTip(this.$t('contextmenu.copyFail'))
|
||||
}
|
||||
},
|
||||
|
||||
aiCreate() {
|
||||
this.$root.$bus.$emit('ai_create_part', this.node)
|
||||
this.hide()
|
||||
},
|
||||
|
||||
selectNodes(type) {
|
||||
if (type === 'all') {
|
||||
this.mindMap.execCommand('SELECT_ALL')
|
||||
} else if (type === 'second') {
|
||||
this.mindMap.execCommand('SELECT_ONE_LEVEL', 2)
|
||||
} else if (type === 'third') {
|
||||
this.mindMap.execCommand('SELECT_ONE_LEVEL', 3)
|
||||
} else if (type === 'brother') {
|
||||
this.mindMap.execCommand('SELECT_ONE_LEVEL', this.node.layerIndex + 1)
|
||||
} else if (type === 'selfAll') {
|
||||
this.mindMap.execCommand('SELECT_SELF_AND_CHILDREN')
|
||||
} else if (type === 'children') {
|
||||
this.mindMap.execCommand('SELECT_CHILDREN')
|
||||
}
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -625,6 +718,18 @@ export default {
|
|||
width: 250px;
|
||||
}
|
||||
}
|
||||
.listBox {
|
||||
width: 250px;
|
||||
background: #fff;
|
||||
box-shadow: 0 4px 12px 0 hsla(0, 0%, 69%, 0.5);
|
||||
border-radius: 4px;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
|
||||
&.isDark {
|
||||
background: #363b3f;
|
||||
}
|
||||
}
|
||||
.contextmenuContainer {
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
|
|
@ -725,6 +830,7 @@ export default {
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.subItems {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<div class="countContainer" :class="{ isDark: isDark }"></div>
|
||||
<div class="countContainer" :class="{ isDark: isDark }">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
// 字数及节点数量统计
|
||||
let countEl = document.createElement('div')
|
||||
export default {
|
||||
props: {
|
||||
|
|
@ -40,7 +40,6 @@ export default {
|
|||
this.$root.$bus.$off('data_change', this.onDataChange)
|
||||
},
|
||||
methods: {
|
||||
// 监听数据变化
|
||||
onDataChange(data) {
|
||||
this.textStr = ''
|
||||
this.words = 0
|
||||
|
|
@ -61,7 +60,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 遍历
|
||||
walk(data) {
|
||||
if (!data) return
|
||||
this.num++
|
||||
|
|
@ -103,7 +101,6 @@ export default {
|
|||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
/* 隐藏默认状态栏项 */
|
||||
.smm-file-status-active .status-bar-item.plugin-word-count,
|
||||
.smm-file-status-active .status-bar-item.plugin-sync {
|
||||
display: none !important;
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ export default {
|
|||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
font-size: 20px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -56,7 +56,47 @@
|
|||
:mindMap="mindMap"
|
||||
></NodeImgPlacementToolbar>
|
||||
<NodeNoteSidebar v-if="mindMap" :mindMap="mindMap"></NodeNoteSidebar>
|
||||
<NodeTextInputObLink
|
||||
v-if="mindMap"
|
||||
:mindMap="mindMap"
|
||||
></NodeTextInputObLink>
|
||||
<Demonstrate v-if="mindMap" :mindMap="mindMap"></Demonstrate>
|
||||
<el-dialog
|
||||
class="confirmSplitLineDialog"
|
||||
:title="$t('edit.tip')"
|
||||
:visible.sync="confirmSplitLineDialogVisible"
|
||||
width="400px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<div class="dialogContent" :class="{ isDark: isDark }">
|
||||
<span class="icon el-icon-info"></span>
|
||||
<span class="text">{{ $t('edit.splitByWrap') }}</span>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button
|
||||
size="small"
|
||||
class="elButtonSmall"
|
||||
@click="handleSplitLine(1)"
|
||||
>{{ $t('edit.notSplit') }}</el-button
|
||||
>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
class="elButtonSmall"
|
||||
@click="handleSplitLine(2)"
|
||||
>{{ $t('edit.keepLevel') }}</el-button
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
class="elButtonSmall"
|
||||
@click="handleSplitLine(3)"
|
||||
>{{ $t('edit.ignoreLevel') }}</el-button
|
||||
>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -84,11 +124,7 @@ import OuterFrame from 'simple-mind-map/src/plugins/OuterFrame.js'
|
|||
import MindMapLayoutPro from 'simple-mind-map/src/plugins/MindMapLayoutPro.js'
|
||||
import NodeBase64ImageStorage from 'simple-mind-map/src/plugins/NodeBase64ImageStorage.js'
|
||||
import Themes from 'simple-mind-map-plugin-themes'
|
||||
import {
|
||||
isSameObject,
|
||||
getImageSize,
|
||||
nodeRichTextToTextWithWrap
|
||||
} from 'simple-mind-map/src/utils/index.js'
|
||||
import { isSameObject, getImageSize } from 'simple-mind-map/src/utils/index.js'
|
||||
import OutlineSidebar from './OutlineSidebar.vue'
|
||||
import Style from './Style.vue'
|
||||
import BaseStyle from './BaseStyle.vue'
|
||||
|
|
@ -119,15 +155,16 @@ import NodeNoteSidebar from './NodeNoteSidebar.vue'
|
|||
import {
|
||||
isHyperlink,
|
||||
isObLinkText,
|
||||
dfsTraverse,
|
||||
isNormalUrl,
|
||||
getFilenameWithExtension,
|
||||
checkMindTreeHasImg
|
||||
checkMindTreeHasImg,
|
||||
parseInnerLinkAndText
|
||||
} from '@/utils'
|
||||
import NodeTextInputObLink from './NodeTextInputObLink.vue'
|
||||
import Demonstrate from './Demonstrate.vue'
|
||||
import { imgFail, obFileHyperlinkIcon, hyperlinkIcon } from '@/config/icons'
|
||||
import imgHostingUpload from '@/mixins/imgHostingUpload'
|
||||
|
||||
// 注册插件
|
||||
MindMap.usePlugin(MiniMap)
|
||||
.usePlugin(Watermark)
|
||||
.usePlugin(Drag)
|
||||
|
|
@ -149,10 +186,10 @@ MindMap.usePlugin(MiniMap)
|
|||
.usePlugin(NodeBase64ImageStorage)
|
||||
.usePlugin(RichText)
|
||||
|
||||
// 注册主题
|
||||
Themes.init(MindMap)
|
||||
|
||||
export default {
|
||||
mixins: [imgHostingUpload],
|
||||
components: {
|
||||
OutlineSidebar,
|
||||
Style,
|
||||
|
|
@ -178,6 +215,7 @@ export default {
|
|||
AssociativeLineStyle,
|
||||
NodeImgPlacementToolbar,
|
||||
NodeNoteSidebar,
|
||||
NodeTextInputObLink,
|
||||
Demonstrate
|
||||
},
|
||||
data() {
|
||||
|
|
@ -194,7 +232,10 @@ export default {
|
|||
savingTimer: null,
|
||||
storeConfigTimer: null,
|
||||
isNotTriggerDataChange: false,
|
||||
dragEnterNode: null
|
||||
dragEnterNode: null,
|
||||
confirmSplitLineDialogVisible: false,
|
||||
confirmSplitLineTaskResolve: null,
|
||||
confirmSplitLineTaskReject: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -205,7 +246,9 @@ export default {
|
|||
state.localConfig.useLeftKeySelectionRightKeyDrag,
|
||||
extraTextOnExport: state => state.extraTextOnExport,
|
||||
autoSaveTime: state => state.localConfig.autoSaveTime,
|
||||
isShowBottomToolbar: state => state.localConfig.isShowBottomToolbar
|
||||
isShowBottomToolbar: state => state.localConfig.isShowBottomToolbar,
|
||||
isReadonly: state => state.isReadonly,
|
||||
isDark: state => state.localConfig.isDark
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -247,27 +290,6 @@ export default {
|
|||
this.$root.$bus.$on('obTabDeactivate', this.onObTabDeactivate)
|
||||
this.$root.$bus.$on('toggleReadonly', this.onToggleReadonly)
|
||||
this.$root.$bus.$on('jumpToNodeByUid', this.jumpToNodeByUid)
|
||||
this.$root.$bus.$on('toggleActiveExpand', this.toggleActiveExpand)
|
||||
this.$root.$bus.$on('copyNode', this.copyNode)
|
||||
this.$root.$bus.$on('cutNode', this.cutNode)
|
||||
this.$root.$bus.$on('pasteNode', this.pasteNode)
|
||||
this.$root.$bus.$on('canvasZoomIn', this.canvasZoomIn)
|
||||
this.$root.$bus.$on('canvasZoomOut', this.canvasZoomOut)
|
||||
this.$root.$bus.$on('canvasBackToRoot', this.canvasBackToRoot)
|
||||
this.$root.$bus.$on('canvasZoomFit', this.canvasZoomFit)
|
||||
this.$root.$bus.$on('toggleBold', this.toggleBold)
|
||||
this.$root.$bus.$on('toggleItalic', this.toggleItalic)
|
||||
this.$root.$bus.$on('toggleUnderline', this.toggleUnderline)
|
||||
this.$root.$bus.$on('toggleStrikethrough', this.toggleStrikethrough)
|
||||
this.$root.$bus.$on('increaseFontSize', this.increaseFontSize)
|
||||
this.$root.$bus.$on('decreaseFontSize', this.decreaseFontSize)
|
||||
this.$root.$bus.$on('canvasActivateLeftNode', this.canvasActivateLeftNode)
|
||||
this.$root.$bus.$on('canvasActivateRightNode', this.canvasActivateRightNode)
|
||||
this.$root.$bus.$on(
|
||||
'canvasActivateBottomNode',
|
||||
this.canvasActivateBottomNode
|
||||
)
|
||||
this.$root.$bus.$on('canvasActivateTopNode', this.canvasActivateTopNode)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$bus.$off('execCommand', this.execCommand)
|
||||
|
|
@ -296,30 +318,6 @@ export default {
|
|||
this.$root.$bus.$off('obTabDeactivate', this.onObTabDeactivate)
|
||||
this.$root.$bus.$off('toggleReadonly', this.onToggleReadonly)
|
||||
this.$root.$bus.$off('jumpToNodeByUid', this.jumpToNodeByUid)
|
||||
this.$root.$bus.$off('toggleActiveExpand', this.toggleActiveExpand)
|
||||
this.$root.$bus.$off('copyNode', this.copyNode)
|
||||
this.$root.$bus.$off('cutNode', this.cutNode)
|
||||
this.$root.$bus.$off('pasteNode', this.pasteNode)
|
||||
this.$root.$bus.$off('canvasZoomIn', this.canvasZoomIn)
|
||||
this.$root.$bus.$off('canvasZoomOut', this.canvasZoomOut)
|
||||
this.$root.$bus.$off('canvasBackToRoot', this.canvasBackToRoot)
|
||||
this.$root.$bus.$off('canvasZoomFit', this.canvasZoomFit)
|
||||
this.$root.$bus.$off('toggleBold', this.toggleBold)
|
||||
this.$root.$bus.$off('toggleItalic', this.toggleItalic)
|
||||
this.$root.$bus.$off('toggleUnderline', this.toggleUnderline)
|
||||
this.$root.$bus.$off('toggleStrikethrough', this.toggleStrikethrough)
|
||||
this.$root.$bus.$off('increaseFontSize', this.increaseFontSize)
|
||||
this.$root.$bus.$off('decreaseFontSize', this.decreaseFontSize)
|
||||
this.$root.$bus.$off('canvasActivateLeftNode', this.canvasActivateLeftNode)
|
||||
this.$root.$bus.$off(
|
||||
'canvasActivateRightNode',
|
||||
this.canvasActivateRightNode
|
||||
)
|
||||
this.$root.$bus.$off(
|
||||
'canvasActivateBottomNode',
|
||||
this.canvasActivateBottomNode
|
||||
)
|
||||
this.$root.$bus.$off('canvasActivateTopNode', this.canvasActivateTopNode)
|
||||
clearTimeout(this.autoSaveTimer)
|
||||
clearTimeout(this.savingTimer)
|
||||
this.mindMap.destroy()
|
||||
|
|
@ -362,13 +360,11 @@ export default {
|
|||
this.mindMap.resize()
|
||||
},
|
||||
|
||||
// 显示loading
|
||||
handleShowLoading() {
|
||||
this.enableShowLoading = true
|
||||
this.showLoading()
|
||||
},
|
||||
|
||||
// 渲染结束后关闭loading
|
||||
handleHideLoading() {
|
||||
if (this.enableShowLoading) {
|
||||
this.enableShowLoading = false
|
||||
|
|
@ -376,7 +372,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 获取思维导图数据,实际应该调接口获取
|
||||
getData() {
|
||||
this.mindMapData = JSON.parse(
|
||||
this.$root.$obsidianAPI.getInitMindMapData()
|
||||
|
|
@ -384,7 +379,6 @@ export default {
|
|||
this.mindMapConfig = this.$root.$obsidianAPI.getMindMapConfig() || {}
|
||||
},
|
||||
|
||||
// 初始化
|
||||
init() {
|
||||
let { root, layout, theme, view } = this.mindMapData
|
||||
const config = this.mindMapConfig
|
||||
|
|
@ -403,9 +397,7 @@ export default {
|
|||
show: (...args) => {
|
||||
this.$root.$bus.$emit('showNoteContent', ...args)
|
||||
},
|
||||
hide: () => {
|
||||
// this.$root.$bus.$emit('hideNoteContent')
|
||||
}
|
||||
hide: () => {}
|
||||
},
|
||||
openRealtimeRenderOnNodeTextEdit: false,
|
||||
enableAutoEnterTextEditWhenKeydown: true,
|
||||
|
|
@ -436,19 +428,13 @@ export default {
|
|||
defaultAssociativeLineText: this.$t('edit.defaultAssociativeLineText'),
|
||||
defaultOuterFrameText: this.$t('edit.defaultOuterFrameText'),
|
||||
handleIsSplitByWrapOnPasteCreateNewNode: () => {
|
||||
return this.$confirm(
|
||||
this.$t('edit.splitByWrap'),
|
||||
this.$t('edit.tip'),
|
||||
{
|
||||
confirmButtonText: this.$t('edit.yes'),
|
||||
cancelButtonText: this.$t('edit.no'),
|
||||
type: 'warning',
|
||||
customClass: 'smmCustomElConfirm'
|
||||
}
|
||||
)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.confirmSplitLineTaskResolve = resolve
|
||||
this.confirmSplitLineTaskReject = reject
|
||||
this.confirmSplitLineDialogVisible = true
|
||||
})
|
||||
},
|
||||
errorHandler: (code, err) => {
|
||||
console.error(err)
|
||||
switch (code) {
|
||||
case 'export_error':
|
||||
this.$root.$obsidianAPI.showTip(this.$t('edit.exportError'))
|
||||
|
|
@ -507,7 +493,10 @@ export default {
|
|||
const linkTitle = node.getData('hyperlinkTitle') || ''
|
||||
if (link) {
|
||||
if (!isHyperlink(link) && isObLinkText(linkTitle)) {
|
||||
const isNewTab = e.ctrlKey || e.metaKey
|
||||
const {
|
||||
embedLinkNewWindowOpen
|
||||
} = this.$root.$obsidianAPI.getSettings()
|
||||
const isNewTab = embedLinkNewWindowOpen || e.ctrlKey || e.metaKey
|
||||
this.$root.$obsidianAPI.openFile(link, isNewTab)
|
||||
} else {
|
||||
this.$root.$obsidianAPI.openWebLink(link)
|
||||
|
|
@ -520,42 +509,61 @@ export default {
|
|||
}
|
||||
return this.$root.$obsidianAPI.getResourcePath(url)
|
||||
},
|
||||
enableQuillFormatList: ['link'],
|
||||
customExtendQuill: Quill => {
|
||||
const Inline = Quill.import('blots/inline')
|
||||
class CustomLinkBlot extends Inline {
|
||||
static create(url) {
|
||||
const node = super.create()
|
||||
node.setAttribute('href', url)
|
||||
node.setAttribute('data-href', url)
|
||||
return node
|
||||
}
|
||||
static formats(node) {
|
||||
return node.getAttribute('href')
|
||||
}
|
||||
}
|
||||
CustomLinkBlot.blotName = 'link'
|
||||
CustomLinkBlot.tagName = 'a'
|
||||
Quill.register('formats/link', CustomLinkBlot)
|
||||
},
|
||||
handleNodePasteImg: async imgFile => {
|
||||
try {
|
||||
this.handleShowLoading()
|
||||
const {
|
||||
nodePasteImageNameFormat,
|
||||
useImgHosting
|
||||
} = this.$root.$obsidianAPI.getSettings()
|
||||
const file = new File(
|
||||
[imgFile],
|
||||
getFilenameWithExtension(
|
||||
this.$t('edit.pasteImage'),
|
||||
this.$root.$obsidianAPI.getFormatFileName(
|
||||
nodePasteImageNameFormat
|
||||
) || this.$t('edit.pasteImage'),
|
||||
imgFile.type
|
||||
),
|
||||
{
|
||||
type: imgFile.type
|
||||
}
|
||||
)
|
||||
const url = await this.$root.$obsidianAPI.saveFileToVault(file)
|
||||
const size = await getImageSize(
|
||||
this.$root.$obsidianAPI.getResourcePath(url)
|
||||
)
|
||||
let url = ''
|
||||
let viewUrl = ''
|
||||
if (useImgHosting) {
|
||||
url = await this.uploadImgToHosting(file)
|
||||
viewUrl = url
|
||||
} else {
|
||||
url = await this.$root.$obsidianAPI.saveFileToVault(file)
|
||||
viewUrl = this.$root.$obsidianAPI.getResourcePath(url)
|
||||
}
|
||||
const size = await getImageSize(viewUrl)
|
||||
this.handleHideLoading()
|
||||
return {
|
||||
url,
|
||||
size
|
||||
}
|
||||
} catch (error) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let fr = new FileReader()
|
||||
fr.readAsDataURL(imgFile)
|
||||
fr.onload = async e => {
|
||||
let url = e.target.result
|
||||
let size = await getImageSize(url)
|
||||
resolve({
|
||||
url,
|
||||
size
|
||||
})
|
||||
}
|
||||
fr.onerror = error => {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
this.handleHideLoading()
|
||||
this.$root.$obsidianAPI.showTip('上传失败')
|
||||
}
|
||||
},
|
||||
dynamicGetHyperlinkIcon: (node, link, linkTitle) => {
|
||||
|
|
@ -570,8 +578,7 @@ export default {
|
|||
style: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
enableDefaultShortcutKeys: false
|
||||
}
|
||||
})
|
||||
this.afterInitMindMap(this.tmpMindMap)
|
||||
},
|
||||
|
|
@ -580,8 +587,6 @@ export default {
|
|||
mindMap.on('node_tree_render_end', this.onFirstNodeTreeRenderEnd)
|
||||
|
||||
this.loadPlugins(mindMap)
|
||||
|
||||
// 转发事件
|
||||
;[
|
||||
'node_active',
|
||||
'data_change',
|
||||
|
|
@ -618,7 +623,6 @@ export default {
|
|||
this.bindSaveEvent()
|
||||
},
|
||||
|
||||
// 切换到其他标签页时结束一些状态
|
||||
onObTabDeactivate() {
|
||||
if (!this.mindMap) return
|
||||
this.mindMap.renderer.textEdit.hideEditTextBox()
|
||||
|
|
@ -632,11 +636,10 @@ export default {
|
|||
this.mindMap.emit('draw_click')
|
||||
},
|
||||
|
||||
// 首次节点树渲染完毕
|
||||
onFirstNodeTreeRenderEnd() {
|
||||
this.mindMap = this.tmpMindMap
|
||||
this.tmpMindMap = null
|
||||
// 判断是否需要定位到某个节点
|
||||
this.$root.$getCurrentMindMap = () => this.mindMap
|
||||
let nodeId = this.$root.$obsidianAPI.getInitLocationNodeId()
|
||||
if (nodeId) {
|
||||
nodeId = nodeId
|
||||
|
|
@ -657,19 +660,24 @@ export default {
|
|||
this.mindMap.execCommand('GO_TARGET_NODE', uid)
|
||||
},
|
||||
|
||||
// 加载相关插件
|
||||
handleSplitLine(type) {
|
||||
if (type === 1) {
|
||||
this.confirmSplitLineTaskReject()
|
||||
} else {
|
||||
this.confirmSplitLineTaskResolve(type === 2 ? 'tree' : 'array')
|
||||
}
|
||||
this.confirmSplitLineDialogVisible = false
|
||||
},
|
||||
|
||||
loadPlugins(mindMap) {
|
||||
if (this.isShowScrollbar) this.addScrollbarPlugin(mindMap)
|
||||
},
|
||||
|
||||
// 动态设置思维导图数据
|
||||
setData(data) {
|
||||
this.handleShowLoading()
|
||||
let rootNodeData = null
|
||||
if (data.root) {
|
||||
this.mindMapData = data
|
||||
this.mindMap.setFullData(data)
|
||||
rootNodeData = data.root
|
||||
} else {
|
||||
this.updateMindMapData(
|
||||
{
|
||||
|
|
@ -678,49 +686,49 @@ export default {
|
|||
false
|
||||
)
|
||||
this.mindMap.setData(data)
|
||||
rootNodeData = data
|
||||
}
|
||||
this.mindMap.view.reset()
|
||||
this.saveToLocal()
|
||||
},
|
||||
|
||||
// 从obsidian获取数据后更新思维导图数据
|
||||
onUpdateMindMapDataFromOb(data) {
|
||||
onUpdateMindMapDataFromOb(data, ignoreLayoutAndTheme = false) {
|
||||
try {
|
||||
this.isNotTriggerDataChange = true
|
||||
data = JSON.parse(data)
|
||||
this.mindMap.updateData(data.root)
|
||||
// 判断结构是否发生改变
|
||||
if (data.layout !== this.mindMap.getLayout()) {
|
||||
this.mindMap.setLayout(data.layout)
|
||||
}
|
||||
// 判断主题是否发生改变
|
||||
if (data.theme) {
|
||||
// 基础主题
|
||||
if (
|
||||
data.theme.template &&
|
||||
data.theme.template !== this.mindMap.getTheme()
|
||||
) {
|
||||
this.mindMap.setTheme(data.theme.template)
|
||||
if (ignoreLayoutAndTheme) {
|
||||
this.updateMindMapData(
|
||||
{
|
||||
root: data.root
|
||||
},
|
||||
false
|
||||
)
|
||||
} else {
|
||||
if (data.layout !== this.mindMap.getLayout()) {
|
||||
this.mindMap.setLayout(data.layout)
|
||||
}
|
||||
// 主题配置
|
||||
if (
|
||||
data.theme.config &&
|
||||
!isSameObject(
|
||||
data.theme.config,
|
||||
this.mindMap.getCustomThemeConfig()
|
||||
)
|
||||
) {
|
||||
this.mindMap.setThemeConfig(data.theme.config)
|
||||
if (data.theme) {
|
||||
if (
|
||||
data.theme.template &&
|
||||
data.theme.template !== this.mindMap.getTheme()
|
||||
) {
|
||||
this.mindMap.setTheme(data.theme.template)
|
||||
}
|
||||
if (
|
||||
data.theme.config &&
|
||||
!isSameObject(
|
||||
data.theme.config,
|
||||
this.mindMap.getCustomThemeConfig()
|
||||
)
|
||||
) {
|
||||
this.mindMap.setThemeConfig(data.theme.config)
|
||||
}
|
||||
}
|
||||
this.updateMindMapData(data, false)
|
||||
}
|
||||
this.updateMindMapData(data, false)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
} catch (error) {}
|
||||
},
|
||||
|
||||
// 更新思维导图数据
|
||||
updateMindMapData(data, isSaveToLocal = true) {
|
||||
this.mindMapData = {
|
||||
...this.mindMapData,
|
||||
|
|
@ -731,7 +739,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 存储数据当数据有变时
|
||||
bindSaveEvent() {
|
||||
this.$root.$bus.$on('data_change', data => {
|
||||
if (this.isNotTriggerDataChange) {
|
||||
|
|
@ -746,7 +753,6 @@ export default {
|
|||
this.updateMindMapData({ root: data }, false)
|
||||
})
|
||||
this.$root.$bus.$on('view_data_change', data => {
|
||||
// this.autoSave()
|
||||
clearTimeout(this.storeConfigTimer)
|
||||
this.storeConfigTimer = setTimeout(() => {
|
||||
this.updateMindMapData({ view: data }, false)
|
||||
|
|
@ -754,7 +760,6 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 自动保存
|
||||
autoSave() {
|
||||
clearTimeout(this.autoSaveTimer)
|
||||
this.autoSaveTimer = setTimeout(() => {
|
||||
|
|
@ -766,7 +771,6 @@ export default {
|
|||
clearTimeout(this.autoSaveTimer)
|
||||
},
|
||||
|
||||
// 手动触发保存
|
||||
manuallySave(...args) {
|
||||
clearTimeout(this.autoSaveTimer)
|
||||
this.saveToLocal(...args)
|
||||
|
|
@ -790,7 +794,8 @@ export default {
|
|||
}
|
||||
const {
|
||||
compressImageIsTransparent,
|
||||
saveCanvasViewData
|
||||
saveCanvasViewData,
|
||||
embedImageIsSeparateFile
|
||||
} = this.$root.$obsidianAPI.getSettings()
|
||||
if (!saveCanvasViewData) {
|
||||
data.view = null
|
||||
|
|
@ -799,58 +804,24 @@ export default {
|
|||
if (getSvg) {
|
||||
const hasImg = checkMindTreeHasImg(data.root)
|
||||
svgData = await this.mindMap.export(
|
||||
hasImg ? 'png' : 'svg',
|
||||
embedImageIsSeparateFile ? 'svg' : hasImg ? 'png' : 'svg',
|
||||
false,
|
||||
'',
|
||||
compressImageIsTransparent
|
||||
)
|
||||
}
|
||||
await this.$root.$obsidianAPI.saveMindMapData(
|
||||
JSON.stringify(data),
|
||||
svgData
|
||||
)
|
||||
await this.$root.$obsidianAPI.saveMindMapData('', svgData)
|
||||
if (isShowTip) {
|
||||
this.$root.$obsidianAPI.showTip(this.$t('edit.savingTip3'))
|
||||
}
|
||||
this.isSaving = false
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$root.$obsidianAPI.showTip(error || this.$t('edit.savingTip4'))
|
||||
this.isSaving = false
|
||||
}
|
||||
}, 200)
|
||||
},
|
||||
|
||||
// 更新内链到md文档
|
||||
updateInnerLink(data) {
|
||||
const obLinkMap = {}
|
||||
const textData = []
|
||||
const { supportObSearch } = this.$root.$obsidianAPI.getSettings()
|
||||
dfsTraverse(data.root, node => {
|
||||
if (supportObSearch) {
|
||||
textData.push(
|
||||
nodeRichTextToTextWithWrap(node.data.text) + ' ^' + node.data.uid
|
||||
)
|
||||
}
|
||||
// 超链接
|
||||
const { hyperlink, hyperlinkTitle } = node.data
|
||||
if (
|
||||
hyperlink &&
|
||||
hyperlinkTitle &&
|
||||
!isHyperlink(hyperlink) &&
|
||||
isObLinkText(hyperlinkTitle)
|
||||
) {
|
||||
obLinkMap[hyperlinkTitle] = true
|
||||
}
|
||||
})
|
||||
return {
|
||||
linkData: Object.keys(obLinkMap).map(key => {
|
||||
return key
|
||||
}),
|
||||
textData
|
||||
}
|
||||
},
|
||||
|
||||
// 发送最新数据给ob保存
|
||||
emitMindMapCurrentData() {
|
||||
try {
|
||||
|
|
@ -864,77 +835,67 @@ export default {
|
|||
if (!saveCanvasViewData) {
|
||||
data.view = null
|
||||
}
|
||||
const { linkData, textData } = this.updateInnerLink(data)
|
||||
const { linkData, textData } = parseInnerLinkAndText(
|
||||
data,
|
||||
this.$root.$obsidianAPI
|
||||
)
|
||||
this.$root.$obsidianAPI.getMindMapCurrentData(
|
||||
JSON.stringify(data),
|
||||
linkData,
|
||||
textData
|
||||
)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
} catch (error) {}
|
||||
},
|
||||
|
||||
// 重新渲染
|
||||
reRender() {
|
||||
this.mindMap.reRender()
|
||||
},
|
||||
|
||||
// 执行命令
|
||||
execCommand(...args) {
|
||||
this.mindMap.execCommand(...args)
|
||||
},
|
||||
|
||||
// 导出
|
||||
async export(...args) {
|
||||
try {
|
||||
this.showLoading()
|
||||
await this.mindMap.export(...args)
|
||||
this.hideLoading()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.hideLoading()
|
||||
}
|
||||
},
|
||||
|
||||
// 修改导出内边距
|
||||
onPaddingChange(data) {
|
||||
this.mindMap.updateConfig(data)
|
||||
},
|
||||
|
||||
// 加载滚动条插件
|
||||
addScrollbarPlugin(mindMap) {
|
||||
mindMap = mindMap || this.mindMap
|
||||
if (!mindMap) return
|
||||
mindMap.addPlugin(ScrollbarPlugin)
|
||||
},
|
||||
|
||||
// 移除滚动条插件
|
||||
removeScrollbarPlugin() {
|
||||
this.mindMap.removePlugin(ScrollbarPlugin)
|
||||
},
|
||||
|
||||
// 拖拽进入节点事件
|
||||
onDragEnter(e) {
|
||||
if (this.isReadonly) return
|
||||
e.preventDefault()
|
||||
const node = this.getDragEnterNode(e)
|
||||
if (node) {
|
||||
if (this.dragEnterNode && this.dragEnterNode.uid === node.uid) {
|
||||
// 和当前移入节点一样,啥也不干
|
||||
} else {
|
||||
// 否则先取消当前节点的激活状态
|
||||
if (this.dragEnterNode) {
|
||||
if (this.dragEnterNode.getData('isActive')) {
|
||||
this.mindMap.renderer.removeNodeFromActiveList(this.dragEnterNode)
|
||||
}
|
||||
}
|
||||
// 然后激活并存储当前移入节点
|
||||
this.mindMap.renderer.addNodeToActiveList(node)
|
||||
this.dragEnterNode = node
|
||||
this.mindMap.renderer.emitNodeActiveEvent()
|
||||
}
|
||||
} else {
|
||||
// 没有移入节点,取消当前节点的激活状态
|
||||
if (this.dragEnterNode) {
|
||||
this.mindMap.renderer.removeNodeFromActiveList(this.dragEnterNode)
|
||||
this.mindMap.renderer.emitNodeActiveEvent()
|
||||
|
|
@ -943,7 +904,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 获取拖拽进入的节点
|
||||
getDragEnterNode(e) {
|
||||
let res = null
|
||||
const { x, y } = this.mindMap.toPos(e.clientX, e.clientY)
|
||||
|
|
@ -966,7 +926,6 @@ export default {
|
|||
res = node
|
||||
return
|
||||
}
|
||||
// 概要节点
|
||||
if (node._generalizationList && node._generalizationList.length > 0) {
|
||||
let exist = false
|
||||
node._generalizationList.forEach(item => {
|
||||
|
|
@ -985,13 +944,17 @@ export default {
|
|||
})
|
||||
}
|
||||
}
|
||||
walk(this.mindMap.renderer.root)
|
||||
const treeList = this.mindMap.renderer.getAllRenderRoot()
|
||||
if (treeList.length) {
|
||||
treeList.forEach(item => {
|
||||
walk(item)
|
||||
})
|
||||
}
|
||||
return res
|
||||
},
|
||||
|
||||
// 拖拽插入图片或文件到某个节点
|
||||
// 支持:ob仓库文件、本地文件、图片、文字
|
||||
onDrop(e) {
|
||||
if (this.isReadonly) return
|
||||
e.preventDefault()
|
||||
if (!this.dragEnterNode) return
|
||||
const textData = e.dataTransfer?.getData('text/plain')
|
||||
|
|
@ -1002,118 +965,65 @@ export default {
|
|||
this.dragEnterNode = null
|
||||
},
|
||||
|
||||
// 插入拖拽数据到节点
|
||||
async insertDragDataToNode({ textData, files, node }) {
|
||||
let err = ''
|
||||
let fileUrl = ''
|
||||
// 先检查文本数据是否为ob文件
|
||||
if (textData) {
|
||||
const tFile = this.$root.$obsidianAPI.getFileFromUri(textData)
|
||||
// ob文件
|
||||
if (tFile) {
|
||||
fileUrl = tFile.path
|
||||
try {
|
||||
this.handleShowLoading()
|
||||
const { useImgHosting } = this.$root.$obsidianAPI.getSettings()
|
||||
let err = ''
|
||||
let fileUrl = ''
|
||||
if (textData) {
|
||||
const tFile = this.$root.$obsidianAPI.getFileFromUri(textData)
|
||||
// ob文件
|
||||
if (tFile) {
|
||||
fileUrl = tFile.path
|
||||
}
|
||||
}
|
||||
}
|
||||
// 没有文本数据,则检查文件数据
|
||||
if (!fileUrl && files.length > 0) {
|
||||
const file = files[0]
|
||||
// 将文件上传到ob仓库
|
||||
const result = await this.$root.$obsidianAPI.saveFileToVault(
|
||||
file,
|
||||
false
|
||||
)
|
||||
if (result) {
|
||||
fileUrl = result
|
||||
} else {
|
||||
err = this.$t('nodeHyperlink.tip4')
|
||||
if (!fileUrl && files.length > 0) {
|
||||
const file = files[0]
|
||||
let result = ''
|
||||
if (useImgHosting && /\.(jpg|jpeg|png|gif|webp)$/.test(file.name)) {
|
||||
result = await this.uploadImgToHosting(file)
|
||||
} else {
|
||||
result = await this.$root.$obsidianAPI.saveFileToVault(file, false)
|
||||
}
|
||||
if (result) {
|
||||
fileUrl = result
|
||||
} else {
|
||||
err = this.$t('nodeHyperlink.tip4')
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fileUrl) {
|
||||
this.$root.$obsidianAPI.showTip(err || this.$t('nodeHyperlink.tip5'))
|
||||
return
|
||||
}
|
||||
// if (node.isGeneratorFunction) {
|
||||
// console.log(node)
|
||||
// return
|
||||
// }
|
||||
// 图片直接以图片形式插入
|
||||
if (/\.(jpg|jpeg|png|gif|webp)$/.test(fileUrl)) {
|
||||
const viewUrl = this.$root.$obsidianAPI.getResourcePath(fileUrl)
|
||||
const { width, height } = await getImageSize(viewUrl)
|
||||
node.setImage({
|
||||
url: fileUrl,
|
||||
title: '',
|
||||
width,
|
||||
height
|
||||
})
|
||||
} else {
|
||||
// 文件以链接形式插入
|
||||
// 获取上传的文件的路径数据
|
||||
const result = this.$root.$obsidianAPI.createLinkInfoFromFilePath(
|
||||
fileUrl
|
||||
)
|
||||
if (!result) {
|
||||
this.$root.$obsidianAPI.showTip(this.$t('nodeHyperlink.tip2'))
|
||||
if (!fileUrl) {
|
||||
this.handleHideLoading()
|
||||
this.$root.$obsidianAPI.showTip(err || this.$t('nodeHyperlink.tip5'))
|
||||
return
|
||||
}
|
||||
node.setHyperlink(result.link, result.linkText || '')
|
||||
if (/\.(jpg|jpeg|png|gif|webp)$/.test(fileUrl)) {
|
||||
const viewUrl = useImgHosting
|
||||
? fileUrl
|
||||
: this.$root.$obsidianAPI.getResourcePath(fileUrl)
|
||||
const { width, height } = await getImageSize(viewUrl)
|
||||
node.setImage({
|
||||
url: fileUrl,
|
||||
title: '',
|
||||
width,
|
||||
height
|
||||
})
|
||||
} else {
|
||||
const result = this.$root.$obsidianAPI.createLinkInfoFromFilePath(
|
||||
fileUrl
|
||||
)
|
||||
if (!result) {
|
||||
this.$root.$obsidianAPI.showTip(this.$t('nodeHyperlink.tip2'))
|
||||
return
|
||||
}
|
||||
node.setHyperlink(result.link, result.linkText || '')
|
||||
}
|
||||
this.handleHideLoading()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.handleHideLoading()
|
||||
this.$root.$obsidianAPI.showTip('上传失败')
|
||||
}
|
||||
},
|
||||
|
||||
// 转发事件
|
||||
toggleActiveExpand() {
|
||||
this.mindMap.renderer.toggleActiveExpand()
|
||||
},
|
||||
copyNode() {
|
||||
this.mindMap.renderer.copy()
|
||||
},
|
||||
cutNode() {
|
||||
this.mindMap.renderer.cut()
|
||||
},
|
||||
pasteNode() {
|
||||
this.mindMap.renderer.paste()
|
||||
},
|
||||
canvasZoomIn() {
|
||||
this.mindMap.view.enlarge()
|
||||
},
|
||||
canvasZoomOut() {
|
||||
this.mindMap.view.narrow()
|
||||
},
|
||||
canvasBackToRoot() {
|
||||
this.mindMap.renderer.setRootNodeCenter()
|
||||
},
|
||||
canvasZoomFit() {
|
||||
this.mindMap.view.fit()
|
||||
},
|
||||
toggleBold() {
|
||||
this.mindMap.renderer.toggleNodeBold()
|
||||
},
|
||||
toggleItalic() {
|
||||
this.mindMap.renderer.toggleNodeItalic()
|
||||
},
|
||||
toggleUnderline() {
|
||||
this.mindMap.renderer.toggleNodeUnderline()
|
||||
},
|
||||
toggleStrikethrough() {
|
||||
this.mindMap.renderer.toggleNodeLineThrough()
|
||||
},
|
||||
increaseFontSize() {
|
||||
this.mindMap.renderer.increaseNodeFontSize()
|
||||
},
|
||||
decreaseFontSize() {
|
||||
this.mindMap.renderer.decreaseNodeFontSize()
|
||||
},
|
||||
canvasActivateLeftNode() {
|
||||
this.mindMap.keyboardNavigation.onLeftKeyUp()
|
||||
},
|
||||
canvasActivateRightNode() {
|
||||
this.mindMap.keyboardNavigation.onRightKeyUp()
|
||||
},
|
||||
canvasActivateBottomNode() {
|
||||
this.mindMap.keyboardNavigation.onDownKeyUp()
|
||||
},
|
||||
canvasActivateTopNode() {
|
||||
this.mindMap.keyboardNavigation.onUpKeyUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1134,11 +1044,32 @@ export default {
|
|||
top: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// left: 100px;
|
||||
// top: 100px;
|
||||
// right: 100px;
|
||||
// bottom: 100px;
|
||||
.confirmSplitLineDialog {
|
||||
/deep/ .el-dialog__body {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.dialogContent {
|
||||
display: flex;
|
||||
|
||||
&.isDark {
|
||||
.text {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: #e6a23c;
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.text {
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@
|
|||
:width="'90%'"
|
||||
:top="isMobile ? '20px' : '15vh'"
|
||||
:modal-append-to-body="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<div class="exportContainer" :class="{ isDark: isDark }">
|
||||
<!-- 导出类型选择 -->
|
||||
<div class="downloadTypeSelectBox">
|
||||
<!-- 类型列表 -->
|
||||
<div class="downloadTypeList smmCustomScrollbar">
|
||||
<div
|
||||
class="downloadTypeItem"
|
||||
|
|
@ -31,9 +31,7 @@
|
|||
<div class="icon checked el-icon-check"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 类型内容 -->
|
||||
<div class="downloadTypeContent">
|
||||
<!-- 文件名称输入 -->
|
||||
<div class="nameInputBox">
|
||||
<div class="nameInput">
|
||||
<span class="name">{{ $t('export.filename') }}</span>
|
||||
|
|
@ -46,7 +44,6 @@
|
|||
</div>
|
||||
<span class="closeBtn el-icon-close" @click="cancel"></span>
|
||||
</div>
|
||||
<!-- 配置 -->
|
||||
<div class="contentBox smmCustomScrollbar">
|
||||
<div class="contentRow">
|
||||
<div class="contentName">{{ $t('export.format') }}</div>
|
||||
|
|
@ -116,7 +113,7 @@
|
|||
</div>
|
||||
<div class="valueSubItem">
|
||||
<el-checkbox
|
||||
v-show="['png', 'pdf'].includes(exportType)"
|
||||
v-show="['png', 'pdf', 'svg'].includes(exportType)"
|
||||
v-model="isTransparent"
|
||||
>{{ $t('export.isTransparent') }}</el-checkbox
|
||||
>
|
||||
|
|
@ -130,7 +127,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 按钮 -->
|
||||
<div class="btnList">
|
||||
<el-button @click="cancel" size="small" class="smmElButtonSmall">{{
|
||||
$t('dialog.cancel')
|
||||
|
|
@ -156,7 +152,6 @@ import { createUid } from 'simple-mind-map/src/utils/index'
|
|||
import MarkdownIt from 'markdown-it'
|
||||
import { isHyperlink, isNormalUrl, imageUrlToBase64 } from '@/utils'
|
||||
|
||||
// 导出
|
||||
let md = null
|
||||
export default {
|
||||
data() {
|
||||
|
|
@ -197,7 +192,9 @@ export default {
|
|||
},
|
||||
|
||||
noOptions() {
|
||||
return ['md', 'xmind', 'txt'].includes(this.exportType)
|
||||
return ['md', 'xmind', 'txt', 'xlsx', 'mm'].includes(
|
||||
this.exportType
|
||||
)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
|
@ -225,6 +222,12 @@ export default {
|
|||
},
|
||||
|
||||
confirm() {
|
||||
if (this.exportType === 'xlsx') {
|
||||
if (this.fileName.length > 31) {
|
||||
this.$message.error('导出Excel文件名称长度不能超过31个字符')
|
||||
return
|
||||
}
|
||||
}
|
||||
this.setExtraTextOnExport(this.extraText)
|
||||
if (this.exportType === 'svg') {
|
||||
this.$root.$bus.$emit(
|
||||
|
|
@ -232,11 +235,7 @@ export default {
|
|||
this.exportType,
|
||||
true,
|
||||
this.fileName,
|
||||
`* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}`
|
||||
this.isTransparent
|
||||
)
|
||||
} else if (['smm', 'json'].includes(this.exportType)) {
|
||||
this.$root.$bus.$emit(
|
||||
|
|
@ -266,6 +265,22 @@ export default {
|
|||
this.isTransparent,
|
||||
this.isFitBg
|
||||
)
|
||||
} else if (this.exportType === 'mm') {
|
||||
this.$root.$bus.$emit('export', this.exportType, true, this.fileName, {
|
||||
transformNote: note => {
|
||||
if (!md) {
|
||||
md = new MarkdownIt()
|
||||
}
|
||||
return md.render(note)
|
||||
},
|
||||
transformImage: img => {
|
||||
if (isHyperlink(img)) {
|
||||
return img
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (this.exportType === 'xmind') {
|
||||
this.$root.$bus.$emit(
|
||||
'export',
|
||||
|
|
@ -274,6 +289,17 @@ export default {
|
|||
this.fileName,
|
||||
this.transformMindMapData
|
||||
)
|
||||
} else if (this.exportType === 'md') {
|
||||
const {
|
||||
nodeTextToMarkdownTitleMaxLevel
|
||||
} = this.$root.$obsidianAPI.getSettings()
|
||||
this.$root.$bus.$emit(
|
||||
'export',
|
||||
this.exportType,
|
||||
true,
|
||||
this.fileName,
|
||||
nodeTextToMarkdownTitleMaxLevel
|
||||
)
|
||||
} else {
|
||||
this.$root.$bus.$emit('export', this.exportType, true, this.fileName)
|
||||
}
|
||||
|
|
@ -304,7 +330,6 @@ export default {
|
|||
root.data.image = key
|
||||
resolve()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
|
|
|||
221
plugin/src/pages/Edit/components/FileSelectList.vue
Normal file
221
plugin/src/pages/Edit/components/FileSelectList.vue
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
<template>
|
||||
<div
|
||||
class="fileSelectListContainer"
|
||||
:style="{ left: position.left + 'px', top: position.top + 'px' }"
|
||||
:class="{ isDark: isDark }"
|
||||
@click.stop.passive
|
||||
@mousedown.stop
|
||||
>
|
||||
<div class="fileSelectListSearch">
|
||||
<el-input
|
||||
v-model="searchText"
|
||||
:placeholder="$t('fileSelectList.searchPlaceholder')"
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="fileSelectList smmCustomScrollbar" ref="fileSelectListRef">
|
||||
<div
|
||||
class="fileSelectItem"
|
||||
v-for="(item, index) in list"
|
||||
:key="item.path"
|
||||
:class="{ active: activeIndex === index }"
|
||||
@click="onSelect(item)"
|
||||
>
|
||||
<div class="fileSelectItemTitle">{{ item.basename }}</div>
|
||||
<div
|
||||
class="fileSelectItemNote"
|
||||
v-if="item.folder && item.folder !== '/'"
|
||||
>
|
||||
{{ item.folder }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
position: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchText: '',
|
||||
allList: null,
|
||||
list: [],
|
||||
searchTimer: null,
|
||||
activeIndex: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isDark: state => state.localConfig.isDark
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
searchText() {
|
||||
clearTimeout(this.searchTimer)
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.activeIndex = 0
|
||||
this.getFileList()
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getFileList()
|
||||
document.body.addEventListener('keydown', this.onKeydown)
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearTimeout(this.searchTimer)
|
||||
document.body.removeEventListener('keydown', this.onKeydown)
|
||||
},
|
||||
methods: {
|
||||
getFileList() {
|
||||
let list = []
|
||||
if (this.allList) {
|
||||
list = this.allList
|
||||
} else {
|
||||
list = this.allList = this.$root.$obsidianAPI
|
||||
.getObAllFiles()
|
||||
.map(item => {
|
||||
return {
|
||||
folder: item.parent.path,
|
||||
path: item.path,
|
||||
basename: item.basename
|
||||
}
|
||||
})
|
||||
}
|
||||
if (this.searchText.trim()) {
|
||||
list = list.filter(item =>
|
||||
item.basename.includes(this.searchText.trim())
|
||||
)
|
||||
}
|
||||
this.list = list.slice(0, 50)
|
||||
},
|
||||
|
||||
onSelect(item) {
|
||||
this.$emit('select', item)
|
||||
},
|
||||
|
||||
onKeydown(e) {
|
||||
if ([38, 40, 13].includes(e.keyCode)) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (e.keyCode === 38) {
|
||||
this.activeIndex = Math.max(0, this.activeIndex - 1)
|
||||
this.updateScroll()
|
||||
} else if (e.keyCode === 40) {
|
||||
this.activeIndex = Math.min(
|
||||
this.list.length - 1,
|
||||
this.activeIndex + 1
|
||||
)
|
||||
this.updateScroll()
|
||||
} else if (e.keyCode === 13) {
|
||||
if (this.list[this.activeIndex]) {
|
||||
this.onSelect(this.list[this.activeIndex])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateScroll() {
|
||||
const activeItem = this.$refs.fileSelectListRef.children[this.activeIndex]
|
||||
if (activeItem) {
|
||||
const activeItemRect = activeItem.getBoundingClientRect()
|
||||
const fileSelectListRect = this.$refs.fileSelectListRef.getBoundingClientRect()
|
||||
if (activeItemRect.top < fileSelectListRect.top) {
|
||||
this.$refs.fileSelectListRef.scrollTop -= activeItemRect.height + 10
|
||||
} else if (activeItemRect.bottom > fileSelectListRect.bottom) {
|
||||
this.$refs.fileSelectListRef.scrollTop += activeItemRect.height + 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.fileSelectListContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.028),
|
||||
0px 3.4px 6.7px rgba(0, 0, 0, 0.042), 0px 15px 30px rgba(0, 0, 0, 0.07);
|
||||
border: 1px solid #f6f6f6;
|
||||
z-index: 3001;
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
overflow: hidden;
|
||||
padding: 8px 10px;
|
||||
|
||||
&.isDark {
|
||||
background-color: #363b3f;
|
||||
border-color: #363b3f;
|
||||
|
||||
.fileSelectList {
|
||||
.fileSelectItem {
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: hsla(0, 0%, 100%, 0.05);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: hsla(0, 0%, 100%, 0.05);
|
||||
}
|
||||
|
||||
.fileSelectItemNote {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fileSelectListSearch {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fileSelectList {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
.fileSelectItem {
|
||||
cursor: pointer;
|
||||
padding: 8px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 15px;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.067);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: rgba(0, 0, 0, 0.067);
|
||||
}
|
||||
|
||||
.fileSelectItemTitle {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fileSelectItemNote {
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
color: #5c5c5c;
|
||||
width: 100%;
|
||||
flex-basis: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -6,7 +6,20 @@
|
|||
:visible.sync="dialogVisible"
|
||||
width="350px"
|
||||
:modal-append-to-body="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<div class="dragUploadBox">
|
||||
<div class="dragUploadAreaBox">
|
||||
<label
|
||||
class="dragUploadInputArea"
|
||||
@dragenter.stop.prevent
|
||||
@dragover.stop.prevent
|
||||
@drop.stop.prevent="onDrop"
|
||||
>{{ $t('import.dragTip') }}</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<el-upload
|
||||
action="x"
|
||||
:accept="supportFileStr"
|
||||
|
|
@ -24,7 +37,18 @@
|
|||
}}</el-button>
|
||||
</div>
|
||||
<div slot="tip" class="el-upload__tip">
|
||||
{{ $t('import.support') }}{{ supportFileStr }}{{ $t('import.file') }}
|
||||
<div>
|
||||
<el-checkbox
|
||||
v-model="transformFormula"
|
||||
style="margin-bottom: 5px;margin-top: 6px;"
|
||||
>转换数学公式</el-checkbox
|
||||
>
|
||||
</div>
|
||||
<div class="tip">
|
||||
{{ $t('import.support') }}{{ supportFileStr
|
||||
}}{{ $t('import.file') }}
|
||||
</div>
|
||||
<div class="warningTip">{{ $t('import.warningTip') }}</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
|
|
@ -45,8 +69,9 @@
|
|||
:title="$t('import.xmindCanvasSelectDialogTitle')"
|
||||
:visible.sync="xmindCanvasSelectDialogVisible"
|
||||
width="300px"
|
||||
:show-close="false"
|
||||
:modal-append-to-body="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<el-radio-group v-model="selectCanvas" class="canvasList">
|
||||
<el-radio
|
||||
|
|
@ -72,10 +97,17 @@
|
|||
<script>
|
||||
import xmind from 'simple-mind-map/src/parse/xmind.js'
|
||||
import markdown from 'simple-mind-map/src/parse/markdown.js'
|
||||
import txt from 'simple-mind-map/src/parse/txt.js'
|
||||
import { mapMutations } from 'vuex'
|
||||
import { base64ToFile } from '@/utils'
|
||||
import { base64ToFile, tFileToFile } from '@/utils'
|
||||
import { getDataFromDt } from '@/components/ImgUpload/utils'
|
||||
|
||||
const mimeTypes = {
|
||||
md: 'text/markdown',
|
||||
json: 'application/json',
|
||||
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
}
|
||||
|
||||
// 导入
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -84,12 +116,13 @@ export default {
|
|||
selectPromiseResolve: null,
|
||||
xmindCanvasSelectDialogVisible: false,
|
||||
selectCanvas: '',
|
||||
canvasList: []
|
||||
canvasList: [],
|
||||
transformFormula: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
supportFileStr() {
|
||||
return '.smm,.json,.xmind,.md'
|
||||
return '.smm,.json,.xmind,.md,.txt'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -115,10 +148,32 @@ export default {
|
|||
},
|
||||
|
||||
getRegexp() {
|
||||
return new RegExp(`\.(smm|json|xmind|md)$`)
|
||||
return new RegExp(`\.(smm|json|xmind|md|txt)$`)
|
||||
},
|
||||
|
||||
async onDrop(e) {
|
||||
const { file, text } = await getDataFromDt(e.dataTransfer)
|
||||
if (file) {
|
||||
this.onChange({
|
||||
raw: file,
|
||||
name: file.name
|
||||
})
|
||||
} else if (text) {
|
||||
const tFile = this.$root.$obsidianAPI.getFileFromUri(text)
|
||||
if (tFile) {
|
||||
const fileRaw = await tFileToFile(
|
||||
tFile,
|
||||
this.$root.$obsidianAPI.getObsidianApp(),
|
||||
mimeTypes
|
||||
)
|
||||
this.onChange({
|
||||
raw: fileRaw,
|
||||
name: fileRaw.name
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 文件选择
|
||||
onChange(file) {
|
||||
if (!this.getRegexp().test(file.name)) {
|
||||
this.$root.$obsidianAPI.showTip(
|
||||
|
|
@ -132,22 +187,18 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 移除文件
|
||||
onRemove(file, fileList) {
|
||||
this.fileList = fileList
|
||||
},
|
||||
|
||||
// 数量超出限制
|
||||
onExceed() {
|
||||
this.$root.$obsidianAPI.showTip(this.$t('import.maxFileNum'))
|
||||
},
|
||||
|
||||
// 取消
|
||||
cancel() {
|
||||
this.dialogVisible = false
|
||||
},
|
||||
|
||||
// 确定
|
||||
confirm() {
|
||||
if (this.fileList.length <= 0) {
|
||||
return this.$root.$obsidianAPI.showTip(this.$t('import.notSelectTip'))
|
||||
|
|
@ -160,12 +211,13 @@ export default {
|
|||
this.handleXmind(file)
|
||||
} else if (/\.md$/.test(file.name)) {
|
||||
this.handleMd(file)
|
||||
} else if (/\.txt$/.test(file.name)) {
|
||||
this.handleTxt(file)
|
||||
}
|
||||
this.cancel()
|
||||
this.setActiveSidebar(null)
|
||||
},
|
||||
|
||||
// 处理.smm文件
|
||||
handleSmm(file) {
|
||||
let fileReader = new FileReader()
|
||||
fileReader.readAsText(file.raw)
|
||||
|
|
@ -179,13 +231,11 @@ export default {
|
|||
this.$root.$bus.$emit('setData', data)
|
||||
this.$root.$obsidianAPI.showTip(this.$t('import.importSuccess'))
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$root.$obsidianAPI.showTip(this.$t('import.fileParsingFailed'))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 处理.xmind文件
|
||||
async handleXmind(file) {
|
||||
try {
|
||||
let data = await xmind.parseXmindFile(file.raw, content => {
|
||||
|
|
@ -194,23 +244,38 @@ export default {
|
|||
this.selectPromiseResolve = resolve
|
||||
})
|
||||
})
|
||||
this.handleDataFormula(data)
|
||||
await this.transformImgToFile(data)
|
||||
this.$root.$bus.$emit('setData', data)
|
||||
this.$root.$obsidianAPI.showTip(this.$t('import.importSuccess'))
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$root.$obsidianAPI.showTip(this.$t('import.fileParsingFailed'))
|
||||
}
|
||||
},
|
||||
|
||||
// 显示xmind文件的多个画布选择弹窗
|
||||
handleTxt(file) {
|
||||
const fileReader = new FileReader()
|
||||
fileReader.readAsText(file.raw)
|
||||
fileReader.onload = async evt => {
|
||||
try {
|
||||
const data = txt.txtTo(evt.target.result)
|
||||
this.handleDataFormula(data)
|
||||
this.$root.$bus.$emit('setData', data)
|
||||
this.$root.$obsidianAPI.showTip(this.$t('import.importSuccess'))
|
||||
} catch (error) {
|
||||
this.$root.$obsidianAPI.showTip(
|
||||
error || this.$t('import.fileParsingFailed')
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
showSelectXmindCanvasDialog(content) {
|
||||
this.canvasList = content
|
||||
this.selectCanvas = 0
|
||||
this.xmindCanvasSelectDialogVisible = true
|
||||
},
|
||||
|
||||
// 确认导入指定的画布
|
||||
confirmSelect() {
|
||||
this.selectPromiseResolve(this.canvasList[this.selectCanvas])
|
||||
this.xmindCanvasSelectDialogVisible = false
|
||||
|
|
@ -218,7 +283,31 @@ export default {
|
|||
this.selectCanvas = 0
|
||||
},
|
||||
|
||||
// 处理markdown文件
|
||||
convertMathFormula(text) {
|
||||
const html = text.replace(/\$([^\$]+)\$/g, (match, p1) => {
|
||||
return `<span class="ql-formula" data-value="${p1}">
|
||||
${this.$root.$getCurrentMindMap().formula.renderToString(p1)}
|
||||
</span>`
|
||||
})
|
||||
return `<p>
|
||||
${html}
|
||||
</p>`
|
||||
},
|
||||
|
||||
handleDataFormula(data) {
|
||||
if (!this.transformFormula) return
|
||||
const walk = node => {
|
||||
if (node.data.text && /\$([^\$]+)\$/g.test(node.data.text)) {
|
||||
node.data.text = this.convertMathFormula(node.data.text)
|
||||
node.data.richText = true
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children.forEach(walk)
|
||||
}
|
||||
}
|
||||
walk(data)
|
||||
},
|
||||
|
||||
async handleMd(file) {
|
||||
let fileReader = new FileReader()
|
||||
fileReader.readAsText(file.raw)
|
||||
|
|
@ -229,6 +318,7 @@ export default {
|
|||
false,
|
||||
file.raw.name
|
||||
)
|
||||
this.handleDataFormula(data)
|
||||
if (!data) {
|
||||
this.$root.$obsidianAPI.showTip(this.$t('import.mdImportFailTip'))
|
||||
this.$root.$bus.$emit('hideLoading')
|
||||
|
|
@ -237,13 +327,11 @@ export default {
|
|||
this.$root.$bus.$emit('setData', data)
|
||||
this.$root.$obsidianAPI.showTip(this.$t('import.importSuccess'))
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$root.$obsidianAPI.showTip(this.$t('import.fileParsingFailed'))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 导入指定文件
|
||||
handleImportFile(file) {
|
||||
this.onChange({
|
||||
raw: file,
|
||||
|
|
@ -279,7 +367,6 @@ export default {
|
|||
root.data.image = res
|
||||
resolve()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
|
@ -303,6 +390,53 @@ export default {
|
|||
|
||||
<style lang="less" scoped>
|
||||
.nodeImportDialog {
|
||||
/deep/ .el-dialog__body {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.warningTip {
|
||||
color: #f56c6c;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.dragUploadBox {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&.isDark {
|
||||
.dragUploadAreaBox {
|
||||
.dragUploadInputArea {
|
||||
background-color: #363b3f;
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dragUploadAreaBox {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.dragUploadInputArea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 20px;
|
||||
color: rgba(51, 51, 51, 0.4);
|
||||
background-color: hsla(0, 0%, 87%, 0.6);
|
||||
border: none;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: normal;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btnList {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
<script>
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
|
||||
// 鼠标操作设置
|
||||
export default {
|
||||
props: {
|
||||
mindMap: {
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ export default {
|
|||
)
|
||||
},
|
||||
methods: {
|
||||
// 切换显示小地图
|
||||
toggle_mini_map(show) {
|
||||
this.showMiniMap = show
|
||||
this.$nextTick(() => {
|
||||
|
|
@ -103,7 +102,6 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 思维导图数据改变,更新小地图
|
||||
data_change() {
|
||||
if (!this.showMiniMap) {
|
||||
return
|
||||
|
|
@ -114,7 +112,6 @@ export default {
|
|||
}, 500)
|
||||
},
|
||||
|
||||
// 计算容器宽度
|
||||
setSize() {
|
||||
clearTimeout(this.setSizeTimer)
|
||||
this.setSizeTimer = setTimeout(() => {
|
||||
|
|
@ -128,14 +125,12 @@ export default {
|
|||
}, 300)
|
||||
},
|
||||
|
||||
// 获取宽高
|
||||
init() {
|
||||
let { width, height } = this.$refs.navigatorBox.getBoundingClientRect()
|
||||
this.boxWidth = width - 2 // 减去边框
|
||||
this.boxHeight = height - 2
|
||||
},
|
||||
|
||||
// 渲染小地图
|
||||
drawMiniMap() {
|
||||
let {
|
||||
getImgUrl,
|
||||
|
|
@ -144,7 +139,6 @@ export default {
|
|||
miniMapBoxLeft,
|
||||
miniMapBoxTop
|
||||
} = this.mindMap.miniMap.calculationMiniMap(this.boxWidth, this.boxHeight)
|
||||
// 渲染到小地图
|
||||
getImgUrl(img => {
|
||||
this.mindMapImg = img
|
||||
})
|
||||
|
|
@ -154,17 +148,14 @@ export default {
|
|||
this.svgBoxTop = miniMapBoxTop
|
||||
},
|
||||
|
||||
// 小地图鼠标按下事件
|
||||
onMousedown(e) {
|
||||
this.mindMap.miniMap.onMousedown(e)
|
||||
},
|
||||
|
||||
// 小地图鼠标移动事件
|
||||
onMousemove(e) {
|
||||
this.mindMap.miniMap.onMousemove(e)
|
||||
},
|
||||
|
||||
// 鼠标松开事件,最好绑定要window
|
||||
onMouseup(e) {
|
||||
if (!this.withTransition) {
|
||||
this.withTransition = true
|
||||
|
|
@ -172,17 +163,14 @@ export default {
|
|||
if (this.mindMap.miniMap) this.mindMap.miniMap.onMouseup(e)
|
||||
},
|
||||
|
||||
// 视口框的鼠标按下事件
|
||||
onViewBoxMousedown(e) {
|
||||
this.mindMap.miniMap.onViewBoxMousedown(e)
|
||||
},
|
||||
|
||||
// 视口框的鼠标移动事件
|
||||
onViewBoxMousemove(e) {
|
||||
this.mindMap.miniMap.onViewBoxMousemove(e)
|
||||
},
|
||||
|
||||
// 视口框的位置大小改变了,需要更新
|
||||
onViewBoxPositionChange({ left, right, top, bottom }) {
|
||||
this.withTransition = false
|
||||
this.viewBoxStyle.left = left
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
:aria-label="$t('navigatorToolbar.searchReplace')"
|
||||
data-tooltip-position="top"
|
||||
>
|
||||
<div class="btn iconfont iconsousuo" @click="showSearch"></div>
|
||||
<div class="btn iconfont iconsousuo1" @click="showSearch"></div>
|
||||
</div>
|
||||
<div class="item" v-if="!isMobile">
|
||||
<MouseAction :isDark="isDark" :mindMap="mindMap"></MouseAction>
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
<Count
|
||||
:mindMap="mindMap"
|
||||
v-if="!isZenMode"
|
||||
style="margin-left: 6px"
|
||||
style="margin-left: 6px;"
|
||||
></Count>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -95,7 +95,6 @@ import { mapState, mapMutations } from 'vuex'
|
|||
import pkg from 'simple-mind-map/package.json'
|
||||
import Count from './Count.vue'
|
||||
|
||||
// 导航器工具栏
|
||||
export default {
|
||||
components: {
|
||||
Scale,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
:width="'90%'"
|
||||
:top="isMobile ? '20px' : '15vh'"
|
||||
:modal-append-to-body="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<el-tabs v-model="activeType" @tab-click="handleTypeClick">
|
||||
<el-tab-pane
|
||||
|
|
@ -120,7 +122,6 @@
|
|||
import { mapState } from 'vuex'
|
||||
import { isHyperlink, isObLinkText } from '@/utils'
|
||||
|
||||
// 节点超链接内容设置
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -135,7 +136,7 @@ export default {
|
|||
selectedFilePath: '',
|
||||
searchFileText: '',
|
||||
searchTimer: null,
|
||||
localFileList: [] // 本地文件列表
|
||||
localFileList: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -216,7 +217,6 @@ export default {
|
|||
}
|
||||
this.fileList = list.slice(0, 50)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -308,17 +308,14 @@ export default {
|
|||
this.localFileList = []
|
||||
},
|
||||
|
||||
// 文件选择
|
||||
onChange(file) {
|
||||
this.localFileList.push(file)
|
||||
},
|
||||
|
||||
// 移除文件
|
||||
onRemove(file, fileList) {
|
||||
this.localFileList = fileList
|
||||
},
|
||||
|
||||
// 数量超出限制
|
||||
onExceed() {
|
||||
this.$root.$obsidianAPI.showTip(this.$t('import.maxFileNum'))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
:visible.sync="dialogVisible"
|
||||
width="500"
|
||||
:modal-append-to-body="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<div class="item" v-for="item in nodeIconList" :key="item.name">
|
||||
<div class="title">{{ item.name }}</div>
|
||||
|
|
@ -28,7 +30,6 @@
|
|||
import { nodeIconList } from 'simple-mind-map/src/svg/icons'
|
||||
import icon from '@/config/icon'
|
||||
|
||||
// 节点图标内容设置
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -70,18 +71,15 @@ export default {
|
|||
let index = this.iconList.findIndex(item => {
|
||||
return item === key
|
||||
})
|
||||
// 删除icon
|
||||
if (index !== -1) {
|
||||
this.iconList.splice(index, 1)
|
||||
} else {
|
||||
let typeIndex = this.iconList.findIndex(item => {
|
||||
return item.split('_')[0] === type
|
||||
})
|
||||
// 替换icon
|
||||
if (typeIndex !== -1) {
|
||||
this.iconList.splice(typeIndex, 1, key)
|
||||
} else {
|
||||
// 增加icon
|
||||
this.iconList.push(key)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@
|
|||
</el-select>
|
||||
</div>
|
||||
<div class="boxContent smmCustomScrollbar" ref="boxContentRef">
|
||||
<!-- 图标 -->
|
||||
<div class="iconBox" v-if="activeName === 'icon'">
|
||||
<div
|
||||
class="item"
|
||||
|
|
@ -52,7 +51,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 贴纸 -->
|
||||
<div class="imageBox" v-if="activeName === 'image'">
|
||||
<div
|
||||
class="item"
|
||||
|
|
@ -143,7 +141,7 @@ export default {
|
|||
if (this.activeNodes.length === 1) {
|
||||
let firstNode = this.activeNodes[0]
|
||||
this.nodeImage = firstNode.getData('image') || ''
|
||||
this.iconList = firstNode.getData('icon') || [] // 回显图标
|
||||
this.iconList = firstNode.getData('icon') || []
|
||||
} else {
|
||||
this.nodeImage = []
|
||||
this.iconList = []
|
||||
|
|
@ -158,12 +156,10 @@ export default {
|
|||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 获取图标渲染方式
|
||||
getHtml(icon) {
|
||||
return /^<svg/.test(icon) ? icon : `<img src="${icon}" />`
|
||||
},
|
||||
|
||||
// 设置icon
|
||||
setIcon(type, name) {
|
||||
this.activeNodes.forEach(node => {
|
||||
const iconList = [...(node.getData('icon') || [])]
|
||||
|
|
@ -171,18 +167,15 @@ export default {
|
|||
let index = iconList.findIndex(item => {
|
||||
return item === key
|
||||
})
|
||||
// 删除icon
|
||||
if (index !== -1) {
|
||||
iconList.splice(index, 1)
|
||||
} else {
|
||||
let typeIndex = iconList.findIndex(item => {
|
||||
return item.split('_')[0] === type
|
||||
})
|
||||
// 替换icon
|
||||
if (typeIndex !== -1) {
|
||||
iconList.splice(typeIndex, 1, key)
|
||||
} else {
|
||||
// 增加icon
|
||||
iconList.push(key)
|
||||
}
|
||||
}
|
||||
|
|
@ -193,7 +186,6 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 设置贴纸
|
||||
setImage(image) {
|
||||
this.activeNodes.forEach(node => {
|
||||
this.nodeImage = image.url
|
||||
|
|
|
|||
|
|
@ -151,30 +151,25 @@ export default {
|
|||
this.close()
|
||||
},
|
||||
|
||||
// 获取图标渲染方式
|
||||
getHtml(icon) {
|
||||
return /^<svg/.test(icon) ? icon : `<img src="${icon}" />`
|
||||
},
|
||||
|
||||
// 设置icon
|
||||
setIcon(name) {
|
||||
let key = this.iconType + '_' + name
|
||||
let index = this.nodeIconList.findIndex(item => {
|
||||
return item === key
|
||||
})
|
||||
// 删除icon
|
||||
if (index !== -1) {
|
||||
this.nodeIconList.splice(index, 1)
|
||||
} else {
|
||||
let typeIndex = this.nodeIconList.findIndex(item => {
|
||||
return item.split('_')[0] === this.iconType
|
||||
})
|
||||
// 替换icon
|
||||
if (typeIndex !== -1) {
|
||||
this.nodeIconList.splice(typeIndex, 1, key)
|
||||
this.iconName = name
|
||||
} else {
|
||||
// 增加icon
|
||||
this.nodeIconList.push(key)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,57 +8,64 @@
|
|||
:top="isMobile ? '20px' : '15vh'"
|
||||
:modal-append-to-body="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<div class="tip">{{ $t('nodeImage.tip') }}</div>
|
||||
<div class="title">1.{{ $t('nodeImage.selectLocal') }}</div>
|
||||
<ImgUpload
|
||||
ref="ImgUpload"
|
||||
v-model="img"
|
||||
style="margin-bottom: 12px;"
|
||||
@selectObFile="onImgUploadSelectObFile"
|
||||
></ImgUpload>
|
||||
<div class="inputBox">
|
||||
<span class="inputTitle" :title="$t('nodeImage.selectVault')"
|
||||
>2.{{ $t('nodeImage.selectVault') }}</span
|
||||
>
|
||||
<el-select
|
||||
v-model="obFile"
|
||||
filterable
|
||||
:placeholder="''"
|
||||
class="obFileSelect"
|
||||
popper-class="smmObFileSelectPopper"
|
||||
clearable
|
||||
:remote-method="getObFileList"
|
||||
@change="onObFileChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in obFileList"
|
||||
:key="item.path"
|
||||
:label="item.basename"
|
||||
:value="item.path"
|
||||
<div v-if="dialogVisible">
|
||||
<div class="tip">{{ $t('nodeImage.tip') }}</div>
|
||||
<div class="title">1.{{ $t('nodeImage.selectLocal') }}</div>
|
||||
<ImgUpload
|
||||
ref="ImgUpload"
|
||||
v-model="img"
|
||||
style="margin-bottom: 12px;"
|
||||
@selectObFile="onImgUploadSelectObFile"
|
||||
></ImgUpload>
|
||||
<div class="inputBox">
|
||||
<span class="inputTitle" :title="$t('nodeImage.selectVault')"
|
||||
>2.{{ $t('nodeImage.selectVault') }}</span
|
||||
>
|
||||
<div class="fileName">{{ item.basename }}</div>
|
||||
<div class="folder">{{ item.folder }}</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
<span class="inputTitle" :title="$t('nodeImage.selectUrl')"
|
||||
>3.{{ $t('nodeImage.selectUrl') }}</span
|
||||
>
|
||||
<el-input
|
||||
v-model="imgUrl"
|
||||
size="mini"
|
||||
placeholder="http://xxx.com/xx.jpg"
|
||||
@keydown.native.stop
|
||||
@change="onImgUrlChange"
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
<span class="inputTitle" :title="$t('nodeImage.imgTitle')">{{
|
||||
$t('nodeImage.imgTitle')
|
||||
}}</span>
|
||||
<el-input v-model="imgTitle" size="mini" @keydown.native.stop></el-input>
|
||||
<el-select
|
||||
v-model="obFile"
|
||||
filterable
|
||||
:placeholder="''"
|
||||
class="obFileSelect"
|
||||
popper-class="smmObFileSelectPopper"
|
||||
clearable
|
||||
:remote-method="getObFileList"
|
||||
@change="onObFileChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in obFileList"
|
||||
:key="item.path"
|
||||
:label="item.basename"
|
||||
:value="item.path"
|
||||
>
|
||||
<div class="fileName">{{ item.basename }}</div>
|
||||
<div class="folder">{{ item.folder }}</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
<span class="inputTitle" :title="$t('nodeImage.selectUrl')"
|
||||
>3.{{ $t('nodeImage.selectUrl') }}</span
|
||||
>
|
||||
<el-input
|
||||
v-model="imgUrl"
|
||||
size="mini"
|
||||
placeholder="http://xxx.com/xx.jpg"
|
||||
@keydown.native.stop
|
||||
@change="onImgUrlChange"
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="inputBox">
|
||||
<span class="inputTitle" :title="$t('nodeImage.imgTitle')">{{
|
||||
$t('nodeImage.imgTitle')
|
||||
}}</span>
|
||||
<el-input
|
||||
v-model="imgTitle"
|
||||
size="mini"
|
||||
@keydown.native.stop
|
||||
></el-input>
|
||||
</div>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="cancel" size="small" class="smmElButtonSmall">{{
|
||||
|
|
@ -81,7 +88,6 @@ import { getImageSize } from 'simple-mind-map/src/utils/index'
|
|||
import { mapState } from 'vuex'
|
||||
import { isNormalUrl } from '@/utils'
|
||||
|
||||
// 节点图片内容设置
|
||||
export default {
|
||||
components: {
|
||||
ImgUpload
|
||||
|
|
@ -113,12 +119,10 @@ export default {
|
|||
this.$root.$bus.$off('showNodeImage', this.handleShowNodeImage)
|
||||
},
|
||||
methods: {
|
||||
// 保存激活的节点
|
||||
handleNodeActive(...args) {
|
||||
this.activeNodes = [...args[1]]
|
||||
},
|
||||
|
||||
// 显示节点图片设置对话框
|
||||
handleShowNodeImage() {
|
||||
if (!this.activeNodes || this.activeNodes.length === 0) return
|
||||
this.reset()
|
||||
|
|
@ -127,9 +131,8 @@ export default {
|
|||
const firstNode = this.activeNodes[0]
|
||||
let img = firstNode.getData('image') || ''
|
||||
img =
|
||||
(firstNode.mindMap.renderer.renderTree.data.imgMap || {})[img] || img
|
||||
(firstNode.mindMap.renderer.treeRender.renderTree.data.imgMap || {})[img] || img
|
||||
if (img) {
|
||||
// 网络图片
|
||||
if (isNormalUrl(img)) {
|
||||
this.imgUrl = img
|
||||
this.img = img
|
||||
|
|
@ -161,7 +164,6 @@ export default {
|
|||
|
||||
async confirm() {
|
||||
try {
|
||||
// 删除图片
|
||||
if (!this.img && !this.imgUrl && !this.obFile) {
|
||||
this.cancel()
|
||||
this.activeNodes.forEach(node => {
|
||||
|
|
@ -188,7 +190,6 @@ export default {
|
|||
})
|
||||
this.cancel()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -196,7 +197,6 @@ export default {
|
|||
return this.obFileList.find(item => item.path === filePath)
|
||||
},
|
||||
|
||||
// 从Vault获取图片文件列表
|
||||
getObFileList(searchText) {
|
||||
let list = []
|
||||
if (this.allObFileList) {
|
||||
|
|
@ -221,7 +221,6 @@ export default {
|
|||
this.obFileList = list.slice(0, 50)
|
||||
},
|
||||
|
||||
// 从Vault选择图片
|
||||
onObFileChange() {
|
||||
if (this.obFile) {
|
||||
this.img = this.obFile
|
||||
|
|
@ -234,7 +233,6 @@ export default {
|
|||
this.onObFileChange()
|
||||
},
|
||||
|
||||
// 网络图片地址输入
|
||||
onImgUrlChange() {
|
||||
if (this.imgUrl) {
|
||||
this.img = this.imgUrl
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<viewer :images="images">
|
||||
<viewer :images="images" style="position: absolute; left: -9999999px;">
|
||||
<img v-for="src in images" :key="src" :src="src" />
|
||||
</viewer>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,27 +1,41 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
class="nodeNoteDialog smmElDialog"
|
||||
:title="$t('nodeNote.title')"
|
||||
:visible.sync="dialogVisible"
|
||||
:width="'90%'"
|
||||
:top="isMobile ? '20px' : '15vh'"
|
||||
:modal-append-to-body="false"
|
||||
:close-on-click-modal="false"
|
||||
<div
|
||||
class="nodeNoteEditContainer"
|
||||
:class="{ isDark: isDark }"
|
||||
v-show="dialogVisible"
|
||||
>
|
||||
<div class="noteEditor" ref="noteEditor" @keyup.stop @keydown.stop></div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="cancel" size="small" class="smmElButtonSmall">{{
|
||||
$t('dialog.cancel')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="confirm"
|
||||
size="small"
|
||||
class="smmElButtonSmall"
|
||||
>{{ $t('dialog.confirm') }}</el-button
|
||||
>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<div class="nodeNoteEditContent" :class="{ isFullScreen: isFullScreen }">
|
||||
<div class="editWrap">
|
||||
<div
|
||||
class="noteEditor"
|
||||
ref="noteEditor"
|
||||
@keyup.stop
|
||||
@keydown.stop
|
||||
></div>
|
||||
<!-- 全屏 -->
|
||||
<div
|
||||
class="fullScreenBtn"
|
||||
:class="{ active: isFullScreen }"
|
||||
@click="toggleFullScreen"
|
||||
:title="isFullScreen ? '退出全屏' : '全屏'"
|
||||
>
|
||||
<span class="icon iconfont iconquanping1"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<el-button @click="cancel" size="small" class="elButtonSmall">{{
|
||||
$t('dialog.cancel')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="confirm"
|
||||
size="small"
|
||||
class="elButtonSmall"
|
||||
>{{ $t('dialog.confirm') }}</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -33,9 +47,7 @@ import { toastUiEditorLangMap } from '@/config/constant'
|
|||
import { compressImage, isNormalUrl } from '@/utils'
|
||||
import noteMixin from '@/mixins/note'
|
||||
|
||||
// 节点备注内容设置
|
||||
export default {
|
||||
name: 'NodeNote',
|
||||
mixins: [noteMixin],
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -43,7 +55,8 @@ export default {
|
|||
note: '',
|
||||
activeNodes: [],
|
||||
editor: null,
|
||||
appointNode: null
|
||||
appointNode: null,
|
||||
isFullScreen: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -99,18 +112,16 @@ export default {
|
|||
if (!this.editor) {
|
||||
this.editor = new Editor({
|
||||
el: this.$refs.noteEditor,
|
||||
height: '500px',
|
||||
height: '100%',
|
||||
initialEditType: 'markdown',
|
||||
previewStyle: 'vertical',
|
||||
theme: this.isDark ? 'dark' : 'light',
|
||||
language:
|
||||
toastUiEditorLangMap[this.$i18n.locale] || toastUiEditorLangMap.en,
|
||||
customHTMLRenderer: {
|
||||
// 拦截图片渲染逻辑
|
||||
image: (node, { entering }) => {
|
||||
if (!entering) return null
|
||||
let url = node.destination || node.src
|
||||
// 自定义转换逻辑(例如添加前缀、替换域名)
|
||||
if (!isNormalUrl(url)) {
|
||||
url = this.$root.$obsidianAPI.getResourcePath(
|
||||
decodeURIComponent(url)
|
||||
|
|
@ -160,7 +171,6 @@ export default {
|
|||
}
|
||||
callback(result)
|
||||
} catch (error) {
|
||||
console.error('上传失败', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -186,22 +196,94 @@ export default {
|
|||
node.setNote(this.note)
|
||||
})
|
||||
}
|
||||
|
||||
this.cancel()
|
||||
},
|
||||
|
||||
toggleFullScreen() {
|
||||
this.isFullScreen = !this.isFullScreen
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nodeNoteDialog {
|
||||
/deep/ .el-dialog {
|
||||
max-width: 800px;
|
||||
.nodeNoteEditContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
|
||||
&.isDark {
|
||||
.nodeNoteEditContent {
|
||||
background-color: #262a2e;
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-top: 5px;
|
||||
color: #dcdfe6;
|
||||
.nodeNoteEditContent {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 600px;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.06);
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding-bottom: 27px;
|
||||
|
||||
&.isFullScreen {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.editWrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.fullScreenBtn {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
border-radius: 3px;
|
||||
margin: 7px 5px;
|
||||
border: 1px solid #f7f9fc;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid #e4e7ee;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #00a9ff;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
height: 50px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import { mapState } from 'vuex'
|
|||
import { toastUiEditorLangMap } from '@/config/constant'
|
||||
import noteMixin from '@/mixins/note'
|
||||
|
||||
// 节点备注内容显示
|
||||
export default {
|
||||
mixins: [noteMixin],
|
||||
props: {
|
||||
|
|
@ -82,7 +81,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 显示备注浮层
|
||||
onShowNoteContent(content, left, top, width, height, node) {
|
||||
this.initEditor(() => {
|
||||
this.node = node
|
||||
|
|
@ -98,7 +96,6 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 超链接新窗口打开
|
||||
handleALink() {
|
||||
const list = this.$refs.noteContentViewer.querySelectorAll('a')
|
||||
Array.from(list).forEach(a => {
|
||||
|
|
@ -106,7 +103,6 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 更新位置
|
||||
updateNoteContentPosition(left, top, width, height) {
|
||||
const boxRect = this.$refs.noteContentViewer.getBoundingClientRect()
|
||||
const elRect = this.mindMap.elRect
|
||||
|
|
@ -128,19 +124,16 @@ export default {
|
|||
this.top = top
|
||||
},
|
||||
|
||||
// 画布缩放事件
|
||||
onScale() {
|
||||
if (!this.node || !this.show) return
|
||||
const { left, top, width, height } = this.node.getNoteContentPosition()
|
||||
this.updateNoteContentPosition(left, top, width, height)
|
||||
},
|
||||
|
||||
// 隐藏备注浮层
|
||||
hideNoteContent() {
|
||||
this.show = false
|
||||
},
|
||||
|
||||
// 初始化编辑器
|
||||
initEditor(callback) {
|
||||
if (!this.editor) {
|
||||
this.mindMap.el.appendChild(this.$refs.noteContentViewer)
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 初始化编辑器
|
||||
initEditor() {
|
||||
if (!this.editor) {
|
||||
this.editor = new Viewer({
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('nodeOuterFrame.boxStyle') }}</span>
|
||||
<!-- 宽度 -->
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
|
|
@ -47,7 +46,6 @@
|
|||
></span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<!-- 实现虚线 -->
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px;margin-left: 4px;"
|
||||
|
|
@ -395,14 +393,15 @@ import { mapState, mapMutations } from 'vuex'
|
|||
import {
|
||||
lineWidthList,
|
||||
borderDasharrayList,
|
||||
fontFamilyList,
|
||||
fontSizeList,
|
||||
borderRadiusList,
|
||||
lineHeightList
|
||||
} from '@/config'
|
||||
import OuterFrame from 'simple-mind-map/src/plugins/OuterFrame'
|
||||
import fontFamilyMixin from '@/mixins/fontFamily'
|
||||
|
||||
export default {
|
||||
mixins: [fontFamilyMixin],
|
||||
components: {
|
||||
Sidebar,
|
||||
Color
|
||||
|
|
@ -434,11 +433,7 @@ export default {
|
|||
borderDasharrayList() {
|
||||
return borderDasharrayList[this.$i18n.locale] || borderDasharrayList.en
|
||||
}
|
||||
}),
|
||||
|
||||
fontFamilyList() {
|
||||
return fontFamilyList[this.$i18n.locale] || fontFamilyList.en
|
||||
}
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
activeSidebar(val) {
|
||||
|
|
@ -463,7 +458,6 @@ export default {
|
|||
...mapMutations(['setActiveSidebar']),
|
||||
|
||||
onOuterFrameActive(el, parentNode, range) {
|
||||
// 取范围内第一个节点的外框样式
|
||||
const firstNode = parentNode.children[range[0]]
|
||||
const firstNodeOuterFrame = firstNode.getData('outerFrame')
|
||||
Object.keys(this.styleConfig).forEach(key => {
|
||||
|
|
@ -486,14 +480,12 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 切换加粗样式
|
||||
toggleFontWeight() {
|
||||
const newValue =
|
||||
this.styleConfig.fontWeight === 'bold' ? 'normal' : 'bold'
|
||||
this.updateOuterFrame('fontWeight', newValue)
|
||||
},
|
||||
|
||||
// 切换字体样式
|
||||
toggleFontStyle() {
|
||||
const newValue =
|
||||
this.styleConfig.fontStyle === 'italic' ? 'normal' : 'italic'
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
:width="'90%'"
|
||||
:top="isMobile ? '20px' : '15vh'"
|
||||
:modal-append-to-body="false"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
>
|
||||
<el-input
|
||||
v-model="tag"
|
||||
|
|
@ -15,6 +17,7 @@
|
|||
:disabled="tagArr.length >= max"
|
||||
:placeholder="$t('nodeTag.addTip')"
|
||||
>
|
||||
<el-button slot="append" @click="add">{{ $t('nodeTag.add') }}</el-button>
|
||||
</el-input>
|
||||
<div class="tagList">
|
||||
<div
|
||||
|
|
@ -48,7 +51,6 @@
|
|||
import { generateColorByContent } from 'simple-mind-map/src/utils/index'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
// 节点标签内容设置
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -87,11 +87,9 @@ export default {
|
|||
if (typeof tag === 'string') {
|
||||
this.text = tag
|
||||
} else {
|
||||
// v0.10.3+版本支持对象类型
|
||||
this.text = tag.text
|
||||
this.fill = tag.style && tag.style.fill ? tag.style.fill : ''
|
||||
}
|
||||
// 获取外框的位置大小信息
|
||||
const rect = el.rbox()
|
||||
const elRect = this.mindMap.elRect
|
||||
const boxWidth = 260
|
||||
|
|
|
|||
202
plugin/src/pages/Edit/components/NodeTextInputObLink.vue
Normal file
202
plugin/src/pages/Edit/components/NodeTextInputObLink.vue
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
<template>
|
||||
<FileSelectList
|
||||
v-if="showFileSelectList"
|
||||
:position="fileSelectListPosition"
|
||||
@select="onFileSelect"
|
||||
></FileSelectList>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FileSelectList from './FileSelectList.vue'
|
||||
import { isHyperlink, linkRichToObUrlText, getObLinkShowName } from '@/utils'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FileSelectList
|
||||
},
|
||||
props: {
|
||||
mindMap: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showFileSelectList: false,
|
||||
fileSelectListPosition: null,
|
||||
lastRange: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.mindMap.on('node_click', this.handleNodeTextATagClick)
|
||||
this.mindMap.on('node_text_edit_change', this.onNodeTextEditChange)
|
||||
this.mindMap.on('scale', this.hide)
|
||||
this.mindMap.on('translate', this.hide)
|
||||
this.mindMap.on('svg_mousedown', this.hide)
|
||||
this.mindMap.on('expand_btn_click', this.hide)
|
||||
this.mindMap.opt.transformRichTextOnEnterEdit.push(this.linkRichToText)
|
||||
this.mindMap.opt.beforeHideRichTextEdit.push(this.textToLink)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.mindMap.off('node_text_edit_change', this.onNodeTextEditChange)
|
||||
this.mindMap.off('node_click', this.handleNodeTextATagClick)
|
||||
this.mindMap.off('scale', this.hide)
|
||||
this.mindMap.off('translate', this.hide)
|
||||
this.mindMap.off('svg_mousedown', this.hide)
|
||||
this.mindMap.off('expand_btn_click', this.hide)
|
||||
this.removeHandlers()
|
||||
},
|
||||
methods: {
|
||||
removeHandlers() {
|
||||
const index = this.mindMap.opt.transformRichTextOnEnterEdit.find(item => {
|
||||
return item === this.linkRichToText
|
||||
})
|
||||
if (index > -1) {
|
||||
this.mindMap.opt.transformRichTextOnEnterEdit.splice(index, 1)
|
||||
}
|
||||
const index2 = this.mindMap.opt.beforeHideRichTextEdit.find(item => {
|
||||
return item === this.textToLink
|
||||
})
|
||||
if (index2 > -1) {
|
||||
this.mindMap.opt.beforeHideRichTextEdit.splice(index2, 1)
|
||||
}
|
||||
},
|
||||
|
||||
onNodeTextEditChange() {
|
||||
const quill = this.mindMap.richText.quill
|
||||
const selection = quill.getSelection()
|
||||
this.lastRange = selection
|
||||
if (!selection) return
|
||||
const cursorPos = selection.index
|
||||
const linkPos = cursorPos - 2
|
||||
let textBefore = quill.getText(Math.max(0, linkPos), 2)
|
||||
if (/【【/.test(textBefore)) {
|
||||
quill.deleteText(linkPos, 2)
|
||||
quill.insertText(linkPos, '[[')
|
||||
textBefore = '[['
|
||||
}
|
||||
if (/\[\[/.test(textBefore)) {
|
||||
const textEditNode = this.mindMap.richText.textEditNode
|
||||
const rect = textEditNode.getBoundingClientRect()
|
||||
const cursorBounds = quill.getBounds(cursorPos)
|
||||
const elRect = this.mindMap.elRect
|
||||
let left = rect.left + cursorBounds.left - elRect.left
|
||||
let top = rect.top + cursorBounds.bottom + 10 - elRect.top
|
||||
if (left + 300 > elRect.width) {
|
||||
left = elRect.width - 300
|
||||
}
|
||||
if (top + 300 > elRect.height) {
|
||||
top = rect.top + cursorBounds.top - 300 - 10 - elRect.top
|
||||
}
|
||||
this.fileSelectListPosition = {
|
||||
left,
|
||||
top
|
||||
}
|
||||
this.showFileSelectList = true
|
||||
} else {
|
||||
this.hide()
|
||||
}
|
||||
},
|
||||
|
||||
handleNodeTextATagClick(node, e) {
|
||||
this.hide()
|
||||
const el = e.target
|
||||
if (el.tagName.toLowerCase() === 'a') {
|
||||
e.preventDefault()
|
||||
const link = el.getAttribute('data-href') || el.getAttribute('href')
|
||||
if (!isHyperlink(link)) {
|
||||
const {
|
||||
embedLinkNewWindowOpen
|
||||
} = this.$root.$obsidianAPI.getSettings()
|
||||
const isNewTab = embedLinkNewWindowOpen || e.ctrlKey || e.metaKey
|
||||
this.$root.$obsidianAPI.openFile(link, isNewTab, true)
|
||||
} else {
|
||||
this.$root.$obsidianAPI.openWebLink(link)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onFileSelect(file) {
|
||||
const quill = this.mindMap.richText.quill
|
||||
const curRange = quill.getSelection()
|
||||
const range = curRange || this.lastRange
|
||||
if (!range) return
|
||||
const linkPos = range.index - 2
|
||||
const oldFormat = quill.getFormat(linkPos, 2)
|
||||
const res = this.$root.$obsidianAPI.createLinkInfoFromFilePath(file.path)
|
||||
if (res) {
|
||||
quill.deleteText(linkPos, 2)
|
||||
quill.insertText(linkPos, res.linkText, {
|
||||
...oldFormat
|
||||
})
|
||||
}
|
||||
this.lastRange = null
|
||||
if (!curRange) {
|
||||
quill.setSelection(linkPos + res.linkText.length)
|
||||
}
|
||||
this.hide()
|
||||
},
|
||||
|
||||
linkRichToText(nodeText) {
|
||||
nodeText = linkRichToObUrlText(nodeText, () => {
|
||||
if (this.mindMap.opt.openRealtimeRenderOnNodeTextEdit) {
|
||||
setTimeout(() => {
|
||||
this.mindMap.emit('node_text_edit_change', {
|
||||
node: this.mindMap.richText.node,
|
||||
text: this.mindMap.richText.getEditText(),
|
||||
richText: true
|
||||
})
|
||||
}, 0)
|
||||
}
|
||||
})
|
||||
return nodeText
|
||||
},
|
||||
|
||||
textToLink(richText) {
|
||||
const contents = richText.quill.getContents()
|
||||
const ops = contents.ops
|
||||
for (let i = ops.length - 1; i >= 0; i--) {
|
||||
const op = ops[i]
|
||||
const insert = op.insert
|
||||
if (insert && typeof insert !== 'object' && insert !== '\n') {
|
||||
if (/\[\[[^\[\]]+\]\]/g.test(insert)) {
|
||||
const m = [...insert.matchAll(/\[\[[^\[\]]+\]\]/g)]
|
||||
const arr = insert.split(/\[\[[^\[\]]+\]\]/g)
|
||||
for (let j = m.length - 1; j >= 0; j--) {
|
||||
const exp = m[j] && m[j][0] ? m[j][0].slice(2, -2) || null : null
|
||||
if (exp !== null && exp.trim().length > 0) {
|
||||
arr.splice(j + 1, 0, {
|
||||
insert: getObLinkShowName(exp),
|
||||
attributes: { link: exp }
|
||||
})
|
||||
} else {
|
||||
arr.splice(j + 1, 0, '')
|
||||
}
|
||||
}
|
||||
while (arr.length > 0) {
|
||||
let v = arr.pop()
|
||||
let attributes = {}
|
||||
if (typeof v === 'string') {
|
||||
if (v.length < 1) continue
|
||||
v = { insert: v }
|
||||
} else if (typeof v === 'object') {
|
||||
attributes = v['attributes'] || {}
|
||||
}
|
||||
v['attributes'] = {
|
||||
...ops[i]['attributes'],
|
||||
...attributes
|
||||
}
|
||||
ops.splice(i + 1, 0, v)
|
||||
}
|
||||
ops.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
richText.quill.setContents(contents)
|
||||
},
|
||||
|
||||
hide() {
|
||||
this.showFileSelectList = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -45,8 +45,9 @@ import {
|
|||
htmlEscape,
|
||||
handleInputPasteText
|
||||
} from 'simple-mind-map/src/utils'
|
||||
import { linkRichToObUrlText, obUrlToLinkRich } from '@/utils'
|
||||
import CursorTextHandler from '@/utils/CursorTextHandler'
|
||||
|
||||
// 大纲树
|
||||
export default {
|
||||
props: {
|
||||
mindMap: {
|
||||
|
|
@ -65,7 +66,8 @@ export default {
|
|||
beInsertNodeUid: '',
|
||||
insertType: '',
|
||||
isInTreArea: false,
|
||||
isAfterCreateNewNode: false
|
||||
isAfterCreateNewNode: false,
|
||||
beingSplitNodeContent: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -98,7 +100,6 @@ export default {
|
|||
},
|
||||
|
||||
handleDataChange() {
|
||||
// 在大纲里操作节点时不要响应该事件,否则会重新刷新树
|
||||
if (this.notHandleDataChange) {
|
||||
this.notHandleDataChange = false
|
||||
this.isAfterCreateNewNode = false
|
||||
|
|
@ -112,13 +113,11 @@ export default {
|
|||
},
|
||||
|
||||
handleNodeTreeRenderEnd() {
|
||||
// 当前存在未完成的节点插入操作
|
||||
if (this.insertType) {
|
||||
this[this.insertType]()
|
||||
this.insertType = ''
|
||||
return
|
||||
}
|
||||
// 插入了新节点后需要做一些操作
|
||||
if (this.isHandleNodeTreeRenderEnd) {
|
||||
this.isHandleNodeTreeRenderEnd = false
|
||||
this.refresh()
|
||||
|
|
@ -128,17 +127,17 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 刷新树数据
|
||||
refresh() {
|
||||
this.fileNameToPath = {}
|
||||
let data = this.mindMap.getData()
|
||||
data.root = true // 标记根节点
|
||||
data.root = true
|
||||
let walk = root => {
|
||||
let text = root.data.richText
|
||||
? nodeRichTextToTextWithWrap(root.data.text)
|
||||
? nodeRichTextToTextWithWrap(linkRichToObUrlText(root.data.text))
|
||||
: root.data.text
|
||||
text = htmlEscape(text)
|
||||
text = text.replace(/\n/g, '<br>')
|
||||
root.textCache = text // 保存一份修改前的数据,用于对比是否修改了
|
||||
root.textCache = text
|
||||
root.label = text
|
||||
root.uid = root.data.uid
|
||||
if (root.children && root.children.length > 0) {
|
||||
|
|
@ -148,23 +147,24 @@ export default {
|
|||
}
|
||||
}
|
||||
walk(data)
|
||||
this.data = [data]
|
||||
if (data.data.freeNodeTrees && data.data.freeNodeTrees.length > 0) {
|
||||
data.data.freeNodeTrees.forEach(item => {
|
||||
item.root = true
|
||||
walk(item)
|
||||
})
|
||||
}
|
||||
this.data = [data, ...data.data.freeNodeTrees]
|
||||
},
|
||||
|
||||
// 插入了新节点之后
|
||||
afterCreateNewNode() {
|
||||
// 如果是新插入节点,那么需要手动高亮该节点、定位该节点及聚焦
|
||||
let id = this.beInsertNodeUid
|
||||
if (id && this.$refs.tree) {
|
||||
try {
|
||||
this.isAfterCreateNewNode = true
|
||||
// 高亮树节点
|
||||
this.$refs.tree.setCurrentKey(id)
|
||||
let node = this.$refs.tree.getNode(id)
|
||||
this.onCurrentChange(node.data)
|
||||
// 定位该节点
|
||||
this.onClick(node.data)
|
||||
// 聚焦该树节点的编辑框
|
||||
const el = document.querySelector(
|
||||
`.smmOutlineTreeCustomNode[data-id="${id}"] .smmOutlineTreeCustomNodeEdit`
|
||||
)
|
||||
|
|
@ -177,90 +177,113 @@ export default {
|
|||
let offsetTop = el.offsetTop
|
||||
this.$emit('scrollTo', offsetTop)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
this.beInsertNodeUid = ''
|
||||
},
|
||||
|
||||
// 根节点不允许拖拽
|
||||
checkAllowDrag(node) {
|
||||
return !node.data.root
|
||||
},
|
||||
|
||||
// 失去焦点更新节点文本
|
||||
onBlur(e, node) {
|
||||
// 节点数据没有修改
|
||||
if (node.data.textCache === e.target.innerHTML) {
|
||||
// 如果存在未执行的插入新节点操作,那么直接执行
|
||||
if (this.insertType) {
|
||||
this[this.insertType]()
|
||||
this.insertType = ''
|
||||
}
|
||||
return
|
||||
}
|
||||
// 否则插入新节点操作需要等待当前修改事件渲染完成后再执行
|
||||
const richText = node.data.data.richText
|
||||
let text = richText ? e.target.innerHTML : e.target.innerText
|
||||
const targetNode = this.mindMap.renderer.findNodeByUid(node.data.uid)
|
||||
if (!targetNode) return
|
||||
this.notHandleDataChange = true
|
||||
this.notHandleDataChange = !this.insertType
|
||||
if (richText) {
|
||||
targetNode.setText(textToNodeRichTextWithWrap(text), true)
|
||||
text = textToNodeRichTextWithWrap(text)
|
||||
text = obUrlToLinkRich(text)
|
||||
targetNode.setText(text, true)
|
||||
} else {
|
||||
targetNode.setText(text)
|
||||
}
|
||||
},
|
||||
|
||||
// 拦截粘贴事件
|
||||
onPaste(e) {
|
||||
handleInputPasteText(e)
|
||||
},
|
||||
|
||||
// 生成唯一的key
|
||||
getKey() {
|
||||
return Math.random()
|
||||
},
|
||||
|
||||
// 节点输入区域按键事件
|
||||
onNodeInputKeydown(e) {
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
// 插入兄弟节点
|
||||
e.preventDefault()
|
||||
this.insertType = 'insertNode'
|
||||
this.insertType = e.ctrlKey ? 'splitInsertNode' : 'insertNode'
|
||||
if (e.ctrlKey) {
|
||||
const cursorTextHandler = new CursorTextHandler(e.target)
|
||||
const afterText = cursorTextHandler.getTextAfterCursor()
|
||||
if (!afterText) {
|
||||
return
|
||||
}
|
||||
const beforeText = cursorTextHandler.getTextBeforeCursor()
|
||||
e.target.innerHTML = beforeText
|
||||
this.beingSplitNodeContent = afterText
|
||||
}
|
||||
e.target.blur()
|
||||
}
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault()
|
||||
if (e.shiftKey) {
|
||||
// 节点上升一级
|
||||
this.insertType = 'moveUp'
|
||||
e.target.blur()
|
||||
} else {
|
||||
// 插入子节点
|
||||
this.insertType = 'insertChildNode'
|
||||
this.insertType = 'moveDown'
|
||||
e.target.blur()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 节点上移一个层级
|
||||
moveUp() {
|
||||
this.mindMap.execCommand('MOVE_UP_ONE_LEVEL')
|
||||
},
|
||||
|
||||
// 插入兄弟节点
|
||||
insertNode() {
|
||||
moveDown() {
|
||||
this.mindMap.execCommand('MOVE_DOWN_ONE_LEVEL')
|
||||
},
|
||||
|
||||
splitInsertNode() {
|
||||
if (!this.beingSplitNodeContent) {
|
||||
return
|
||||
}
|
||||
this.notHandleDataChange = true
|
||||
this.isHandleNodeTreeRenderEnd = true
|
||||
this.beInsertNodeUid = createUid()
|
||||
const richText = !!this.mindMap.richText
|
||||
this.mindMap.execCommand('INSERT_NODE', false, [], {
|
||||
uid: this.beInsertNodeUid,
|
||||
text: richText
|
||||
? textToNodeRichTextWithWrap(this.beingSplitNodeContent)
|
||||
: this.beingSplitNodeContent,
|
||||
richText
|
||||
})
|
||||
this.beingSplitNodeContent = ''
|
||||
},
|
||||
|
||||
insertNode() {
|
||||
const activeNode = this.mindMap.renderer.getActiveNodeList()[0]
|
||||
let commandName = 'INSERT_NODE'
|
||||
if (activeNode && activeNode.isRoot) {
|
||||
commandName = 'INSERT_CHILD_NODE'
|
||||
}
|
||||
this.notHandleDataChange = true
|
||||
this.isHandleNodeTreeRenderEnd = true
|
||||
this.beInsertNodeUid = createUid()
|
||||
this.mindMap.execCommand(commandName, false, [], {
|
||||
uid: this.beInsertNodeUid
|
||||
})
|
||||
},
|
||||
|
||||
// 插入下级节点
|
||||
insertChildNode() {
|
||||
this.notHandleDataChange = true
|
||||
this.isHandleNodeTreeRenderEnd = true
|
||||
|
|
@ -270,17 +293,15 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 激活当前节点且移动当前节点到画布中间
|
||||
onClick(data) {
|
||||
this.notHandleDataChange = true
|
||||
const targetNode = this.mindMap.renderer.findNodeByUid(data.uid)
|
||||
if (targetNode && targetNode.nodeData.data.isActive) return
|
||||
this.notHandleDataChange = true
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', data.uid, () => {
|
||||
this.notHandleDataChange = false
|
||||
})
|
||||
},
|
||||
|
||||
// 拖拽结束事件
|
||||
onNodeDrop(data, target, postion) {
|
||||
this.notHandleDataChange = true
|
||||
const node = this.mindMap.renderer.findNodeByUid(data.data.uid)
|
||||
|
|
@ -303,12 +324,10 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 当前选中的树节点变化事件
|
||||
onCurrentChange(data) {
|
||||
this.currentData = data
|
||||
},
|
||||
|
||||
// 删除节点
|
||||
onKeyDown(e) {
|
||||
if (!this.isInTreArea) return
|
||||
if ([46, 8].includes(e.keyCode) && this.currentData) {
|
||||
|
|
|
|||
|
|
@ -70,10 +70,15 @@ import {
|
|||
htmlEscape,
|
||||
handleInputPasteText
|
||||
} from 'simple-mind-map/src/utils'
|
||||
import { printOutline } from '@/utils'
|
||||
import {
|
||||
printOutline,
|
||||
linkRichToObUrlText,
|
||||
obUrlToLinkRich,
|
||||
parseInnerLinkAndText
|
||||
} from '@/utils'
|
||||
import Import from './Import.vue'
|
||||
import CursorTextHandler from '@/utils/CursorTextHandler'
|
||||
|
||||
// 大纲侧边栏
|
||||
export default {
|
||||
components: {
|
||||
Import
|
||||
|
|
@ -125,7 +130,6 @@ export default {
|
|||
this.$root.$bus.$off('printOutline', this.onPrint)
|
||||
},
|
||||
methods: {
|
||||
// 获取初始数据
|
||||
getData() {
|
||||
this.mindMapData = JSON.parse(
|
||||
this.$root.$obsidianAPI.getInitMindMapData()
|
||||
|
|
@ -134,20 +138,19 @@ export default {
|
|||
this.mindMapDataToTree(rootData)
|
||||
},
|
||||
|
||||
// 思维导图数据转换为树结构
|
||||
mindMapDataToTree(rootData, callback = () => {}) {
|
||||
this.currentData = null
|
||||
this.currentNode = null
|
||||
this.treeData = []
|
||||
this.$nextTick(() => {
|
||||
rootData.root = true // 标记根节点
|
||||
rootData.root = true
|
||||
let walk = root => {
|
||||
let text = root.data.richText
|
||||
? nodeRichTextToTextWithWrap(root.data.text)
|
||||
? nodeRichTextToTextWithWrap(linkRichToObUrlText(root.data.text))
|
||||
: root.data.text
|
||||
text = htmlEscape(text)
|
||||
text = text.replace(/\n/g, '<br>')
|
||||
root.textCache = text // 保存一份修改前的数据,用于对比是否修改了
|
||||
root.textCache = text
|
||||
root.label = text
|
||||
root.uid = root.data.uid
|
||||
if (root.children && root.children.length > 0) {
|
||||
|
|
@ -157,54 +160,57 @@ export default {
|
|||
}
|
||||
}
|
||||
walk(rootData)
|
||||
this.treeData = [rootData]
|
||||
const freeNodeTrees = rootData.data.freeNodeTrees || []
|
||||
delete rootData.data.freeNodeTrees
|
||||
if (freeNodeTrees && freeNodeTrees.length > 0) {
|
||||
freeNodeTrees.forEach(item => {
|
||||
item.root = true
|
||||
walk(item)
|
||||
})
|
||||
}
|
||||
this.treeData = [rootData, ...freeNodeTrees]
|
||||
callback()
|
||||
})
|
||||
},
|
||||
|
||||
// 根节点不允许拖拽
|
||||
checkAllowDrag(node) {
|
||||
return !node.data.root
|
||||
},
|
||||
|
||||
// 拖拽结束事件
|
||||
onNodeDrop() {
|
||||
this.autoSave()
|
||||
},
|
||||
|
||||
// 当前选中的树节点变化事件
|
||||
onCurrentChange(data, node) {
|
||||
this.currentData = data
|
||||
this.currentNode = node
|
||||
},
|
||||
|
||||
// 失去焦点更新节点文本
|
||||
onBlur(e, node) {
|
||||
// 节点数据没有修改
|
||||
if (node.data.textCache === e.target.innerHTML) {
|
||||
return
|
||||
}
|
||||
const richText = node.data.data.richText
|
||||
let text = richText ? e.target.innerHTML : e.target.innerText
|
||||
if (richText) {
|
||||
text = textToNodeRichTextWithWrap(text)
|
||||
let text2 = textToNodeRichTextWithWrap(text)
|
||||
text2 = obUrlToLinkRich(text2)
|
||||
node.data.data.text = text2
|
||||
}
|
||||
node.data.data.text = text
|
||||
node.data.label = text
|
||||
node.data.textCache = e.target.innerHTML
|
||||
this.autoSave()
|
||||
},
|
||||
|
||||
// 节点输入区域按键事件
|
||||
onNodeInputKeydown(e, node) {
|
||||
const richText = !!node.data.data.richText
|
||||
const uid = createUid()
|
||||
const text = this.$t('outline.nodeDefaultText')
|
||||
const data = {
|
||||
textCache: text,
|
||||
uid,
|
||||
label: text,
|
||||
textCache: '',
|
||||
label: '',
|
||||
data: {
|
||||
text: richText ? textToNodeRichTextWithWrap(text) : text,
|
||||
text: '',
|
||||
uid,
|
||||
richText
|
||||
},
|
||||
|
|
@ -212,20 +218,51 @@ export default {
|
|||
}
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
if (node.data.root) {
|
||||
return
|
||||
if (e.ctrlKey) {
|
||||
const cursorTextHandler = new CursorTextHandler(e.target)
|
||||
const afterText = cursorTextHandler.getTextAfterCursor()
|
||||
if (!afterText) {
|
||||
return
|
||||
}
|
||||
const beforeText = cursorTextHandler.getTextBeforeCursor()
|
||||
e.target.innerHTML = beforeText
|
||||
data.label = afterText
|
||||
data.textCache = afterText
|
||||
data.data.text = richText
|
||||
? textToNodeRichTextWithWrap(afterText)
|
||||
: afterText
|
||||
this.$refs.tree.insertAfter(data, node)
|
||||
} else {
|
||||
const text = this.$t('outline.nodeDefaultText')
|
||||
data.label = text
|
||||
data.textCache = text
|
||||
data.data.text = richText ? textToNodeRichTextWithWrap(text) : text
|
||||
if (node.data.root) {
|
||||
this.$refs.tree.append(data, node)
|
||||
} else {
|
||||
this.$refs.tree.insertAfter(data, node)
|
||||
}
|
||||
}
|
||||
this.$refs.tree.insertAfter(data, node)
|
||||
}
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault()
|
||||
if (e.shiftKey) {
|
||||
// 上移一个层级
|
||||
this.$refs.tree.insertAfter(node.data, node.parent)
|
||||
this.$refs.tree.remove(node)
|
||||
} else {
|
||||
this.$refs.tree.append(data, node)
|
||||
if (node.data.root) {
|
||||
return
|
||||
}
|
||||
if (e.shiftKey) {
|
||||
this.onBlur(e, node)
|
||||
this.$refs.tree.insertAfter(node.data, node.parent)
|
||||
} else {
|
||||
const index = node.parent.childNodes.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
if (index <= 0) return
|
||||
const prevNode = node.parent.childNodes[index - 1]
|
||||
if (prevNode) {
|
||||
this.$refs.tree.append(node.data, prevNode)
|
||||
}
|
||||
}
|
||||
this.$refs.tree.remove(node)
|
||||
}
|
||||
this.autoSave()
|
||||
this.$nextTick(() => {
|
||||
|
|
@ -245,7 +282,6 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 删除节点
|
||||
onKeyDown(e) {
|
||||
if ([46, 8].includes(e.keyCode) && this.currentData) {
|
||||
e.stopPropagation()
|
||||
|
|
@ -256,22 +292,18 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 拦截粘贴事件
|
||||
onPaste(e) {
|
||||
handleInputPasteText(e)
|
||||
},
|
||||
|
||||
// 生成唯一的key
|
||||
getKey() {
|
||||
return Math.random()
|
||||
},
|
||||
|
||||
// 打印
|
||||
onPrint() {
|
||||
printOutline(this.$refs.smmOutlineEditBox)
|
||||
},
|
||||
|
||||
// 滚动
|
||||
scrollTo(y) {
|
||||
let container = this.$refs.smmOutlineEditBox
|
||||
let height = container.offsetHeight
|
||||
|
|
@ -282,7 +314,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 导入数据
|
||||
onImportData(data) {
|
||||
let rootNodeData = null
|
||||
if (data.root) {
|
||||
|
|
@ -295,7 +326,6 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 获取当前编辑中的思维导图数据
|
||||
getCurrentData() {
|
||||
let newNode = {}
|
||||
let node = this.treeData[0]
|
||||
|
|
@ -309,23 +339,37 @@ export default {
|
|||
})
|
||||
}
|
||||
walk(node, newNode)
|
||||
const freeNodeTrees = []
|
||||
if (this.treeData.length > 1) {
|
||||
this.treeData.slice(1).forEach(item => {
|
||||
const newNode = {}
|
||||
freeNodeTrees.push(newNode)
|
||||
walk(item, newNode)
|
||||
})
|
||||
newNode.data.freeNodeTrees = freeNodeTrees
|
||||
}
|
||||
return simpleDeepClone(newNode)
|
||||
},
|
||||
|
||||
// 发送最新数据给ob保存
|
||||
emitMindMapCurrentData() {
|
||||
try {
|
||||
const curData = {
|
||||
...this.mindMapData,
|
||||
root: this.getCurrentData()
|
||||
}
|
||||
this.$root.$obsidianAPI.getMindMapCurrentData(JSON.stringify(curData))
|
||||
const { linkData, textData } = parseInnerLinkAndText(
|
||||
curData,
|
||||
this.$root.$obsidianAPI
|
||||
)
|
||||
this.$root.$obsidianAPI.getMindMapCurrentData(
|
||||
JSON.stringify(curData),
|
||||
linkData,
|
||||
textData
|
||||
)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
},
|
||||
|
||||
// 自动保存
|
||||
autoSave() {
|
||||
this.$root.$bus.$emit('data_change')
|
||||
clearTimeout(this.autoSaveTimer)
|
||||
|
|
@ -334,29 +378,24 @@ export default {
|
|||
}, this.autoSaveTime * 1000)
|
||||
},
|
||||
|
||||
// 取消当前自动保存
|
||||
clearAutoSave() {
|
||||
clearTimeout(this.autoSaveTimer)
|
||||
},
|
||||
|
||||
// 手动触发保存
|
||||
manuallySave() {
|
||||
clearTimeout(this.autoSaveTimer)
|
||||
this.saveToLocal()
|
||||
},
|
||||
|
||||
// 保存
|
||||
saveToLocal() {
|
||||
this.$root.$obsidianAPI.saveMindMapData()
|
||||
},
|
||||
|
||||
// 从obsidian获取数据后更新思维导图数据
|
||||
onUpdateMindMapDataFromOb(data) {
|
||||
try {
|
||||
data = JSON.parse(data)
|
||||
this.mindMapDataToTree(data.root)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -495,7 +534,6 @@ export default {
|
|||
|
||||
/deep/ .smmOutlineTreeCustomNode {
|
||||
.smmOutlineTreeCustomNodeEdit {
|
||||
// max-width: 800px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import { mapState, mapMutations } from 'vuex'
|
|||
import Outline from './Outline.vue'
|
||||
import { printOutline } from '@/utils'
|
||||
|
||||
// 大纲侧边栏
|
||||
export default {
|
||||
components: {
|
||||
Sidebar,
|
||||
|
|
@ -64,7 +63,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 打印
|
||||
onPrint() {
|
||||
printOutline(this.$refs.outlineRef.$el)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,11 +159,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { fontFamilyList, fontSizeList, alignList } from '@/config'
|
||||
import { fontSizeList, alignList } from '@/config'
|
||||
import Color from './Color.vue'
|
||||
import { mapState } from 'vuex'
|
||||
import fontFamilyMixin from '@/mixins/fontFamily'
|
||||
|
||||
export default {
|
||||
mixins: [fontFamilyMixin],
|
||||
components: {
|
||||
Color
|
||||
},
|
||||
|
|
@ -190,10 +192,6 @@ export default {
|
|||
isDark: state => state.localConfig.isDark
|
||||
}),
|
||||
|
||||
fontFamilyList() {
|
||||
return fontFamilyList[this.$i18n.locale] || fontFamilyList.en
|
||||
},
|
||||
|
||||
alignList() {
|
||||
return alignList[this.$i18n.locale] || alignList.en
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
// 放大缩小
|
||||
export default {
|
||||
props: {
|
||||
mindMap: {
|
||||
|
|
@ -65,32 +64,26 @@ export default {
|
|||
this.mindMap.off('draw_click', this.onDrawClick)
|
||||
},
|
||||
methods: {
|
||||
// 转换成百分数
|
||||
toPer(scale) {
|
||||
return (scale * 100).toFixed(0)
|
||||
},
|
||||
|
||||
// 缩小
|
||||
narrow() {
|
||||
this.mindMap.view.narrow()
|
||||
},
|
||||
|
||||
// 放大
|
||||
enlarge() {
|
||||
this.mindMap.view.enlarge()
|
||||
},
|
||||
|
||||
// 聚焦时缓存当前缩放倍数
|
||||
onScaleNumInputFocus() {
|
||||
this.cacheScaleNum = this.scaleNum
|
||||
},
|
||||
|
||||
// 禁止输入非数字
|
||||
onScaleNumInput() {
|
||||
this.scaleNum = this.scaleNum.replace(/[^0-9]+/g, '')
|
||||
},
|
||||
|
||||
// 手动输入缩放倍数
|
||||
onScaleNumChange() {
|
||||
const scaleNum = Number(this.scaleNum)
|
||||
if (Number.isNaN(scaleNum) || scaleNum <= 0) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div class="scrollbarContainer" :class="{ isDark: isDark }">
|
||||
<!-- 竖向 -->
|
||||
<div
|
||||
class="scrollbar verticalScrollbar"
|
||||
ref="verticalScrollbarRef"
|
||||
|
|
@ -13,7 +12,6 @@
|
|||
@mousedown="onVerticalScrollbarMousedown"
|
||||
></div>
|
||||
</div>
|
||||
<!-- 横向 -->
|
||||
<div
|
||||
class="scrollbar horizontalScrollbar"
|
||||
ref="horizontalScrollbarRef"
|
||||
|
|
@ -61,7 +59,6 @@ export default {
|
|||
this.$root.$bus.$off('windowResize', this.onResize)
|
||||
},
|
||||
methods: {
|
||||
// 向插件传递滚动条宽高数据
|
||||
setScrollBarWrapSize() {
|
||||
if (!this.mindMap.scrollbar) return
|
||||
const {
|
||||
|
|
@ -71,7 +68,6 @@ export default {
|
|||
this.mindMap.scrollbar.setScrollBarWrapSize(width, height)
|
||||
},
|
||||
|
||||
// 窗口尺寸变化
|
||||
onResize() {
|
||||
clearTimeout(this.resizeTimer)
|
||||
this.resizeTimer = setTimeout(() => {
|
||||
|
|
@ -79,7 +75,6 @@ export default {
|
|||
}, 300)
|
||||
},
|
||||
|
||||
// 调用插件方法更新滚动条位置和大小
|
||||
updateScrollbar({ vertical, horizontal }) {
|
||||
this.verticalScrollbarStyle = {
|
||||
top: vertical.top + '%',
|
||||
|
|
@ -91,22 +86,18 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 垂直滚动条按下事件调用插件方法
|
||||
onVerticalScrollbarMousedown(e) {
|
||||
this.mindMap.scrollbar.onMousedown(e, 'vertical')
|
||||
},
|
||||
|
||||
// 垂直滚动条点击事件调用插件方法
|
||||
onVerticalScrollbarClick(e) {
|
||||
this.mindMap.scrollbar.onClick(e, 'vertical')
|
||||
},
|
||||
|
||||
// 水平滚动条按下事件调用插件方法
|
||||
onHorizontalScrollbarMousedown(e) {
|
||||
this.mindMap.scrollbar.onMousedown(e, 'horizontal')
|
||||
},
|
||||
|
||||
// 水平滚动条点击事件调用插件方法
|
||||
onHorizontalScrollbarClick(e) {
|
||||
this.mindMap.scrollbar.onClick(e, 'horizontal')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@
|
|||
import { mapState } from 'vuex'
|
||||
import { isUndef, getTextFromHtml } from 'simple-mind-map/src/utils/index'
|
||||
|
||||
// 搜索替换
|
||||
export default {
|
||||
props: {
|
||||
mindMap: {
|
||||
|
|
@ -122,6 +121,7 @@ export default {
|
|||
'search_match_node_list_change',
|
||||
this.onSearchMatchNodeListChange
|
||||
)
|
||||
this.mindMap.keyCommand.addShortcut('Control+f', this.showSearch)
|
||||
this.$root.$bus.$on('windowResize', this.setSearchResultListHeight)
|
||||
this.$root.$bus.$on('setData', this.close)
|
||||
},
|
||||
|
|
@ -138,6 +138,7 @@ export default {
|
|||
'search_match_node_list_change',
|
||||
this.onSearchMatchNodeListChange
|
||||
)
|
||||
this.mindMap.keyCommand.removeShortcut('Control+f', this.showSearch)
|
||||
this.$root.$bus.$off('windowResize', this.setSearchResultListHeight)
|
||||
this.$root.$bus.$off('setData', this.close)
|
||||
},
|
||||
|
|
@ -161,21 +162,18 @@ export default {
|
|||
this.replaceText = ''
|
||||
},
|
||||
|
||||
// 输入框聚焦时,禁止思维导图节点响应按键事件自动进入文本编辑
|
||||
onFocus() {
|
||||
this.mindMap.updateConfig({
|
||||
enableAutoEnterTextEditWhenKeydown: false
|
||||
})
|
||||
},
|
||||
|
||||
// 输入框失焦时恢复
|
||||
onBlur() {
|
||||
this.mindMap.updateConfig({
|
||||
enableAutoEnterTextEditWhenKeydown: true
|
||||
})
|
||||
},
|
||||
|
||||
// 画布,节点点击时让输入框失焦
|
||||
blur() {
|
||||
if (this.$refs.searchInputRef) {
|
||||
this.$refs.searchInputRef.blur()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
v-if="configData"
|
||||
>
|
||||
<div class="smmSidebarGroupTitle noTop">{{ $t('setting.title1') }}</div>
|
||||
<!-- 根节点是否允许收起下级 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -24,7 +23,6 @@
|
|||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置开启自由拖拽 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -36,9 +34,14 @@
|
|||
"
|
||||
>{{ $t('setting.enableFreeDrag') }}</el-checkbox
|
||||
>
|
||||
<span
|
||||
class="iconfont iconbangzhu"
|
||||
style="margin-left: 4px;"
|
||||
:aria-label="$t('setting.enableFreeDragTip')"
|
||||
data-tooltip-position="left"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 节点连线样式是否允许继承祖先的连线样式 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -50,7 +53,6 @@
|
|||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否一直显示展开收起按钮 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -60,7 +62,6 @@
|
|||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 图片和文本内容的间距 -->
|
||||
<div class="row" style="margin-bottom: 0;">
|
||||
<div class="rowItem">
|
||||
<span
|
||||
|
|
@ -80,7 +81,6 @@
|
|||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 文本各内容的间距 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span
|
||||
|
|
@ -101,7 +101,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="smmSidebarGroupTitle">{{ $t('setting.title2') }}</div>
|
||||
<!-- 是否开启文本编辑时实时更新节点大小 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -111,9 +110,14 @@
|
|||
"
|
||||
>{{ $t('setting.openRealtimeRenderOnNodeTextEdit') }}</el-checkbox
|
||||
>
|
||||
<span
|
||||
class="iconfont iconbangzhu"
|
||||
style="margin-left: 4px;"
|
||||
:aria-label="$t('setting.openRealtimeRenderOnNodeTextEditTip')"
|
||||
data-tooltip-position="left"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否在键盘输入时自动进入节点文本编辑模式 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -125,7 +129,6 @@
|
|||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置鼠标滚轮行为 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name" :title="$t('setting.mousewheelAction')">{{
|
||||
|
|
@ -150,7 +153,6 @@
|
|||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置鼠标缩放行为 -->
|
||||
<div class="row" v-if="config.mousewheelAction === 'zoom'">
|
||||
<div class="rowItem">
|
||||
<span
|
||||
|
|
@ -180,7 +182,6 @@
|
|||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 配置创建新节点时的行为 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name" :title="$t('setting.createNewNodeBehavior')">{{
|
||||
|
|
@ -213,9 +214,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="smmSidebarGroupTitle">{{ $t('setting.title3') }}</div>
|
||||
<!-- 水印 -->
|
||||
<div class="row">
|
||||
<!-- 是否显示水印 -->
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
v-model="watermarkConfig.show"
|
||||
|
|
@ -225,7 +224,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="watermarkBox" v-if="watermarkConfig.show">
|
||||
<!-- 是否仅在导出时显示 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -235,7 +233,6 @@
|
|||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否在节点下方 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -245,7 +242,6 @@
|
|||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkText') }}</span>
|
||||
|
|
@ -257,7 +253,6 @@
|
|||
></el-input>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字颜色 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkTextColor') }}</span>
|
||||
|
|
@ -279,7 +274,6 @@
|
|||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字透明度 -->
|
||||
<div class="row">
|
||||
<div class="rowItem" style="margin-bottom: 0;">
|
||||
<span class="name">{{ $t('setting.watermarkTextOpacity') }}</span>
|
||||
|
|
@ -293,7 +287,6 @@
|
|||
></el-slider>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字字号 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkTextFontSize') }}</span>
|
||||
|
|
@ -308,7 +301,6 @@
|
|||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 旋转角度 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkAngle') }}</span>
|
||||
|
|
@ -323,7 +315,6 @@
|
|||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印行间距 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">{{ $t('setting.watermarkLineSpacing') }}</span>
|
||||
|
|
@ -336,7 +327,6 @@
|
|||
></el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 水印文字间距 -->
|
||||
<div class="row" style="margin-bottom: 0;">
|
||||
<div class="rowItem" style="margin-bottom: 0;">
|
||||
<span class="name">{{ $t('setting.watermarkTextSpacing') }}</span>
|
||||
|
|
@ -350,7 +340,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否显示滚动条 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -360,7 +349,6 @@
|
|||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 是否显示底部工具栏 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -371,7 +359,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="smmSidebarGroupTitle">{{ $t('setting.title4') }}</div>
|
||||
<!-- 配置性能模式 -->
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<el-checkbox
|
||||
|
|
@ -383,6 +370,12 @@
|
|||
"
|
||||
>{{ $t('setting.openPerformance') }}</el-checkbox
|
||||
>
|
||||
<span
|
||||
class="iconfont iconbangzhu"
|
||||
style="margin-left: 4px;"
|
||||
:aria-label="$t('setting.openPerformanceTip')"
|
||||
data-tooltip-position="left"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -425,7 +418,10 @@ export default {
|
|||
imgTextMargin: 0,
|
||||
textContentMargin: 0,
|
||||
enableInheritAncestorLineStyle: false,
|
||||
rootEnableUnExpand: false
|
||||
rootEnableUnExpand: false,
|
||||
demonstrateConfig: {
|
||||
openBlankMode: false
|
||||
}
|
||||
},
|
||||
watermarkConfig: {
|
||||
show: false,
|
||||
|
|
@ -471,7 +467,6 @@ export default {
|
|||
methods: {
|
||||
...mapMutations(['setLocalConfig']),
|
||||
|
||||
// 初始化其他配置
|
||||
initConfig() {
|
||||
Object.keys(this.config).forEach(key => {
|
||||
if (typeof this.config[key] === 'object') {
|
||||
|
|
@ -484,14 +479,12 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
// 初始化本地配置
|
||||
initLoacalConfig() {
|
||||
Object.keys(this.localConfigs).forEach(key => {
|
||||
this.localConfigs[key] = this.localConfig[key]
|
||||
})
|
||||
},
|
||||
|
||||
// 初始化水印配置
|
||||
initWatermark() {
|
||||
const config = this.mindMap.getConfig('watermarkConfig')
|
||||
;['text', 'lineSpacing', 'textSpacing', 'angle', 'onlyExport'].forEach(
|
||||
|
|
@ -503,12 +496,24 @@ export default {
|
|||
this.watermarkConfig.textStyle = { ...config.textStyle }
|
||||
},
|
||||
|
||||
// 更新其他配置
|
||||
updateOtherConfig(key, value) {
|
||||
this.mindMap.updateConfig({
|
||||
[key]: value
|
||||
})
|
||||
this.configData[key] = value
|
||||
if (key === 'openBlankMode') {
|
||||
this.mindMap.updateConfig({
|
||||
demonstrateConfig: {
|
||||
...(this.mindMap.getConfig('demonstrateConfig') || {}),
|
||||
openBlankMode: value
|
||||
}
|
||||
})
|
||||
if (!this.configData.demonstrateConfig) {
|
||||
this.configData.demonstrateConfig = {}
|
||||
}
|
||||
this.configData.demonstrateConfig[key] = value
|
||||
} else {
|
||||
this.mindMap.updateConfig({
|
||||
[key]: value
|
||||
})
|
||||
this.configData[key] = value
|
||||
}
|
||||
this.$root.$obsidianAPI.saveMindMapConfig(this.configData)
|
||||
if (
|
||||
[
|
||||
|
|
@ -522,7 +527,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 更新水印配置
|
||||
updateWatermarkConfig() {
|
||||
clearTimeout(this.updateWatermarkTimer)
|
||||
this.updateWatermarkTimer = setTimeout(() => {
|
||||
|
|
@ -537,7 +541,6 @@ export default {
|
|||
}, 300)
|
||||
},
|
||||
|
||||
// 切换显示水印与否
|
||||
watermarkShowChange(value) {
|
||||
if (value) {
|
||||
let text =
|
||||
|
|
@ -549,14 +552,12 @@ export default {
|
|||
this.updateWatermarkConfig()
|
||||
},
|
||||
|
||||
// 本地配置
|
||||
updateLocalConfig(key, value) {
|
||||
this.setLocalConfig({
|
||||
[key]: value
|
||||
})
|
||||
},
|
||||
|
||||
// 导出配置
|
||||
getOutputConfig() {
|
||||
return {
|
||||
localConfig: this.localConfigs || {},
|
||||
|
|
@ -564,7 +565,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
// 导入配置
|
||||
setConfig({ localConfig, config }) {
|
||||
Object.keys(localConfig).forEach(key => {
|
||||
const value = localConfig[key]
|
||||
|
|
@ -573,7 +573,10 @@ export default {
|
|||
})
|
||||
Object.keys(config).forEach(key => {
|
||||
const value = config[key]
|
||||
if (key === 'watermarkConfig') {
|
||||
if (key === 'demonstrateConfig') {
|
||||
this.config[key] = value
|
||||
this.updateOtherConfig('openBlankMode', value.openBlankMode)
|
||||
} else if (key === 'watermarkConfig') {
|
||||
Object.keys(value).forEach(key2 => {
|
||||
this.watermarkConfig[key2] = value[key2]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tip">{{ $t('shortcutKey.tip') }}</div>
|
||||
</div>
|
||||
</Sidebar>
|
||||
</template>
|
||||
|
|
@ -25,7 +24,6 @@ import Sidebar from './Sidebar.vue'
|
|||
import { shortcutKeyList } from '@/config'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
// 快捷键
|
||||
export default {
|
||||
components: {
|
||||
Sidebar
|
||||
|
|
@ -116,12 +114,5 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
font-size: 14px;
|
||||
color: #909090;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue