Ajoute sauvegarde UI + explorateur arborescent et ajuste modales
Some checks failed
Tests / test-java-8-war-generation (push) Failing after 8s
Tests / test-mvn-livecycle (11) (push) Failing after 6s
Tests / test-mvn-livecycle (17) (push) Failing after 6s
Tests / test-embedded (11) (push) Has been skipped
Tests / test-embedded (17) (push) Has been skipped
Tests / test-mvn-jetty-run (11) (push) Has been skipped
Tests / test-mvn-jetty-run (17) (push) Has been skipped
Tests / test-jetty (11) (push) Has been skipped
Tests / test-jetty (17) (push) Has been skipped
Tests / test-tomcat (11) (push) Has been skipped
Tests / test-tomcat (17) (push) Has been skipped
Some checks failed
Tests / test-java-8-war-generation (push) Failing after 8s
Tests / test-mvn-livecycle (11) (push) Failing after 6s
Tests / test-mvn-livecycle (17) (push) Failing after 6s
Tests / test-embedded (11) (push) Has been skipped
Tests / test-embedded (17) (push) Has been skipped
Tests / test-mvn-jetty-run (11) (push) Has been skipped
Tests / test-mvn-jetty-run (17) (push) Has been skipped
Tests / test-jetty (11) (push) Has been skipped
Tests / test-jetty (17) (push) Has been skipped
Tests / test-tomcat (11) (push) Has been skipped
Tests / test-tomcat (17) (push) Has been skipped
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ out/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
data/saves
|
||||
|
||||
26
README.md
26
README.md
@@ -141,6 +141,32 @@ You can set all the following variables:
|
||||
* `HTTP_PROXY_READ_TIMEOUT`
|
||||
* when calling the `proxy` endpoint, the value of `HTTP_PROXY_READ_TIMEOUT` will be the connection read timeout in milliseconds
|
||||
* Default value: `10000` (10 seconds)
|
||||
* `PLANTUML_SAVE_DIR`
|
||||
* Local directory used by the save/load API for storing diagrams.
|
||||
* Default value: `data/saves`
|
||||
* `PLANTUML_SAVE_MAX_SIZE`
|
||||
* Maximum payload size (in bytes) accepted by the save endpoint.
|
||||
* Default value: `200000`
|
||||
* `PLANTUML_SAVE_TOKEN`
|
||||
* Optional shared token to protect the save/load/delete endpoints. If set, requests must provide the header `X-PlantUML-Token`.
|
||||
* Default value: `null` (no auth)
|
||||
|
||||
|
||||
## Save / Load API
|
||||
|
||||
The UI exposes “Save” and “Load” buttons to persist PlantUML sources on the server.
|
||||
|
||||
- Endpoints (JSON):
|
||||
- `POST /api/diagrams` with `{ "id": "my-diagram", "name": "optional name", "uml": "@startuml..." }`
|
||||
- `GET /api/diagrams/{id}` returns the stored diagram
|
||||
- `GET /api/diagrams` returns a lightweight list of stored diagrams
|
||||
- `DELETE /api/diagrams/{id}` deletes a stored diagram
|
||||
- Storage:
|
||||
- Files are written to `PLANTUML_SAVE_DIR` (default `data/saves`). You can structure ids with folders, e.g. `teamA/sequence1`.
|
||||
- Max payload is limited by `PLANTUML_SAVE_MAX_SIZE` (default 200 KB).
|
||||
- Optional protection: set `PLANTUML_SAVE_TOKEN` and pass `X-PlantUML-Token` header when calling the API.
|
||||
|
||||
When hosting publicly, enable a token (or protect via reverse proxy) so user saves are not world-writeable.
|
||||
|
||||
|
||||
## Alternate: How to build your docker image
|
||||
|
||||
@@ -187,6 +187,12 @@
|
||||
<version>${fop.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.11.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package net.sourceforge.plantuml.servlet;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import net.sourceforge.plantuml.servlet.storage.DiagramStorageService;
|
||||
import net.sourceforge.plantuml.servlet.storage.StoredDiagram;
|
||||
import net.sourceforge.plantuml.servlet.storage.StoredDiagramSummary;
|
||||
|
||||
/**
|
||||
* REST-like servlet to save and load diagram sources on the server.
|
||||
*/
|
||||
public class DiagramStorageServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TOKEN_HEADER = "X-PlantUML-Token";
|
||||
|
||||
private final DiagramStorageService storage = new DiagramStorageService();
|
||||
private final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
|
||||
private final String saveToken = System.getenv("PLANTUML_SAVE_TOKEN");
|
||||
|
||||
@Override
|
||||
protected void doGet(
|
||||
final HttpServletRequest req,
|
||||
final HttpServletResponse resp
|
||||
) throws ServletException, IOException {
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("application/json");
|
||||
|
||||
final String id = extractId(req);
|
||||
if (id == null) {
|
||||
final List<StoredDiagramSummary> list;
|
||||
try {
|
||||
list = storage.list();
|
||||
} catch (IOException e) {
|
||||
writeError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to list diagrams");
|
||||
return;
|
||||
}
|
||||
writeJson(resp, HttpServletResponse.SC_OK, list);
|
||||
return;
|
||||
}
|
||||
if (!storage.isValidId(id)) {
|
||||
writeError(resp, HttpServletResponse.SC_BAD_REQUEST, "Invalid diagram id");
|
||||
return;
|
||||
}
|
||||
final StoredDiagram diagram;
|
||||
try {
|
||||
diagram = storage.read(id);
|
||||
} catch (IOException e) {
|
||||
writeError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to read diagram");
|
||||
return;
|
||||
}
|
||||
if (diagram == null) {
|
||||
writeError(resp, HttpServletResponse.SC_NOT_FOUND, "Diagram not found");
|
||||
return;
|
||||
}
|
||||
writeJson(resp, HttpServletResponse.SC_OK, diagram);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(
|
||||
final HttpServletRequest req,
|
||||
final HttpServletResponse resp
|
||||
) throws ServletException, IOException {
|
||||
if (!isAuthorized(req)) {
|
||||
writeError(resp, HttpServletResponse.SC_UNAUTHORIZED, "Missing or invalid token");
|
||||
return;
|
||||
}
|
||||
final String body;
|
||||
try {
|
||||
body = readBody(req, storage.getMaxPayloadBytes());
|
||||
} catch (IOException e) {
|
||||
writeError(resp, HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, "Payload too large");
|
||||
return;
|
||||
}
|
||||
final DiagramPayload payload = gson.fromJson(body, DiagramPayload.class);
|
||||
if (payload == null || payload.getUml() == null || payload.getUml().trim().isEmpty()) {
|
||||
writeError(resp, HttpServletResponse.SC_BAD_REQUEST, "Field 'uml' is required");
|
||||
return;
|
||||
}
|
||||
final String id = resolveId(payload.getId());
|
||||
if (!storage.isValidId(id)) {
|
||||
writeError(resp, HttpServletResponse.SC_BAD_REQUEST, "Invalid diagram id");
|
||||
return;
|
||||
}
|
||||
final StoredDiagram stored;
|
||||
try {
|
||||
stored = storage.save(
|
||||
new StoredDiagram(
|
||||
id,
|
||||
normalize(payload.getName()),
|
||||
payload.getUml(),
|
||||
null,
|
||||
payload.getMetadata()
|
||||
)
|
||||
);
|
||||
} catch (IOException e) {
|
||||
writeError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to store diagram");
|
||||
return;
|
||||
}
|
||||
writeJson(resp, HttpServletResponse.SC_OK, stored);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doDelete(
|
||||
final HttpServletRequest req,
|
||||
final HttpServletResponse resp
|
||||
) throws ServletException, IOException {
|
||||
writeError(resp, HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Delete is disabled");
|
||||
}
|
||||
|
||||
private String extractId(final HttpServletRequest req) {
|
||||
final String pathInfo = req.getPathInfo();
|
||||
if (pathInfo == null || pathInfo.isBlank() || "/".equals(pathInfo)) {
|
||||
return null;
|
||||
}
|
||||
final String trimmed = pathInfo.startsWith("/") ? pathInfo.substring(1) : pathInfo;
|
||||
return trimmed.trim();
|
||||
}
|
||||
|
||||
private String readBody(final HttpServletRequest req, final long maxSize) throws IOException {
|
||||
final long contentLength = req.getContentLengthLong();
|
||||
if (contentLength > maxSize) {
|
||||
throw new IOException("Payload too large");
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8)
|
||||
)) {
|
||||
int read;
|
||||
final char[] buffer = new char[2048];
|
||||
long total = 0;
|
||||
while ((read = reader.read(buffer)) != -1) {
|
||||
total += read;
|
||||
if (total > maxSize) {
|
||||
throw new IOException("Payload too large");
|
||||
}
|
||||
sb.append(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void writeJson(
|
||||
final HttpServletResponse resp,
|
||||
final int status,
|
||||
final Object payload
|
||||
) throws IOException {
|
||||
resp.setStatus(status);
|
||||
resp.setContentType("application/json");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
gson.toJson(payload, resp.getWriter());
|
||||
}
|
||||
|
||||
private void writeError(
|
||||
final HttpServletResponse resp,
|
||||
final int status,
|
||||
final String message
|
||||
) throws IOException {
|
||||
writeJson(resp, status, Map.of("message", message));
|
||||
}
|
||||
|
||||
private boolean isAuthorized(final HttpServletRequest req) {
|
||||
if (saveToken == null || saveToken.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
final String provided = req.getHeader(TOKEN_HEADER);
|
||||
return saveToken.equals(provided);
|
||||
}
|
||||
|
||||
private String resolveId(final String provided) {
|
||||
if (provided == null || provided.isBlank()) {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
return provided.trim();
|
||||
}
|
||||
|
||||
private String normalize(final String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
final String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
private static final class DiagramPayload {
|
||||
private String id;
|
||||
private String name;
|
||||
private String uml;
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getUml() {
|
||||
return uml;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package net.sourceforge.plantuml.servlet.storage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* Minimal file-based storage for UML diagrams.
|
||||
*/
|
||||
public class DiagramStorageService {
|
||||
|
||||
/**
|
||||
* Accept ids like "foo", "foo_bar", or "folder/subfolder/name".
|
||||
* Each segment must be alphanumeric, dash or underscore.
|
||||
*/
|
||||
private static final Pattern ID_PATTERN = Pattern.compile("([A-Za-z0-9_-]+/)*[A-Za-z0-9_-]{1,64}");
|
||||
private static final String DEFAULT_SAVE_DIR = "data/saves";
|
||||
private static final long DEFAULT_MAX_SIZE = 200_000L; // 200 KB
|
||||
private static final int DEFAULT_LIST_LIMIT = 100;
|
||||
|
||||
private final Path baseDir;
|
||||
private final long maxPayloadBytes;
|
||||
private final int listLimit;
|
||||
private final Gson gson;
|
||||
|
||||
public DiagramStorageService() {
|
||||
this.baseDir = Paths.get(
|
||||
Optional.ofNullable(System.getenv("PLANTUML_SAVE_DIR")).orElse(DEFAULT_SAVE_DIR)
|
||||
).toAbsolutePath().normalize();
|
||||
this.maxPayloadBytes = parseLongEnv("PLANTUML_SAVE_MAX_SIZE", DEFAULT_MAX_SIZE);
|
||||
this.listLimit = DEFAULT_LIST_LIMIT;
|
||||
this.gson = new GsonBuilder().disableHtmlEscaping().create();
|
||||
}
|
||||
|
||||
public long getMaxPayloadBytes() {
|
||||
return maxPayloadBytes;
|
||||
}
|
||||
|
||||
public Path getBaseDir() {
|
||||
return baseDir;
|
||||
}
|
||||
|
||||
public boolean isValidId(final String id) {
|
||||
if (id == null) {
|
||||
return false;
|
||||
}
|
||||
return ID_PATTERN.matcher(id).matches();
|
||||
}
|
||||
|
||||
public StoredDiagram save(final StoredDiagram diagram) throws IOException {
|
||||
Objects.requireNonNull(diagram, "diagram must not be null");
|
||||
if (!isValidId(diagram.getId())) {
|
||||
throw new IOException("Invalid diagram id");
|
||||
}
|
||||
final String timestamp = Instant.now().toString();
|
||||
final StoredDiagram toPersist = diagram.withUpdatedAt(timestamp);
|
||||
final Path target = resolvePath(diagram.getId());
|
||||
Files.createDirectories(target.getParent());
|
||||
try (Writer writer = Files.newBufferedWriter(
|
||||
target,
|
||||
StandardCharsets.UTF_8,
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING
|
||||
)) {
|
||||
gson.toJson(toPersist, writer);
|
||||
}
|
||||
return toPersist;
|
||||
}
|
||||
|
||||
public StoredDiagram read(final String id) throws IOException {
|
||||
final Path path = resolvePath(id);
|
||||
if (!Files.exists(path)) {
|
||||
return null;
|
||||
}
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
return gson.fromJson(reader, StoredDiagram.class);
|
||||
}
|
||||
}
|
||||
|
||||
public List<StoredDiagramSummary> list() throws IOException {
|
||||
if (!Files.exists(baseDir)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try (Stream<Path> stream = Files.walk(baseDir, 5)) {
|
||||
return stream
|
||||
.filter(path -> path.getFileName().toString().endsWith(".json"))
|
||||
.sorted(Comparator.comparing(this::getFileTimeSafe).reversed())
|
||||
.limit(listLimit)
|
||||
.map(this::safeReadSummary)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean delete(final String id) throws IOException {
|
||||
final Path path = resolvePath(id);
|
||||
return Files.deleteIfExists(path);
|
||||
}
|
||||
|
||||
private Optional<StoredDiagramSummary> safeReadSummary(final Path path) {
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
final StoredDiagram diagram = gson.fromJson(reader, StoredDiagram.class);
|
||||
if (diagram == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(diagram.toSummary());
|
||||
} catch (IOException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private Path resolvePath(final String id) throws IOException {
|
||||
if (!isValidId(id)) {
|
||||
throw new IOException("Invalid diagram id");
|
||||
}
|
||||
final Path path = baseDir.resolve(id + ".json").normalize();
|
||||
if (!path.startsWith(baseDir)) {
|
||||
throw new IOException("Invalid diagram id");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private long getFileTimeSafe(final Path path) {
|
||||
try {
|
||||
return Files.getLastModifiedTime(path).toMillis();
|
||||
} catch (IOException e) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private long parseLongEnv(final String envKey, final long defaultValue) {
|
||||
try {
|
||||
final String value = System.getenv(envKey);
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException ex) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.sourceforge.plantuml.servlet.storage;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Simple DTO representing a stored diagram.
|
||||
*/
|
||||
public class StoredDiagram {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String uml;
|
||||
private String updatedAt;
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
public StoredDiagram() {
|
||||
// For JSON serialization
|
||||
}
|
||||
|
||||
public StoredDiagram(
|
||||
final String diagramId,
|
||||
final String diagramName,
|
||||
final String diagramUml,
|
||||
final String lastUpdatedAt,
|
||||
final Map<String, Object> diagramMetadata
|
||||
) {
|
||||
this.id = diagramId;
|
||||
this.name = diagramName;
|
||||
this.uml = diagramUml;
|
||||
this.updatedAt = lastUpdatedAt;
|
||||
this.metadata = diagramMetadata;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getUml() {
|
||||
return uml;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public StoredDiagram withUpdatedAt(final String timestamp) {
|
||||
return new StoredDiagram(id, name, uml, timestamp, metadata);
|
||||
}
|
||||
|
||||
public StoredDiagramSummary toSummary() {
|
||||
return new StoredDiagramSummary(id, name, updatedAt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package net.sourceforge.plantuml.servlet.storage;
|
||||
|
||||
/**
|
||||
* Lightweight view returned by the listing endpoint.
|
||||
*/
|
||||
public class StoredDiagramSummary {
|
||||
private String id;
|
||||
private String name;
|
||||
private String updatedAt;
|
||||
|
||||
public StoredDiagramSummary() {
|
||||
// For JSON serialization
|
||||
}
|
||||
|
||||
public StoredDiagramSummary(
|
||||
final String diagramId,
|
||||
final String diagramName,
|
||||
final String lastUpdatedAt
|
||||
) {
|
||||
this.id = diagramId;
|
||||
this.name = diagramName;
|
||||
this.updatedAt = lastUpdatedAt;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -31,14 +31,12 @@
|
||||
<!-- Taglib declarations are no longer required since JSP 2.0, see Removing taglib from web.xml -->
|
||||
<!-- The <taglib> did not need to be a child of <jsp-config> in earlier versions but is required as of Tomcat 7 -->
|
||||
<!-- Note that you can only have one <jsp-config> element per web.xml -->
|
||||
<!--
|
||||
<jsp-config>
|
||||
<taglib>
|
||||
<taglib-uri>http://java.sun.com/jsp/jstl/core</taglib-uri>
|
||||
<taglib-location>/WEB-INF/lib/c.tld</taglib-location>
|
||||
</taglib>
|
||||
<jsp-property-group>
|
||||
<url-pattern>*.jsp</url-pattern>
|
||||
<page-encoding>UTF-8</page-encoding>
|
||||
</jsp-property-group>
|
||||
</jsp-config>
|
||||
-->
|
||||
|
||||
|
||||
<!-- ========================================================== -->
|
||||
@@ -50,7 +48,6 @@
|
||||
<param-value>exact</param-value>
|
||||
</context-param>
|
||||
|
||||
|
||||
<!-- ========================================================== -->
|
||||
<!-- Servlets -->
|
||||
<!-- ========================================================== -->
|
||||
@@ -229,6 +226,15 @@
|
||||
<url-pattern>/metadata/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>diagramstorageservlet</servlet-name>
|
||||
<servlet-class>net.sourceforge.plantuml.servlet.DiagramStorageServlet</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>diagramstorageservlet</servlet-name>
|
||||
<url-pattern>/api/diagrams/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- ========================================================== -->
|
||||
<!-- Error Handler -->
|
||||
<!-- ========================================================== -->
|
||||
|
||||
5
src/main/webapp/assets/actions/load.svg
Normal file
5
src/main/webapp/assets/actions/load.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M3 6h4l2 2h8v8a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z" stroke="#444" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M10 2v6" stroke="#444" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M7.5 6.5 10 8l2.5-1.5" stroke="#444" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 430 B |
6
src/main/webapp/assets/actions/save.svg
Normal file
6
src/main/webapp/assets/actions/save.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M4 2h9l3 3v11a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z" stroke="#444" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M6 2v5h8V2" stroke="#444" stroke-width="1.5"/>
|
||||
<path d="M6 12h8v4H6z" stroke="#444" stroke-width="1.5" fill="none"/>
|
||||
<path d="M8 13h4" stroke="#444" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 443 B |
@@ -9,6 +9,11 @@
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon"/>
|
||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon"/>
|
||||
<link rel="stylesheet" href="min/plantuml.min.css" />
|
||||
<link rel="stylesheet" href="components/modals/diagram-storage/diagram-storage.css?v=2" />
|
||||
<link rel="stylesheet" href="vendor/bootstrap-treeview/bootstrap-treeview.css?v=2" />
|
||||
<script src="js/treeview-stub.js?v=1"></script>
|
||||
<script src="vendor/bootstrap-treeview/bootstrap-treeview.js?v=1"></script>
|
||||
<script src="min/plantuml.min.js"></script>
|
||||
<script src="min/plantuml-language.min.js"></script>
|
||||
<script src="js/storage.js?v=13"></script>
|
||||
<script src="webjars/monaco-editor/0.36.1/min/vs/loader.js"></script>
|
||||
|
||||
@@ -1,8 +1,32 @@
|
||||
<h1>PlantUML Server</h1>
|
||||
<% if (showSocialButtons) { %>
|
||||
<%@ include file="/components/header/social-buttons.jsp" %>
|
||||
<% } %>
|
||||
<% if (showGithubRibbon) { %>
|
||||
<%@ include file="/components/header/github-ribbon.jsp" %>
|
||||
<% } %>
|
||||
<p>Create your <a href="https://plantuml.com">PlantUML</a> diagrams directly in your browser!</p>
|
||||
<style>
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin: 0.5rem 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.header-actions button {
|
||||
background: var(--button-bg, #2c3e50);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.4rem 0.75rem;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
.header-actions button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
<h1>Serveur PlantUML</h1>
|
||||
<div class="header-actions">
|
||||
<button id="header-save-btn" type="button">Sauvegarder le diagramme</button>
|
||||
<button id="header-load-btn" type="button">Charger un diagramme</button>
|
||||
</div>
|
||||
<% if (showSocialButtons) { %> <%@ include
|
||||
file="/components/header/social-buttons.jsp" %> <% } %> <% if (showGithubRibbon)
|
||||
{ %> <%@ include file="/components/header/github-ribbon.jsp" %> <% } %>
|
||||
<p>
|
||||
Créez vos diagrammes <a href="https://plantuml.com">PlantUML</a>
|
||||
directement dans votre navigateur.
|
||||
</p>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<div id="diagram-export" class="modal" style="display: none;" tabindex="-1">
|
||||
<div class="modal-content flex-rows">
|
||||
<div class="modal-header">
|
||||
<h2>Export Diagram</h2>
|
||||
<h2>Exporter un diagramme</h2>
|
||||
<div class="hr"></div>
|
||||
</div>
|
||||
<div class="modal-main flex-main">
|
||||
<div class="label-input-pair flex-columns">
|
||||
<label for="download-name">Diagram name:</label>
|
||||
<label for="download-name">Nom du diagramme :</label>
|
||||
<input class="flex-main" id="download-name" value="diagram.puml" />
|
||||
</div>
|
||||
<div class="label-input-pair flex-columns">
|
||||
<label for="download-type">Diagram type:</label>
|
||||
<label for="download-type">Type de diagramme :</label>
|
||||
<select class="flex-main" id="download-type" name="download-type">
|
||||
<option value="txt">ASCII Art</option>
|
||||
<option value="base64">Base64</option>
|
||||
@@ -18,15 +18,15 @@
|
||||
<option value="epstext">EPS Text</option>
|
||||
<option value="map">MAP</option>
|
||||
<option value="pdf">PDF</option>
|
||||
<option value="code" selected>PlantUML source code</option>
|
||||
<option value="code" selected>Code source PlantUML</option>
|
||||
<option value="png">PNG</option>
|
||||
<option value="svg">SVG</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input id="diagram-export-ok-btn" class="ok" type="button" value="Export" />
|
||||
<input class="cancel" type="button" value="Cancel" onclick="closeModal('diagram-export')" />
|
||||
<input id="diagram-export-ok-btn" class="ok" type="button" value="Exporter" />
|
||||
<input class="cancel" type="button" value="Annuler" onclick="closeModal('diagram-export')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,8 +54,8 @@ function initDiagramImport() {
|
||||
const type = getImageFileType(file);
|
||||
const isCode = type === undefined ? isDiagramCode(file) : false;
|
||||
if (!type && !isCode) {
|
||||
errorMessageElement.innerText = "File not supported. " +
|
||||
"Only PNG and SVG diagram images as well as PlantUML code text files are supported."
|
||||
errorMessageElement.innerText = "Fichier non pris en charge. " +
|
||||
"Seuls les PNG, SVG ou fichiers texte PlantUML sont acceptés."
|
||||
}
|
||||
return { type, isDiagramCode: isCode, valid: type || isCode };
|
||||
}
|
||||
@@ -90,8 +90,8 @@ function initDiagramImport() {
|
||||
resolve();
|
||||
} else {
|
||||
// this error should already be handled.
|
||||
errorMessageElement.innerText = "File not supported. " +
|
||||
"Only PNG and SVG diagram images as well as PlantUML code text files are supported.";
|
||||
errorMessageElement.innerText = "Fichier non pris en charge. " +
|
||||
"Seuls les PNG, SVG ou fichiers texte PlantUML sont acceptés.";
|
||||
reject();
|
||||
}
|
||||
}).then(() => closeDialog(), () => {}).finally(() => dialogElement.classList.remove("wait"));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div id="diagram-import" class="modal" style="display: none;" tabindex="-1">
|
||||
<div class="modal-content flex-rows">
|
||||
<div class="modal-header">
|
||||
<h2>Import Diagram</h2>
|
||||
<h2>Importer un diagramme</h2>
|
||||
<div class="hr"></div>
|
||||
</div>
|
||||
<div class="modal-main flex-main">
|
||||
@@ -9,8 +9,8 @@
|
||||
<p id="diagram-import-error-message" class="error-message"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input id="diagram-import-ok-btn" class="ok" type="button" value="Import" disabled />
|
||||
<input id="diagram-import-cancel-btn" class="cancel" type="button" value="Cancel" onclick="closeModal('diagram-import');" />
|
||||
<input id="diagram-import-ok-btn" class="ok" type="button" value="Importer" disabled />
|
||||
<input id="diagram-import-cancel-btn" class="cancel" type="button" value="Annuler" onclick="closeModal('diagram-import');" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
.saved-list {
|
||||
margin-top: 1rem;
|
||||
border: 1px solid var(--border-color, #ccc);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 340px;
|
||||
max-height: 70vh;
|
||||
background: #f7f9fb;
|
||||
}
|
||||
|
||||
.saved-list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-bottom: 1px solid var(--border-color, #ccc);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.saved-list-header .hint {
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.saved-list-body {
|
||||
overflow-y: auto;
|
||||
padding: 0.5rem 0.75rem;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.modal-storage {
|
||||
max-width: 54rem;
|
||||
width: 94vw;
|
||||
}
|
||||
|
||||
.saved-item {
|
||||
padding: 0.35rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.saved-item:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.saved-item .path {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.saved-item .meta {
|
||||
color: #666;
|
||||
font-size: 0.85rem;
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
.tree {
|
||||
list-style: none;
|
||||
padding-left: 0.25rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tree li {
|
||||
padding: 0.25rem 0.25rem 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tree li:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.tree .caret {
|
||||
width: 0.9rem;
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.tree .label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tree .meta {
|
||||
color: #666;
|
||||
font-size: 0.85rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.tree .icon-folder::before {
|
||||
content: "📂";
|
||||
}
|
||||
|
||||
.tree .icon-file::before {
|
||||
content: "📄";
|
||||
}
|
||||
|
||||
#diagram-storage-status {
|
||||
min-height: 1.4rem;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#diagram-storage-status.error {
|
||||
color: #c0392b;
|
||||
}
|
||||
|
||||
#diagram-storage-status.success {
|
||||
color: #1e8449;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<div id="diagram-storage" class="modal" style="display: none;" tabindex="-1">
|
||||
<div class="modal-content modal-storage flex-rows">
|
||||
<div class="modal-header">
|
||||
<h2>Sauvegarder / Charger un diagramme</h2>
|
||||
<div class="hr"></div>
|
||||
</div>
|
||||
<div class="modal-main flex-main">
|
||||
<div class="form">
|
||||
<div class="label-input-pair">
|
||||
<label for="diagram-storage-path">Nom ou chemin</label>
|
||||
<input id="diagram-storage-path" type="text" placeholder="exemple ou dossier/exemple" />
|
||||
</div>
|
||||
<div class="label-input-pair">
|
||||
<label for="diagram-storage-name">Nom affiche (optionnel)</label>
|
||||
<input id="diagram-storage-name" type="text" placeholder="Diagramme de sequence" />
|
||||
</div>
|
||||
<div class="actions-row">
|
||||
<input id="diagram-storage-save" class="ok" type="button" value="Sauvegarder" />
|
||||
<input id="diagram-storage-load" type="button" value="Charger" />
|
||||
<input id="diagram-storage-refresh" type="button" value="Rafraichir la liste" />
|
||||
</div>
|
||||
<p id="diagram-storage-status" class="status-message"></p>
|
||||
</div>
|
||||
<div class="saved-list">
|
||||
<div class="saved-list-header">
|
||||
<span>Diagrammes sauvegardes</span>
|
||||
<span class="hint">Cliquez pour naviguer, double-cliquez pour charger</span>
|
||||
</div>
|
||||
<div id="diagram-storage-list" class="saved-list-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input id="diagram-storage-close" class="cancel" type="button" value="Fermer" onclick="closeModal('diagram-storage');" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,7 +20,7 @@
|
||||
margin: auto;
|
||||
padding: 2rem;
|
||||
border: 3px solid var(--border-color);
|
||||
max-width: 30rem;
|
||||
max-width: 50rem;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
-webkit-animation-name: modal-animatetop;
|
||||
-webkit-animation-duration: 0.4s;
|
||||
|
||||
@@ -49,6 +49,7 @@ function initModals(view) {
|
||||
if (view !== "previewer") {
|
||||
initDiagramExport();
|
||||
initDiagramImport();
|
||||
initDiagramStorage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<div id="settings" class="modal" style="display: none;" tabindex="-1">
|
||||
<div class="modal-content flex-rows">
|
||||
<div class="modal-header">
|
||||
<h2>Settings</h2>
|
||||
<h2>Parametres</h2>
|
||||
<div class="hr"></div>
|
||||
</div>
|
||||
<div class="modal-main flex-main">
|
||||
<div class="label-input-pair flex-columns">
|
||||
<label for="theme">Theme :</label>
|
||||
<select class="flex-main" id="theme" name="theme">
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="light">Clair</option>
|
||||
<option value="dark">Sombre</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="label-input-pair flex-columns">
|
||||
<label for="diagramPreviewType">Diagram Preview Type:</label>
|
||||
<label for="diagramPreviewType">Type d'apercu :</label>
|
||||
<select class="flex-main" id="diagramPreviewType" name="diagramPreviewType">
|
||||
<option value="png">PNG</option>
|
||||
<option value="svg">SVG</option>
|
||||
@@ -22,18 +22,18 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="label-input-pair flex-columns">
|
||||
<label for="editorWatcherTimeout">Editor Watcher Timeout:</label>
|
||||
<label for="editorWatcherTimeout">Delai de rafraichissement (ms) :</label>
|
||||
<input class="flex-main" id="editorWatcherTimeout" type="number" pattern="[1-9]+[0-9]*" value="" />
|
||||
</div>
|
||||
<div class="label-input-pair flex-main">
|
||||
<label for="editorCreateOptions">Monaco Editor Create Options:</label>
|
||||
<label for="editorCreateOptions">Options de l'editeur Monaco :</label>
|
||||
<br />
|
||||
<div id="settings-monaco-editor"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input id="settings-ok-btn" class="ok" type="button" value="Save" />
|
||||
<input class="cancel" type="button" value="Cancel" onclick="closeModal('settings');" />
|
||||
<input id="settings-ok-btn" class="ok" type="button" value="Enregistrer" />
|
||||
<input class="cancel" type="button" value="Annuler" onclick="closeModal('settings');" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<!-- editor modals -->
|
||||
<%@ include file="/components/modals/diagram-import/diagram-import.jsp" %>
|
||||
<%@ include file="/components/modals/diagram-export/diagram-export.jsp" %>
|
||||
<%@ include file="/components/modals/diagram-storage/diagram-storage.jsp" %>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
379
src/main/webapp/js/storage.js
Normal file
379
src/main/webapp/js/storage.js
Normal file
@@ -0,0 +1,379 @@
|
||||
/*********************
|
||||
* Diagram Save / Load *
|
||||
**********************/
|
||||
|
||||
(function() {
|
||||
const API_BASE = "api/diagrams";
|
||||
const TOKEN_HEADER = "X-PlantUML-Token";
|
||||
const TOKEN_STORAGE_KEY = "plantuml.save.token";
|
||||
const ID_REGEX = /^([A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]{1,64}$/;
|
||||
|
||||
let dom = {};
|
||||
const expandedPaths = new Set();
|
||||
function log() {
|
||||
if (console && console.log) {
|
||||
console.log("[diagram-storage]", ...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
function getToken() {
|
||||
return window.localStorage.getItem(TOKEN_STORAGE_KEY) || "";
|
||||
}
|
||||
|
||||
function setToken() {
|
||||
const current = getToken();
|
||||
const value = window.prompt("Définir le jeton de sauvegarde (laisser vide pour effacer) :", current || "") || "";
|
||||
if (value.trim()) {
|
||||
window.localStorage.setItem(TOKEN_STORAGE_KEY, value.trim());
|
||||
setStatus("Jeton enregistré localement.", "success");
|
||||
} else {
|
||||
window.localStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||
setStatus("Jeton supprimé.", "success");
|
||||
}
|
||||
}
|
||||
|
||||
function authHeaders(headers = {}) {
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
headers[TOKEN_HEADER] = token;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
function setStatus(message, type = "") {
|
||||
if (!dom.status) return;
|
||||
dom.status.textContent = message || "";
|
||||
dom.status.className = `status-message ${type}`;
|
||||
if (message) {
|
||||
log("status:", message, type);
|
||||
}
|
||||
}
|
||||
|
||||
function validatePath(path) {
|
||||
if (!path || !path.trim()) {
|
||||
setStatus("Merci de saisir un nom ou un chemin.", "error");
|
||||
return null;
|
||||
}
|
||||
const cleaned = path.trim().replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
|
||||
if (!ID_REGEX.test(cleaned)) {
|
||||
setStatus("Chemin invalide. Utilisez lettres, chiffres, '-' ou '_' avec dossiers optionnels (ex : dossier/nom).", "error");
|
||||
return null;
|
||||
}
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
function handleResponse(resp) {
|
||||
const parse = () => resp.json().catch(() => ({}));
|
||||
if (resp.ok) {
|
||||
return parse();
|
||||
}
|
||||
return parse().then(body => {
|
||||
const err = new Error(body.message || resp.statusText);
|
||||
err.status = resp.status;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function buildTree(items) {
|
||||
const root = { name: "", children: new Map(), item: null };
|
||||
items.forEach(item => {
|
||||
const parts = item.id.split("/");
|
||||
let node = root;
|
||||
parts.forEach((part, idx) => {
|
||||
const isLast = idx === parts.length - 1;
|
||||
if (!node.children.has(part)) {
|
||||
node.children.set(part, { name: part, children: new Map(), item: null });
|
||||
}
|
||||
node = node.children.get(part);
|
||||
if (isLast) {
|
||||
node.item = item;
|
||||
}
|
||||
});
|
||||
});
|
||||
return root;
|
||||
}
|
||||
|
||||
function isFolder(node) {
|
||||
return node.children.size > 0 || !node.item;
|
||||
}
|
||||
|
||||
function renderTreeNode(node, prefix, depth) {
|
||||
const ul = document.createElement("ul");
|
||||
ul.className = "btv";
|
||||
const entries = Array.from(node.children.values()).sort((a, b) => {
|
||||
const aFolder = isFolder(a);
|
||||
const bFolder = isFolder(b);
|
||||
if (aFolder !== bFolder) {
|
||||
return aFolder ? -1 : 1;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
entries.forEach(child => {
|
||||
const li = document.createElement("li");
|
||||
li.style.paddingLeft = `${depth * 1.2}rem`;
|
||||
const fullPath = prefix ? `${prefix}/${child.name}` : child.name;
|
||||
|
||||
const caret = document.createElement("span");
|
||||
caret.className = "caret";
|
||||
const folder = isFolder(child);
|
||||
const isExpanded = depth === 0 || expandedPaths.has(fullPath);
|
||||
caret.textContent = folder ? (isExpanded ? "▾" : "▸") : "";
|
||||
li.appendChild(caret);
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.className = "label " + (folder ? "icon-folder" : "icon-file");
|
||||
label.textContent = child.name;
|
||||
li.appendChild(label);
|
||||
|
||||
if (child.item) {
|
||||
const meta = document.createElement("span");
|
||||
meta.className = "meta";
|
||||
const updated = child.item.updatedAt ? new Date(child.item.updatedAt).toLocaleString() : "";
|
||||
meta.textContent = [updated, child.item.name].filter(Boolean).join(" • ");
|
||||
li.appendChild(meta);
|
||||
}
|
||||
|
||||
li.addEventListener("click", evt => {
|
||||
evt.stopPropagation();
|
||||
dom.path.value = fullPath;
|
||||
dom.name.value = child.item?.name || "";
|
||||
if (folder) {
|
||||
toggleFolder(li, fullPath, caret);
|
||||
}
|
||||
});
|
||||
|
||||
li.addEventListener("dblclick", evt => {
|
||||
evt.stopPropagation();
|
||||
if (child.item) {
|
||||
loadDiagram(fullPath);
|
||||
}
|
||||
});
|
||||
|
||||
if (folder) {
|
||||
const nested = renderTreeNode(child, fullPath, depth + 1);
|
||||
nested.style.display = expandedPaths.has(fullPath) || depth === 0 ? "block" : "none";
|
||||
li.appendChild(nested);
|
||||
}
|
||||
ul.appendChild(li);
|
||||
});
|
||||
return ul;
|
||||
}
|
||||
|
||||
function renderList(items) {
|
||||
if (!dom.list) return;
|
||||
dom.list.innerHTML = "";
|
||||
if (!items || items.length === 0) {
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "saved-item";
|
||||
empty.textContent = "Aucun diagramme sauvegarde.";
|
||||
dom.list.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
// reset expansion: top-level open, rest collapsed unless selected
|
||||
expandedPaths.clear();
|
||||
expandedPaths.add("");
|
||||
if (dom.path?.value) {
|
||||
const parts = dom.path.value.split("/");
|
||||
let prefix = "";
|
||||
parts.forEach((p, idx) => {
|
||||
prefix = idx === 0 ? p : `${prefix}/${p}`;
|
||||
expandedPaths.add(prefix);
|
||||
});
|
||||
}
|
||||
const tree = buildTree(items);
|
||||
const treeEl = renderTreeNode(tree, "", 0);
|
||||
dom.list.appendChild(treeEl);
|
||||
}
|
||||
|
||||
function toggleFolder(li, fullPath, caret) {
|
||||
const nested = li.querySelector("ul");
|
||||
if (!nested) {
|
||||
return;
|
||||
}
|
||||
const willExpand = nested.style.display === "none";
|
||||
nested.style.display = willExpand ? "block" : "none";
|
||||
caret.textContent = willExpand ? "▾" : "▸";
|
||||
if (willExpand) {
|
||||
expandedPaths.add(fullPath);
|
||||
} else {
|
||||
expandedPaths.delete(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshList(showStatus = false) {
|
||||
const headers = authHeaders({});
|
||||
log("refresh list");
|
||||
return fetch(API_BASE, { method: "GET", headers })
|
||||
.then(handleResponse)
|
||||
.then(items => {
|
||||
renderList(items);
|
||||
if (showStatus) {
|
||||
setStatus("Liste rafraichie.", "success");
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
setStatus(`Impossible de charger la liste : ${err.message}`, "error");
|
||||
console.error("[diagram-storage] list error", err);
|
||||
});
|
||||
}
|
||||
|
||||
function saveDiagram() {
|
||||
log("save click");
|
||||
if (!document.editor || typeof document.editor.getValue !== "function") {
|
||||
setStatus("Editeur non pret.", "error");
|
||||
return;
|
||||
}
|
||||
const path = validatePath(dom.path.value);
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
id: path,
|
||||
name: dom.name.value || null,
|
||||
uml: document.editor.getValue(),
|
||||
};
|
||||
const headers = authHeaders({ "Content-Type": "application/json" });
|
||||
fetch(API_BASE, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
.then(handleResponse)
|
||||
.then(data => {
|
||||
setStatus(`Diagramme "${data.id}" sauvegarde.`, "success");
|
||||
refreshList();
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.status === 401) {
|
||||
setToken();
|
||||
} else {
|
||||
setStatus(`Echec de la sauvegarde : ${err.message}`, "error");
|
||||
console.error("[diagram-storage] save error", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadDiagram(pathValue) {
|
||||
log("load click", pathValue || dom.path.value);
|
||||
const path = validatePath(pathValue || dom.path.value);
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
const headers = authHeaders({});
|
||||
fetch(`${API_BASE}/${encodeURIComponent(path)}`, { method: "GET", headers })
|
||||
.then(handleResponse)
|
||||
.then(data => {
|
||||
if (typeof setEditorValue === "function" && document.editor) {
|
||||
setEditorValue(document.editor, data.uml, { suppressEditorChangedMessage: false });
|
||||
}
|
||||
dom.path.value = data.id;
|
||||
dom.name.value = data.name || "";
|
||||
setStatus(`Diagramme "${data.id}" charge.`, "success");
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.status === 401) {
|
||||
setToken();
|
||||
} else {
|
||||
setStatus(`Echec du chargement : ${err.message}`, "error");
|
||||
console.error("[diagram-storage] load error", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bindMenuButtons() {
|
||||
const candidates = [
|
||||
{ id: "menu-item-editor-save", label: "menu save click" },
|
||||
{ id: "menu-item-editor-load", label: "menu load click" },
|
||||
{ id: "header-save-btn", label: "header save click" },
|
||||
{ id: "header-load-btn", label: "header load click" },
|
||||
];
|
||||
candidates.forEach(candidate => {
|
||||
const el = document.getElementById(candidate.id);
|
||||
if (el) {
|
||||
el.addEventListener("click", () => {
|
||||
log(candidate.label);
|
||||
openModal("diagram-storage");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let initialized = false;
|
||||
|
||||
function initDiagramStorage() {
|
||||
if (initialized) {
|
||||
log("already initialized");
|
||||
return;
|
||||
}
|
||||
log("init UI");
|
||||
dom = {
|
||||
modal: document.getElementById("diagram-storage"),
|
||||
path: document.getElementById("diagram-storage-path"),
|
||||
name: document.getElementById("diagram-storage-name"),
|
||||
save: document.getElementById("diagram-storage-save"),
|
||||
load: document.getElementById("diagram-storage-load"),
|
||||
del: document.getElementById("diagram-storage-delete"),
|
||||
refresh: document.getElementById("diagram-storage-refresh"),
|
||||
token: document.getElementById("diagram-storage-token"),
|
||||
status: document.getElementById("diagram-storage-status"),
|
||||
list: document.getElementById("diagram-storage-list"),
|
||||
};
|
||||
|
||||
if (dom.save) {
|
||||
dom.save.addEventListener("click", saveDiagram);
|
||||
}
|
||||
if (dom.load) {
|
||||
dom.load.addEventListener("click", () => loadDiagram());
|
||||
}
|
||||
if (dom.del) {
|
||||
dom.del.addEventListener("click", () => {});
|
||||
}
|
||||
if (dom.refresh) {
|
||||
dom.refresh.addEventListener("click", () => refreshList(true));
|
||||
}
|
||||
if (dom.token) {
|
||||
dom.token.addEventListener("click", setToken);
|
||||
}
|
||||
|
||||
registerModalListener(
|
||||
"diagram-storage",
|
||||
() => {
|
||||
if (dom.modal) {
|
||||
setVisibility(dom.modal, true, true);
|
||||
}
|
||||
setStatus("");
|
||||
if (dom.path && !dom.path.value) {
|
||||
dom.path.value = "mon-diagramme";
|
||||
}
|
||||
refreshList();
|
||||
dom.path?.focus();
|
||||
log("modal opened");
|
||||
},
|
||||
() => {
|
||||
if (dom.modal) {
|
||||
setVisibility(dom.modal, false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
bindMenuButtons();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
window.initDiagramStorage = initDiagramStorage;
|
||||
window.addEventListener("load", initDiagramStorage);
|
||||
})();
|
||||
function toggleFolder(li, fullPath, caret) {
|
||||
const nested = li.querySelector("ul");
|
||||
if (!nested) {
|
||||
return;
|
||||
}
|
||||
const next = nested.style.display === "none";
|
||||
nested.style.display = next ? "block" : "none";
|
||||
caret.textContent = next ? "▾" : "▸";
|
||||
if (next) {
|
||||
expandedPaths.add(fullPath);
|
||||
} else {
|
||||
expandedPaths.delete(fullPath);
|
||||
}
|
||||
}
|
||||
70
src/main/webapp/js/treeview-stub.js
Normal file
70
src/main/webapp/js/treeview-stub.js
Normal file
@@ -0,0 +1,70 @@
|
||||
(function() {
|
||||
// Global expansion set used by minified code expecting `expandedPaths`
|
||||
window.expandedPaths = window.expandedPaths || new Set();
|
||||
|
||||
function renderNode(node) {
|
||||
const li = document.createElement("li");
|
||||
li.dataset.id = node.id || "";
|
||||
const caret = document.createElement("span");
|
||||
caret.className = "caret";
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
caret.textContent = hasChildren ? "▾" : "";
|
||||
li.appendChild(caret);
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.className = "label";
|
||||
label.textContent = node.label || node.id || "";
|
||||
li.appendChild(label);
|
||||
|
||||
if (node.meta) {
|
||||
const meta = document.createElement("span");
|
||||
meta.className = "meta";
|
||||
meta.textContent = node.meta;
|
||||
li.appendChild(meta);
|
||||
}
|
||||
|
||||
if (hasChildren) {
|
||||
const ul = document.createElement("ul");
|
||||
ul.className = "tree";
|
||||
node.children.forEach(child => ul.appendChild(renderNode(child)));
|
||||
li.appendChild(ul);
|
||||
}
|
||||
return li;
|
||||
}
|
||||
|
||||
window.TreeView = {
|
||||
create(container, data, { onClick, onDblClick } = {}) {
|
||||
if (!container) return;
|
||||
container.innerHTML = "";
|
||||
const ul = document.createElement("ul");
|
||||
ul.className = "tree";
|
||||
data.forEach(node => ul.appendChild(renderNode(node)));
|
||||
ul.addEventListener("click", evt => {
|
||||
const li = evt.target.closest("li");
|
||||
if (!li) return;
|
||||
const sub = li.querySelector(":scope > ul");
|
||||
if (sub) {
|
||||
const caret = li.querySelector(":scope > .caret");
|
||||
const hidden = sub.style.display === "none";
|
||||
sub.style.display = hidden ? "block" : "none";
|
||||
if (caret) caret.textContent = hidden ? "▾" : "▸";
|
||||
if (hidden) {
|
||||
window.expandedPaths.add(li.dataset.id || "");
|
||||
} else {
|
||||
window.expandedPaths.delete(li.dataset.id || "");
|
||||
}
|
||||
}
|
||||
onClick?.(li.dataset.id || "");
|
||||
});
|
||||
ul.addEventListener("dblclick", evt => {
|
||||
const li = evt.target.closest("li");
|
||||
if (!li) return;
|
||||
const sub = li.querySelector(":scope > ul");
|
||||
if (!sub) {
|
||||
onDblClick?.(li.dataset.id || "");
|
||||
}
|
||||
});
|
||||
container.appendChild(ul);
|
||||
},
|
||||
};
|
||||
})();
|
||||
4
src/main/webapp/min/plantuml.min.css
vendored
4
src/main/webapp/min/plantuml.min.css
vendored
@@ -6,7 +6,9 @@ html{font-family:arial,helvetica,sans-serif}body{background-color:var(--bg-color
|
||||
.monaco-editor-container .menu-kebab{flex-direction:column;position:relative;transition:all 300ms cubic-bezier(0.175,0.885,0.32,1.275)}.monaco-editor-container .menu-kebab .kebab-circle:nth-child(4),.monaco-editor-container .menu-kebab .kebab-circle:nth-child(5){position:absolute;opacity:0;top:50%;margin-top:-6px;left:50%}.monaco-editor-container .menu-kebab .kebab-circle:nth-child(4){margin-left:-25px}.monaco-editor-container .menu-kebab .kebab-circle:nth-child(5){margin-left:13px}.monaco-editor-container .editor-menu:hover .menu-kebab,.monaco-editor-container .editor-menu:focus .menu-kebab{transform:rotate(45deg)}
|
||||
.monaco-editor-container .editor-menu:hover .menu-kebab .kebab-circle,.monaco-editor-container .editor-menu:focus .menu-kebab .kebab-circle{opacity:1}.monaco-editor-container .editor-menu .menu-item{display:none;margin:1rem 0;height:1.75rem;opacity:.5;position:relative;-webkit-animation-name:editor-menu-animateitem;-webkit-animation-duration:.4s;animation-name:editor-menu-animateitem;animation-duration:.4s}@-webkit-keyframes editor-menu-animateitem{from{top:-50%;opacity:0}to{top:0;opacity:.5}}@keyframes editor-menu-animateitem{from{top:-50%;opacity:0}
|
||||
to{top:0;opacity:.5}}.monaco-editor-container .editor-menu .menu-item:hover{opacity:1}.monaco-editor-container .editor-menu:hover .menu-item,.monaco-editor-container .editor-menu:focus .menu-item{display:block}.editor .btn-input{align-items:center;border-bottom:3px solid var(--border-color);box-sizing:border-box;display:flex;justify-content:center}.editor .btn-input input[type=text]{border:0;flex:1 1 1px;font-family:monospace;font-size:medium;padding:.2em;text-overflow:ellipsis}.editor .btn-input input[type=text]:focus{border:0;box-shadow:none;outline:0}.editor .btn-input input[type="image"]{height:1rem;margin-left:.7em;padding:0 .3em}#diagram-export.modal .label-input-pair label{min-width:8rem}#diagram-import p.error-message{color:darkred;padding-left:1rem;padding-right:1rem}#diagram-import input[type="file"]{display:block;width:100%;border:.2rem dashed var(--border-color);border-radius:.4rem;box-sizing:border-box;padding:5rem 2rem}#diagram-import input[type="file"],#diagram-import input[type="file"]::file-selector-button{background-color:var(--modal-bg-color)}#diagram-import input[type="file"]:hover,#diagram-import input[type="file"].drop-able{border-color:var(--border-color-2);background-color:var(--file-drop-color)}
|
||||
#diagram-import input[type="file"]:hover::file-selector-button,#diagram-import input[type="file"].drop-able::file-selector-button{background-color:var(--file-drop-color)}.modal{display:block;position:fixed;z-index:1;padding:5%;left:0;top:0;bottom:0;right:0;overflow:auto;background-color:#000;background-color:rgba(0,0,0,0.4)}.modal .modal-content{background-color:var(--modal-bg-color);margin:auto;padding:2rem;border:3px solid var(--border-color);max-width:30rem;box-shadow:0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);-webkit-animation-name:modal-animatetop;-webkit-animation-duration:.4s;animation-name:modal-animatetop;animation-duration:.4s;position:relative;top:50%;transform:translateY(-50%)}
|
||||
#diagram-import input[type="file"]:hover::file-selector-button,#diagram-import input[type="file"].drop-able::file-selector-button{background-color:var(--file-drop-color)}.saved-list{margin-top:1rem;border:1px solid var(--border-color,#ccc);border-radius:6px;display:flex;flex-direction:column;min-height:260px;max-height:70vh;background:#f7f9fb}.saved-list-header{display:flex;justify-content:space-between;padding:.5rem .75rem;border-bottom:1px solid var(--border-color,#ccc);font-weight:600}.saved-list-header .hint{font-weight:400;color:#666;font-size:.9rem}.saved-list-body{overflow-y:auto;padding:.5rem .75rem;max-height:70vh}.modal-storage{max-width:48rem;width:90vw}
|
||||
.saved-item{padding:.35rem .5rem;border-radius:4px;cursor:pointer;display:flex;justify-content:space-between;align-items:center}.saved-item:hover{background:rgba(0,0,0,0.05)}.saved-item .path{font-weight:600}.saved-item .meta{color:#666;font-size:.85rem;margin-left:.75rem}.tree{list-style:none;padding-left:.25rem;margin:0}.tree li{padding:.25rem .25rem .25rem .5rem;border-radius:4px;cursor:pointer;display:flex;align-items:center;gap:.35rem;user-select:none}.tree li:hover{background:rgba(0,0,0,0.05)}
|
||||
.tree .caret{width:.9rem;text-align:center;font-size:.85rem}.tree .label{font-weight:600}.tree .meta{color:#666;font-size:.85rem;margin-left:auto}.tree .icon-folder::before{content:"📂"}.tree .icon-file::before{content:"📄"}#diagram-storage-status{min-height:1.4rem;color:#444}#diagram-storage-status.error{color:#c0392b}#diagram-storage-status.success{color:#1e8449}.modal{display:block;position:fixed;z-index:1;padding:5%;left:0;top:0;bottom:0;right:0;overflow:auto;background-color:#000;background-color:rgba(0,0,0,0.4)}.modal .modal-content{background-color:var(--modal-bg-color);margin:auto;padding:2rem;border:3px solid var(--border-color);max-width:30rem;box-shadow:0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);-webkit-animation-name:modal-animatetop;-webkit-animation-duration:.4s;animation-name:modal-animatetop;animation-duration:.4s;position:relative;top:50%;transform:translateY(-50%)}
|
||||
@-webkit-keyframes modal-animatetop{from{top:-50%;opacity:0}to{top:50%;opacity:1}}@keyframes modal-animatetop{from{top:-50%;opacity:0}to{top:50%;opacity:1}}.modal .modal-header h2{margin:0}.modal .modal-main{flex:1}.modal .modal-footer{margin-top:1rem;text-align:right}.modal input,.modal select{border:1px solid var(--border-color)}.modal input:not(:focus):invalid{border-bottom-color:red}.modal input[type="file"]::file-selector-button{border:1px solid var(--border-color)}
|
||||
.modal input.ok,.modal input.cancel{min-width:5rem}.modal input.ok[disabled],.modal input.cancel[disabled]{color:var(--font-color-disabled)}.modal input.ok:not([disabled]):hover{border-bottom-color:green}.modal input.cancel:not([disabled]):hover{border-bottom-color:darkred}.modal .label-input-pair{margin:1rem 0;overflow:hidden}.modal .label-input-pair:first-child{margin-top:0}.modal .label-input-pair:last-child{margin-bottom:0}.modal .label-input-pair label{display:inline-block;min-width:15rem}.modal .label-input-pair label+input,.modal .label-input-pair label+select{box-sizing:border-box;display:inline-block;min-width:10rem}#settings #settings-monaco-editor{height:17rem;border:1px solid var(--border-color)}.diagram{height:100%;overflow:auto}.diagram[data-diagram-type="pdf"]{overflow:hidden}.diagram>div{margin:1rem 0;text-align:center}.diagram[data-diagram-type="pdf"]>div{height:20em;width:100%}.diagram img,.diagram svg,.diagram pre{border:3px solid var(--border-color);box-sizing:border-box;padding:10px}@media screen and (min-width:900px){.diagram{position:relative}.diagram>div{margin:0}.diagram:not([data-diagram-type="pdf"])>div{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);max-height:100%;max-width:100%}
|
||||
.diagram[data-diagram-type="pdf"]>div{height:100%}}.preview-menu{margin-left:5%;margin-right:5%}.diagram-link img,.btn-dock{width:2.5rem}.btn-settings{width:2.2rem;margin-left:auto;margin-right:.25rem}.menu-r{min-width:3rem}.menu-r .btn-float-r{float:right;margin-left:.25rem;text-align:right}.diagram-links{align-items:center;display:flex}.diagram-link{margin-left:.25rem;margin-right:.25rem}.diagram-links .diagram-link:first-of-type{margin-left:.5rem}.diagram-links .diagram-link:last-of-type{margin-right:0}#paginator{text-align:center;margin-bottom:1rem}.previewer-container{height:100%}@media screen and (max-width:900px){.previewer-container{height:initial}.previewer-main{flex:none}}
|
||||
73
src/main/webapp/min/plantuml.min.js
vendored
73
src/main/webapp/min/plantuml.min.js
vendored
@@ -1,9 +1,9 @@
|
||||
'use strict';var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.arrayIteratorImpl=function(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}};$jscomp.arrayIterator=function(a){return{next:$jscomp.arrayIteratorImpl(a)}};$jscomp.makeIterator=function(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];if(b)return b.call(a);if("number"==typeof a.length)return $jscomp.arrayIterator(a);throw Error(String(a)+" is not an iterable or ArrayLike");};
|
||||
$jscomp.arrayFromIterator=function(a){for(var b,c=[];!(b=a.next()).done;)c.push(b.value);return c};$jscomp.arrayFromIterable=function(a){return a instanceof Array?a:$jscomp.arrayFromIterator($jscomp.makeIterator(a))};$jscomp.owns=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.ISOLATE_POLYFILLS=!1;$jscomp.FORCE_POLYFILL_PROMISE=!1;
|
||||
$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};
|
||||
$jscomp.getGlobal=function(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);$jscomp.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");$jscomp.TRUST_ES6_POLYFILLS=!$jscomp.ISOLATE_POLYFILLS||$jscomp.IS_SYMBOL_NATIVE;$jscomp.polyfills={};
|
||||
$jscomp.propertyToPolyfillSymbol={};$jscomp.POLYFILL_PREFIX="$jscp$";var $jscomp$lookupPolyfilledValue=function(a,b,c){if(!c||null!=a){c=$jscomp.propertyToPolyfillSymbol[b];if(null==c)return a[b];c=a[c];return void 0!==c?c:a[b]}};$jscomp.polyfill=function(a,b,c,d){b&&($jscomp.ISOLATE_POLYFILLS?$jscomp.polyfillIsolated(a,b,c,d):$jscomp.polyfillUnisolated(a,b,c,d))};
|
||||
$jscomp.polyfillUnisolated=function(a,b,c,d){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];if(!(e in c))return;c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})};
|
||||
'use strict';var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.createTemplateTagFirstArg=function(a){return a.raw=a};$jscomp.createTemplateTagFirstArgWithRaw=function(a,b){a.raw=b;return a};$jscomp.arrayIteratorImpl=function(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}};$jscomp.arrayIterator=function(a){return{next:$jscomp.arrayIteratorImpl(a)}};
|
||||
$jscomp.makeIterator=function(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];if(b)return b.call(a);if("number"==typeof a.length)return $jscomp.arrayIterator(a);throw Error(String(a)+" is not an iterable or ArrayLike");};$jscomp.arrayFromIterator=function(a){for(var b,c=[];!(b=a.next()).done;)c.push(b.value);return c};$jscomp.arrayFromIterable=function(a){return a instanceof Array?a:$jscomp.arrayFromIterator($jscomp.makeIterator(a))};
|
||||
$jscomp.owns=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.ISOLATE_POLYFILLS=!1;$jscomp.FORCE_POLYFILL_PROMISE=!1;$jscomp.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION=!1;
|
||||
$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(a==Array.prototype||a==Object.prototype)return a;a[b]=c.value;return a};$jscomp.getGlobal=function(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var c=a[b];if(c&&c.Math==Math)return c}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
|
||||
$jscomp.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");$jscomp.TRUST_ES6_POLYFILLS=!$jscomp.ISOLATE_POLYFILLS||$jscomp.IS_SYMBOL_NATIVE;$jscomp.polyfills={};$jscomp.propertyToPolyfillSymbol={};$jscomp.POLYFILL_PREFIX="$jscp$";var $jscomp$lookupPolyfilledValue=function(a,b,c){if(!c||null!=a){c=$jscomp.propertyToPolyfillSymbol[b];if(null==c)return a[b];c=a[c];return void 0!==c?c:a[b]}};
|
||||
$jscomp.polyfill=function(a,b,c,d){b&&($jscomp.ISOLATE_POLYFILLS?$jscomp.polyfillIsolated(a,b,c,d):$jscomp.polyfillUnisolated(a,b,c,d))};$jscomp.polyfillUnisolated=function(a,b,c,d){c=$jscomp.global;a=a.split(".");for(d=0;d<a.length-1;d++){var e=a[d];if(!(e in c))return;c=c[e]}a=a[a.length-1];d=c[a];b=b(d);b!=d&&null!=b&&$jscomp.defineProperty(c,a,{configurable:!0,writable:!0,value:b})};
|
||||
$jscomp.polyfillIsolated=function(a,b,c,d){var e=a.split(".");a=1===e.length;d=e[0];d=!a&&d in $jscomp.polyfills?$jscomp.polyfills:$jscomp.global;for(var f=0;f<e.length-1;f++){var g=e[f];if(!(g in d))return;d=d[g]}e=e[e.length-1];c=$jscomp.IS_SYMBOL_NATIVE&&"es6"===c?d[e]:null;b=b(c);null!=b&&(a?$jscomp.defineProperty($jscomp.polyfills,e,{configurable:!0,writable:!0,value:b}):b!==c&&(void 0===$jscomp.propertyToPolyfillSymbol[e]&&(c=1E9*Math.random()>>>0,$jscomp.propertyToPolyfillSymbol[e]=$jscomp.IS_SYMBOL_NATIVE?
|
||||
$jscomp.global.Symbol(e):$jscomp.POLYFILL_PREFIX+c+"$"+e),$jscomp.defineProperty(d,$jscomp.propertyToPolyfillSymbol[e],{configurable:!0,writable:!0,value:b})))};$jscomp.assign=$jscomp.TRUST_ES6_POLYFILLS&&"function"==typeof Object.assign?Object.assign:function(a,b){for(var c=1;c<arguments.length;c++){var d=arguments[c];if(d)for(var e in d)$jscomp.owns(d,e)&&(a[e]=d[e])}return a};$jscomp.polyfill("Object.assign",function(a){return a||$jscomp.assign},"es6","es3");
|
||||
$jscomp.underscoreProtoCanBeSet=function(){var a={a:!0},b={};try{return b.__proto__=a,b.a}catch(c){}return!1};$jscomp.setPrototypeOf=$jscomp.TRUST_ES6_POLYFILLS&&"function"==typeof Object.setPrototypeOf?Object.setPrototypeOf:$jscomp.underscoreProtoCanBeSet()?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null;$jscomp.generator={};
|
||||
@@ -25,9 +25,9 @@ $jscomp.asyncExecutePromiseGenerator=function(a){function b(d){return a.next(d)}
|
||||
$jscomp.getRestArguments=function(){for(var a=Number(this),b=[],c=a;c<arguments.length;c++)b[c-a]=arguments[c];return b};
|
||||
function initApp(){var a,b;return $jscomp.asyncExecutePromiseGeneratorProgram(function(c){if(1==c.nextAddress)return b=null==(a=(new URL(window.location.href)).searchParams.get("view"))?void 0:a.toLowerCase(),c.yield(initEditor(b),2);if(3!=c.nextAddress){var d=analyseUrl(window.location.href),e,f=null==(e=document.editor)?void 0:e.getValue(),g;document.appData=Object.assign({},null==(g=window.opener)?void 0:g.document.appData);0===Object.keys(document.appData).length&&(document.appData={encodedDiagram:d.encodedDiagram,
|
||||
index:d.index,numberOfDiagramPages:f?getNumberOfDiagramPagesFromCode(f):1});initTheme();initAppCommunication();return c.yield(initPreview(b),3)}initModals(b);document.editor&&(document.editor.focus(),"SyfFKj2rKt3CoKnELR1Io4ZDoSa70000"==document.appData.encodedDiagram&&document.editor.setSelection({startLineNumber:2,endLineNumber:2,startColumn:1,endColumn:21}));document.appConfig.autoRefreshState="complete";c.jumpToEnd()})}window.onload=initApp;
|
||||
var $jscomp$destructuring$var0=function(){return{setEditorValue:function(a,b,c){c=void 0===c?{}:c;var d=void 0===c.forceMoveMarkers?void 0:c.forceMoveMarkers;(void 0===c.suppressEditorChangedMessage?0:c.suppressEditorChangedMessage)&&a===document.editor&&suppressNextMessage("editor");a.executeEdits("",[{range:a.getModel().getFullModelRange(),text:b,forceMoveMarkers:d}])},initEditor:function(a){function b(){return new Promise(function(g,m){require.config({paths:{vs:"webjars/monaco-editor/0.36.1/min/vs"}});
|
||||
require(["vs/editor/editor.main"],g)})}function c(){function g(t,p,k){function n(){document.appConfig.autoRefreshState="started";var r=getNumberOfDiagramPagesFromCode(t),u=document.appData.index;void 0===u||1===r?u=void 0:u>=r&&(u=r-1);makeRequest("POST","coder",{data:t}).then(function(v){sendMessage({sender:p,data:{encodedDiagram:v,numberOfDiagramPages:r,index:u},synchronize:!0})})}k=void 0===k?!0:k;var q=function(){return function(){var r=document.editor.getModel();m=m||new PlantUmlLanguageFeatures;
|
||||
m.validateCode(r).then(function(u){return monaco.editor.setModelMarkers(r,"plantuml",u)})}}();p&&k&&n();q()}var m,l=monaco.editor.createModel(function(){var t=document.getElementById("initCode"),p=t.value;t.remove();return p}(),"apex",monaco.Uri.parse("inmemory://plantuml")),h=0;l.onDidChangeContent(function(){clearTimeout(h);document.appConfig.autoRefreshState="waiting";h=setTimeout(function(){return g(l.getValue(),"editor")},document.appConfig.editorWatcherTimeout)});return l}function d(){return{get:function(){},
|
||||
var $jscomp$destructuring$var0=function(){return{setEditorValue:function(a,b,c){c=void 0===c?{}:c;var d=void 0===c.forceMoveMarkers?void 0:c.forceMoveMarkers;(void 0===c.suppressEditorChangedMessage?0:c.suppressEditorChangedMessage)&&a===document.editor&&suppressNextMessage("editor");a.executeEdits("",[{range:a.getModel().getFullModelRange(),text:b,forceMoveMarkers:d}])},initEditor:function(a){function b(){return new Promise(function(g,n){require.config({paths:{vs:"webjars/monaco-editor/0.36.1/min/vs"}});
|
||||
require(["vs/editor/editor.main"],g)})}function c(){function g(t,u,m){function r(){document.appConfig.autoRefreshState="started";var h=getNumberOfDiagramPagesFromCode(t),y=document.appData.index;void 0===y||1===h?y=void 0:y>=h&&(y=h-1);makeRequest("POST","coder",{data:t}).then(function(D){sendMessage({sender:u,data:{encodedDiagram:D,numberOfDiagramPages:h,index:y},synchronize:!0})})}m=void 0===m?!0:m;var v=function(){return function(){var h=document.editor.getModel();n=n||new PlantUmlLanguageFeatures;
|
||||
n.validateCode(h).then(function(y){return monaco.editor.setModelMarkers(h,"plantuml",y)})}}();u&&m&&r();v()}var n,p=monaco.editor.createModel(function(){var t=document.getElementById("initCode"),u=t.value;t.remove();return u}(),"apex",monaco.Uri.parse("inmemory://plantuml")),l=0;p.onDidChangeContent(function(){clearTimeout(l);document.appConfig.autoRefreshState="waiting";l=setTimeout(function(){return g(p.getValue(),"editor")},document.appConfig.editorWatcherTimeout)});return p}function d(){return{get:function(){},
|
||||
getBoolean:function(g){return"expandSuggestionDocs"===g},getNumber:function(){return 0},remove:function(){},store:function(){},onWillSaveState:function(){},onDidChangeStorage:function(){},onDidChangeValue:function(){}}}var e,f;return $jscomp.asyncExecutePromiseGeneratorProgram(function(g){if(1==g.nextAddress)return g.yield(b(),2);"previewer"!==a&&(e=c(),f=d(),document.editor=monaco.editor.create(document.getElementById("monaco-editor"),Object.assign({},{model:e},document.appConfig.editorCreateOptions),
|
||||
{storageService:f}),document.addEventListener("resize",function(){return document.editor.layout()}),initEditorUrlInput(),initEditorMenu());g.jumpToEnd()})}}}(),setEditorValue=$jscomp$destructuring$var0.setEditorValue,initEditor=$jscomp$destructuring$var0.initEditor;
|
||||
function initEditorMenu(){document.getElementById("menu-item-editor-code-copy").addEventListener("click",function(){var a=document.editor.getModel().getFullModelRange();document.editor.focus();document.editor.setSelection(a);a=document.editor.getValue();var b;null==(b=navigator.clipboard)||b.writeText(a).catch(function(){})})}
|
||||
@@ -36,41 +36,56 @@ function(c){var d,e;return $jscomp.asyncExecutePromiseGeneratorProgram(function(
|
||||
function(){b.focus();b.select();var c;null==(c=navigator.clipboard)||c.writeText(b.value).catch(function(){})})}}}(),setUrlValue=$jscomp$destructuring$var3.setUrlValue,initEditorUrlInput=$jscomp$destructuring$var3.initEditorUrlInput;
|
||||
function initDiagramExport(){function a(){setVisibility(document.getElementById("diagram-export"),!0,!0);var f=document.editor.getValue();f=Array.from(f.matchAll(/^\s*@start[a-zA-Z]*\s+([a-zA-Z-_\u00e4\u00f6\u00fc\u00c4\u00d6\u00dc\u00df ]+)\s*$/gm),function(g){return g[1]})[0]||"diagram";d.value=f+".puml";e.value="code";d.focus()}function b(f){var g=f.lastIndexOf(".");return 1>g?{name:f,ext:null}:g===f.length-1?{name:f.slice(0,-1),ext:null}:{name:f.substring(0,g),ext:f.substring(g+1)}}function c(f){if(!f)return f;
|
||||
f=f.toLowerCase();switch(f){case "puml":case "plantuml":case "code":return"code";case "ascii":return"txt";default:return f}}var d=document.getElementById("download-name"),e=document.getElementById("download-type");registerModalListener("diagram-export",a);d.addEventListener("change",function(f){f=b(f.target.value).ext;if(f=c(f))e.value=f});e.addEventListener("change",function(f){f=f.target.value;a:switch(f){case "epstext":f="eps";break a;case "code":f="puml"}var g=b(d.value).name;d.value=g+"."+f});
|
||||
document.getElementById("diagram-export-ok-btn").addEventListener("click",function(){var f=d.value,g=e.value,m=document.createElement("a");m.download=f;"code"===g?(f=document.editor.getValue(),m.href="data:,"+encodeURIComponent(f)):m.href=void 0!==document.appData.index?g+"/"+document.appData.index+"/"+document.appData.encodedDiagram:g+"/"+document.appData.encodedDiagram;m.click()});window.addEventListener("keydown",function(f){"s"===f.key&&(isMac?f.metaKey:f.ctrlKey)&&(f.preventDefault(),isModalOpen("diagram-export")||
|
||||
document.getElementById("diagram-export-ok-btn").addEventListener("click",function(){var f=d.value,g=e.value,n=document.createElement("a");n.download=f;"code"===g?(f=document.editor.getValue(),n.href="data:,"+encodeURIComponent(f)):n.href=void 0!==document.appData.index?g+"/"+document.appData.index+"/"+document.appData.encodedDiagram:g+"/"+document.appData.encodedDiagram;n.click()});window.addEventListener("keydown",function(f){"s"===f.key&&(isMac?f.metaKey:f.ctrlKey)&&(f.preventDefault(),isModalOpen("diagram-export")||
|
||||
a())},!1)}
|
||||
function initDiagramImport(){function a(h){h=void 0===h?!0:h;setVisibility(f,!0,!0);f.dataset.isOpenManually=h.toString();g.value="";c(g)}function b(){g.value="";c(g);f.removeAttribute("data-is-open-manually");setVisibility(f,!1)}function c(h){l.innerText="";var t;m.disabled=1>(null==(t=h.files)?void 0:t.length)}function d(h){function t(k){var n=k.name,q=k.type;k=["plain","text","plantuml","puml"];if(0<k.filter(function(u){return-1!==q.toLowerCase().indexOf(u)}).length)return!0;if(-1===n.indexOf("."))return!1;
|
||||
var r=n.substring(n.lastIndexOf(".")+1).toLowerCase();k=["txt","puml","plantuml"];return 0<k.filter(function(u){return r===u}).length}var p=function(k){var n=k.name,q=k.type;k=["png","svg"];var r=k.filter(function(v){return-1!==q.toLowerCase().indexOf(v)})[0];if(r)return r;if(-1!==n.indexOf(".")){var u=n.substring(n.lastIndexOf(".")+1).toLowerCase();return k.filter(function(v){return u===v})[0]}}(h);h=void 0===p?t(h):!1;p||h||(l.innerText="File not supported. Only PNG and SVG diagram images as well as PlantUML code text files are supported.");
|
||||
return{type:p,isDiagramCode:h,valid:p||h}}function e(h,t){function p(k){var n=new FormData;n.append("diagram",k,k.name);return makeRequest("POST","metadata",{data:n,responseType:"json",headers:{Accept:"application/json"}})}f.classList.add("wait");return(new Promise(function(k,n){if(t.type)p(h).then(function(r){setEditorValue(document.editor,r.decoded);k()},function(r){r=r.response;l.innerText=r.message||r;n()});else if(t.isDiagramCode){var q=new FileReader;q.onload=function(r){setEditorValue(document.editor,
|
||||
r.target.result)};q.readAsText(h);k()}else l.innerText="File not supported. Only PNG and SVG diagram images as well as PlantUML code text files are supported.",n()})).then(function(){return b()},function(){}).finally(function(){return f.classList.remove("wait")})}var f=document.getElementById("diagram-import"),g=document.getElementById("diagram-import-input"),m=document.getElementById("diagram-import-ok-btn"),l=document.getElementById("diagram-import-error-message");window.addEventListener("dragenter",
|
||||
function(h){h.stopPropagation();h.preventDefault();isVisible(f)||a(!1)},!1);g.addEventListener("dragenter",function(h){return h.target.classList.add("drop-able")},!1);g.addEventListener("dragover",function(h){h.stopPropagation();h.preventDefault();null!==h.dataTransfer&&(h.dataTransfer.dropEffect="copy")},!1);g.addEventListener("dragexit",function(h){return h.target.classList.remove("drop-able")},!1);g.addEventListener("drop",function(h){function t(){h.stopPropagation();h.preventDefault()}var p=h.dataTransfer.files||
|
||||
h.target.files;if(!p||1>p.length)return t();p=p[0];var k=d(p);if(!k.valid)return t();"true"!==f.dataset.isOpenManually&&(t(),e(p,k))},!1);g.addEventListener("change",function(h){return c(h.target)});m.addEventListener("click",function(){var h=g.files[0];e(h,d(h))});registerModalListener("diagram-import",a,b)}
|
||||
function initDiagramImport(){function a(l){l=void 0===l?!0:l;setVisibility(f,!0,!0);f.dataset.isOpenManually=l.toString();g.value="";c(g)}function b(){g.value="";c(g);f.removeAttribute("data-is-open-manually");setVisibility(f,!1)}function c(l){p.innerText="";var t;n.disabled=1>(null==(t=l.files)?void 0:t.length)}function d(l){function t(m){var r=m.name,v=m.type;m=["plain","text","plantuml","puml"];if(0<m.filter(function(y){return-1!==v.toLowerCase().indexOf(y)}).length)return!0;if(-1===r.indexOf("."))return!1;
|
||||
var h=r.substring(r.lastIndexOf(".")+1).toLowerCase();m=["txt","puml","plantuml"];return 0<m.filter(function(y){return h===y}).length}var u=function(m){var r=m.name,v=m.type;m=["png","svg"];var h=m.filter(function(D){return-1!==v.toLowerCase().indexOf(D)})[0];if(h)return h;if(-1!==r.indexOf(".")){var y=r.substring(r.lastIndexOf(".")+1).toLowerCase();return m.filter(function(D){return y===D})[0]}}(l);l=void 0===u?t(l):!1;u||l||(p.innerText="Fichier non pris en charge. Seuls les PNG, SVG ou fichiers texte PlantUML sont accept\u00e9s.");
|
||||
return{type:u,isDiagramCode:l,valid:u||l}}function e(l,t){function u(m){var r=new FormData;r.append("diagram",m,m.name);return makeRequest("POST","metadata",{data:r,responseType:"json",headers:{Accept:"application/json"}})}f.classList.add("wait");return(new Promise(function(m,r){if(t.type)u(l).then(function(h){setEditorValue(document.editor,h.decoded);m()},function(h){h=h.response;p.innerText=h.message||h;r()});else if(t.isDiagramCode){var v=new FileReader;v.onload=function(h){setEditorValue(document.editor,
|
||||
h.target.result)};v.readAsText(l);m()}else p.innerText="Fichier non pris en charge. Seuls les PNG, SVG ou fichiers texte PlantUML sont accept\u00e9s.",r()})).then(function(){return b()},function(){}).finally(function(){return f.classList.remove("wait")})}var f=document.getElementById("diagram-import"),g=document.getElementById("diagram-import-input"),n=document.getElementById("diagram-import-ok-btn"),p=document.getElementById("diagram-import-error-message");window.addEventListener("dragenter",function(l){l.stopPropagation();
|
||||
l.preventDefault();isVisible(f)||a(!1)},!1);g.addEventListener("dragenter",function(l){return l.target.classList.add("drop-able")},!1);g.addEventListener("dragover",function(l){l.stopPropagation();l.preventDefault();null!==l.dataTransfer&&(l.dataTransfer.dropEffect="copy")},!1);g.addEventListener("dragexit",function(l){return l.target.classList.remove("drop-able")},!1);g.addEventListener("drop",function(l){function t(){l.stopPropagation();l.preventDefault()}var u=l.dataTransfer.files||l.target.files;
|
||||
if(!u||1>u.length)return t();u=u[0];var m=d(u);if(!m.valid)return t();"true"!==f.dataset.isOpenManually&&(t(),e(u,m))},!1);g.addEventListener("change",function(l){return c(l.target)});n.addEventListener("click",function(){var l=g.files[0];e(l,d(l))});registerModalListener("diagram-import",a,b)}
|
||||
var $jscomp$destructuring$var16=function(){var a={};return{registerModalListener:function(b,c,d){a[b]={fnOpen:c,fnClose:d}},openModal:function(b){var c=$jscomp.getRestArguments.apply(1,arguments),d,e=null==(d=a[b])?void 0:d.fnOpen;e?e.apply(null,$jscomp.arrayFromIterable(c)):setVisibility(document.getElementById(b),!0,!0)},closeModal:function(b){var c=$jscomp.getRestArguments.apply(1,arguments),d,e=null==(d=a[b])?void 0:d.fnClose;e?e.apply(null,$jscomp.arrayFromIterable(c)):setVisibility(document.getElementById(b),
|
||||
!1)}}}(),registerModalListener=$jscomp$destructuring$var16.registerModalListener,openModal=$jscomp$destructuring$var16.openModal,closeModal=$jscomp$destructuring$var16.closeModal;
|
||||
function initModals(a){function b(c){"Escape"===c.key||"Esc"===c.key?(c.preventDefault(),closeModal(c.target.closest(".modal").id)):"Enter"===c.key&&(c.preventDefault(),(c=c.target.closest(".modal").querySelector('input.ok[type\x3d"button"]'))&&!c.disabled&&c.click())}document.querySelectorAll(".modal").forEach(function(c){c.addEventListener("keydown",b,!1)});initSettings();"previewer"!==a&&(initDiagramExport(),initDiagramImport())}
|
||||
function initModals(a){function b(c){"Escape"===c.key||"Esc"===c.key?(c.preventDefault(),closeModal(c.target.closest(".modal").id)):"Enter"===c.key&&(c.preventDefault(),(c=c.target.closest(".modal").querySelector('input.ok[type\x3d"button"]'))&&!c.disabled&&c.click())}document.querySelectorAll(".modal").forEach(function(c){c.addEventListener("keydown",b,!1)});initSettings();"previewer"!==a&&(initDiagramExport(),initDiagramImport(),initDiagramStorage())}
|
||||
function isModalOpen(a){return isVisible(document.getElementById(a))}function closeAllModals(){document.querySelectorAll(".modal").forEach(function(a){return closeModal(a.id)})}
|
||||
function initSettings(){function a(){setVisibility(document.getElementById("settings"),!0,!0);b.value=document.appConfig.theme;c.value=document.appConfig.diagramPreviewType;d.value=document.appConfig.editorWatcherTimeout;setEditorValue(document.settingsEditor,JSON.stringify(document.appConfig.editorCreateOptions,null," "))}var b=document.getElementById("theme"),c=document.getElementById("diagramPreviewType"),d=document.getElementById("editorWatcherTimeout");document.settingsEditor=monaco.editor.create(document.getElementById("settings-monaco-editor"),
|
||||
Object.assign({},{language:"json"},document.appConfig.editorCreateOptions));b.addEventListener("change",function(e){e=e.target.value;var f=document.settingsEditor.getValue();setEditorValue(document.settingsEditor,f.replace(new RegExp('("theme"\\s*:\\s*)"'+("dark"===e?"vs":"vs-dark")+'"',"gm"),'$1"'+("dark"===e?"vs-dark":"vs")+'"'))});document.getElementById("settings-ok-btn").addEventListener("click",function(){var e=Object.assign({},document.appConfig);e.theme=b.value;e.editorWatcherTimeout=d.value;
|
||||
e.diagramPreviewType=c.value;e.editorCreateOptions=JSON.parse(document.settingsEditor.getValue());updateConfig(e);closeModal("settings")});window.addEventListener("keydown",function(e){","===e.key&&(isMac?e.metaKey:e.ctrlKey)&&(e.preventDefault(),isModalOpen("settings")||a())},!1);registerModalListener("settings",a)}
|
||||
function initializeDiagram(){return $jscomp.asyncExecutePromiseGeneratorProgram(function(a){if("png"!==document.appConfig.diagramPreviewType)return a.return(setDiagram(document.appConfig.diagramPreviewType,document.appData.encodedDiagram,document.appData.index));a.jumpToEnd()})}
|
||||
function setDiagram(a,b,c){function d(k,n,q){return $jscomp.asyncExecutePromiseGeneratorProgram(function(r){return r.return(makeRequest("GET",buildUrl(k,n,q)))})}var e,f,g,m,l,h,t,p;return $jscomp.asyncExecutePromiseGeneratorProgram(function(k){switch(k.nextAddress){case 1:e=document.getElementById("diagram");f=document.getElementById("diagram-png");g=document.getElementById("diagram-txt");m=document.getElementById("diagram-pdf");k.setCatchFinallyBlocks(2);if("png"===a)return f.src=buildUrl("png",
|
||||
b,c),k.yield(d("map",b,c),11);if("svg"===a)return k.yield(d("svg",b,c),10);if("txt"!==a){if("pdf"===a)m.data=buildUrl("pdf",b,c);else return l="unknown diagram type: "+a,(console.error||console.log)(l),k.return(Promise.reject(l));k.jumpTo(5);break}h=g;return k.yield(d("txt",b,c),9);case 9:h.innerHTML=k.yieldResult;k.jumpTo(5);break;case 10:t=k.yieldResult;var n=document.getElementById("diagram-svg"),q=document.createElement("div");q.innerHTML=t;q=q.querySelector("svg");q.id="diagram-svg";q.classList=
|
||||
n.classList;q.style.cssText=n.style.cssText;n.parentNode.replaceChild(q,n);k.jumpTo(5);break;case 11:if(p=k.yieldResult,n=document.getElementById("plantuml_map"),q=document.getElementById("map-diagram-link"),p){var r=document.createElement("div");r.innerHTML=p;n.parentNode.replaceChild(r.firstChild,n);setVisibility(q,!0)}else removeChildren(n),setVisibility(q,!1);case 5:n=document.getElementById("plantuml_map");q=document.getElementById("diagram-svg");e.setAttribute("data-diagram-type",a);setVisibility(f,
|
||||
"png"===a);setVisibility(n,"png"===a);setVisibility(q,"svg"===a);setVisibility(g,"txt"===a);setVisibility(m,"pdf"===a);k.leaveTryBlock(0);break;case 2:k.enterCatchBlock(),k.jumpToEnd()}})}function getNumberOfDiagramPagesFromCode(a){var b;return(null==(b=a.match(/^\s*newpage\s?.*$/gm))?void 0:b.length)+1||1}
|
||||
function setDiagram(a,b,c){function d(m,r,v){return $jscomp.asyncExecutePromiseGeneratorProgram(function(h){return h.return(makeRequest("GET",buildUrl(m,r,v)))})}var e,f,g,n,p,l,t,u;return $jscomp.asyncExecutePromiseGeneratorProgram(function(m){switch(m.nextAddress){case 1:e=document.getElementById("diagram");f=document.getElementById("diagram-png");g=document.getElementById("diagram-txt");n=document.getElementById("diagram-pdf");m.setCatchFinallyBlocks(2);if("png"===a)return f.src=buildUrl("png",
|
||||
b,c),m.yield(d("map",b,c),11);if("svg"===a)return m.yield(d("svg",b,c),10);if("txt"!==a){if("pdf"===a)n.data=buildUrl("pdf",b,c);else return p="unknown diagram type: "+a,(console.error||console.log)(p),m.return(Promise.reject(p));m.jumpTo(5);break}l=g;return m.yield(d("txt",b,c),9);case 9:l.innerHTML=m.yieldResult;m.jumpTo(5);break;case 10:t=m.yieldResult;var r=document.getElementById("diagram-svg"),v=document.createElement("div");v.innerHTML=t;v=v.querySelector("svg");v.id="diagram-svg";v.classList=
|
||||
r.classList;v.style.cssText=r.style.cssText;r.parentNode.replaceChild(v,r);m.jumpTo(5);break;case 11:if(u=m.yieldResult,r=document.getElementById("plantuml_map"),v=document.getElementById("map-diagram-link"),u){var h=document.createElement("div");h.innerHTML=u;r.parentNode.replaceChild(h.firstChild,r);setVisibility(v,!0)}else removeChildren(r),setVisibility(v,!1);case 5:r=document.getElementById("plantuml_map");v=document.getElementById("diagram-svg");e.setAttribute("data-diagram-type",a);setVisibility(f,
|
||||
"png"===a);setVisibility(r,"png"===a);setVisibility(v,"svg"===a);setVisibility(g,"txt"===a);setVisibility(n,"pdf"===a);m.leaveTryBlock(0);break;case 2:m.enterCatchBlock(),m.jumpToEnd()}})}function getNumberOfDiagramPagesFromCode(a){var b;return(null==(b=a.match(/^\s*newpage\s?.*$/gm))?void 0:b.length)+1||1}
|
||||
function updatePaginatorSelection(){var a=document.getElementById("paginator"),b=document.appData.index;if(void 0===b||a.childNodes.length<=b)for(a=$jscomp.makeIterator(a.childNodes),b=a.next();!b.done;b=a.next())b.value.checked=!1;else a.childNodes[b].checked=!0}
|
||||
var updatePaginator=function(){function a(b,c){for(;b.childElementCount>c;)b.removeChild(b.lastChild);for(;b.childElementCount<c;){var d=document.createElement("input");d.name="paginator";d.type="radio";d.value=b.childElementCount;d.addEventListener("click",function(e){sendMessage({sender:"paginator",data:{index:e.target.value},synchronize:!0})});b.appendChild(d)}}return function(){var b=document.getElementById("paginator"),c=document.appData.numberOfDiagramPages;1<c?(a(b,c),setVisibility(b,!0)):
|
||||
setVisibility(b,!1)}}();function initializePaginator(){1<document.appData.numberOfDiagramPages&&(updatePaginator(),updatePaginatorSelection())}
|
||||
function initPreview(a){function b(){setVisibility(e,!1);setVisibility(f,window.opener);g&&(g.style.width="100%");m&&setVisibility(m,!1)}function c(){setVisibility(e,!0);setVisibility(f,!1);g&&g.style.removeProperty("width");m&&setVisibility(m,!0)}function d(){var l=new URL(window.location.href);l.searchParams.set("view","previewer");if(l=window.open(l,"PlantUML Diagram Previewer","popup"))l.onbeforeunload=c,b()}var e,f,g,m;return $jscomp.asyncExecutePromiseGeneratorProgram(function(l){if(1==l.nextAddress)return e=
|
||||
document.getElementById("btn-undock"),f=document.getElementById("btn-dock"),g=document.getElementById("editor-main-container"),m=document.getElementById("previewer-main-container"),e.addEventListener("click",d),l.yield(initializeDiagram(),2);initializePaginator();["previewer","editor"].includes(a)&&b();l.jumpToEnd()})}
|
||||
function initPreview(a){function b(){setVisibility(e,!1);setVisibility(f,window.opener);g&&(g.style.width="100%");n&&setVisibility(n,!1)}function c(){setVisibility(e,!0);setVisibility(f,!1);g&&g.style.removeProperty("width");n&&setVisibility(n,!0)}function d(){var p=new URL(window.location.href);p.searchParams.set("view","previewer");if(p=window.open(p,"PlantUML Diagram Previewer","popup"))p.onbeforeunload=c,b()}var e,f,g,n;return $jscomp.asyncExecutePromiseGeneratorProgram(function(p){if(1==p.nextAddress)return e=
|
||||
document.getElementById("btn-undock"),f=document.getElementById("btn-dock"),g=document.getElementById("editor-main-container"),n=document.getElementById("previewer-main-container"),e.addEventListener("click",d),p.yield(initializeDiagram(),2);initializePaginator();["previewer","editor"].includes(a)&&b();p.jumpToEnd()})}
|
||||
var $jscomp$destructuring$var17=function(){var a=function(){var c=[];return{suppressNextMessage:function(d,e){c.push({sender:d,condition:e})},isMessageSuppressed:function(d){for(var e=0;e<c.length;e++){var f=c[e];if(!f.sender||f.sender===d.sender)if(!f.condition||f.condition(d))return c.splice(e,1),!0}return!1}}}(),b=a.isMessageSuppressed;return{sendMessage:function(c){b(c)||(new BroadcastChannel("plantuml-server")).postMessage(c)},suppressNextMessage:a.suppressNextMessage,initAppCommunication:function(){(new BroadcastChannel("plantuml-server")).onmessage=
|
||||
function(c){function d(m){var l,h,t,p,k,n;return $jscomp.asyncExecutePromiseGeneratorProgram(function(q){if(1==q.nextAddress)return document.appConfig.autoRefreshState="syncing",l=document.appData.encodedDiagram,h=document.appData.index,"url"!==m&&document.getElementById("url")&&setUrlValue(void 0,{encodedDiagram:l,index:h},{suppressEditorChangedMessage:!0}),q.yield(setDiagram(document.appConfig.diagramPreviewType,l,h),2);t=$jscomp.makeIterator(document.getElementsByClassName("diagram-link"));for(p=
|
||||
t.next();!p.done;p=t.next())k=p.value,k.href=buildUrl(k.dataset.imgType,l,h);n=replaceUrl(window.location.href,l,h).url;history.replaceState(history.stat,document.title,n);document.appConfig.autoRefreshState="complete";q.jumpToEnd()})}var e,f,g;return $jscomp.asyncExecutePromiseGeneratorProgram(function(m){if(1==m.nextAddress){e=c.data.data;f=c.data.force||!1;if(e&&0!==Object.keys(e).length){var l={};"encodedDiagram"in e&&e.encodedDiagram!==document.appData.encodedDiagram&&(document.appData.encodedDiagram=
|
||||
e.encodedDiagram,l.diagram=!0);"index"in e&&e.index!==document.appData.index&&(document.appData.index=e.index,l.index=!0);"numberOfDiagramPages"in e&&e.numberOfDiagramPages!==document.appData.numberOfDiagramPages&&(document.appData.numberOfDiagramPages=e.numberOfDiagramPages,l.numberOfDiagramPages=!0);"appConfig"in e&&e.appConfig!==document.appConfig&&(document.appConfig=e.appConfig,l.appConfig=!0)}else l={};g=l;return!0!==c.data.synchronize?m.jumpTo(2):f||g.diagram||g.index||g.appConfig?m.yield(d(c.data.sender),
|
||||
3):m.jumpTo(3)}2!=m.nextAddress&&((f||g.numberOfDiagramPages)&&updatePaginator(),(f||g.numberOfDiagramPages||g.index)&&updatePaginatorSelection(),g.appConfig&&applyConfig());!0===c.data.reload&&window.location.reload();m.jumpToEnd()})}}}}(),sendMessage=$jscomp$destructuring$var17.sendMessage,suppressNextMessage=$jscomp$destructuring$var17.suppressNextMessage,initAppCommunication=$jscomp$destructuring$var17.initAppCommunication;
|
||||
function(c){function d(n){var p,l,t,u,m,r;return $jscomp.asyncExecutePromiseGeneratorProgram(function(v){if(1==v.nextAddress)return document.appConfig.autoRefreshState="syncing",p=document.appData.encodedDiagram,l=document.appData.index,"url"!==n&&document.getElementById("url")&&setUrlValue(void 0,{encodedDiagram:p,index:l},{suppressEditorChangedMessage:!0}),v.yield(setDiagram(document.appConfig.diagramPreviewType,p,l),2);t=$jscomp.makeIterator(document.getElementsByClassName("diagram-link"));for(u=
|
||||
t.next();!u.done;u=t.next())m=u.value,m.href=buildUrl(m.dataset.imgType,p,l);r=replaceUrl(window.location.href,p,l).url;history.replaceState(history.stat,document.title,r);document.appConfig.autoRefreshState="complete";v.jumpToEnd()})}var e,f,g;return $jscomp.asyncExecutePromiseGeneratorProgram(function(n){if(1==n.nextAddress){e=c.data.data;f=c.data.force||!1;if(e&&0!==Object.keys(e).length){var p={};"encodedDiagram"in e&&e.encodedDiagram!==document.appData.encodedDiagram&&(document.appData.encodedDiagram=
|
||||
e.encodedDiagram,p.diagram=!0);"index"in e&&e.index!==document.appData.index&&(document.appData.index=e.index,p.index=!0);"numberOfDiagramPages"in e&&e.numberOfDiagramPages!==document.appData.numberOfDiagramPages&&(document.appData.numberOfDiagramPages=e.numberOfDiagramPages,p.numberOfDiagramPages=!0);"appConfig"in e&&e.appConfig!==document.appConfig&&(document.appConfig=e.appConfig,p.appConfig=!0)}else p={};g=p;return!0!==c.data.synchronize?n.jumpTo(2):f||g.diagram||g.index||g.appConfig?n.yield(d(c.data.sender),
|
||||
3):n.jumpTo(3)}2!=n.nextAddress&&((f||g.numberOfDiagramPages)&&updatePaginator(),(f||g.numberOfDiagramPages||g.index)&&updatePaginatorSelection(),g.appConfig&&applyConfig());!0===c.data.reload&&window.location.reload();n.jumpToEnd()})}}}}(),sendMessage=$jscomp$destructuring$var17.sendMessage,suppressNextMessage=$jscomp$destructuring$var17.suppressNextMessage,initAppCommunication=$jscomp$destructuring$var17.initAppCommunication;
|
||||
function makeRequest(a,b,c){c=void 0===c?{}:c;return PlantUmlLanguageFeatures.makeRequest(a,b,{data:void 0===c.data?null:c.data,headers:void 0===c.headers?{"Content-Type":"text/plain"}:c.headers,responseType:void 0===c.responseType?"text":c.responseType,baseUrl:void 0===c.baseUrl?"":c.baseUrl})}
|
||||
var $jscomp$destructuring$var21=function(){var a={changeEventsEnabled:!0,autoRefreshState:"disabled",theme:void 0,diagramPreviewType:"png",editorWatcherTimeout:500,editorCreateOptions:{automaticLayout:!0,fixedOverflowWidgets:!0,minimap:{enabled:!1},scrollbar:{alwaysConsumeMouseWheel:!1},scrollBeyondLastLine:!1,tabSize:2,theme:"vs"}},b;document.appConfig=Object.assign({},null==(b=window.opener)?void 0:b.document.appConfig);0===Object.keys(document.appConfig).length&&(document.appConfig=JSON.parse(localStorage.getItem("document.appConfig"))||
|
||||
a);return{applyConfig:function(){setTheme(document.appConfig.theme);var c;null==(c=document.editor)||c.updateOptions(document.appConfig.editorCreateOptions);var d;null==(d=document.settingsEditor)||d.updateOptions(document.appConfig.editorCreateOptions)},updateConfig:function(c){localStorage.setItem("document.appConfig",JSON.stringify(c));sendMessage({sender:"config",data:{appConfig:c},synchronize:!0})}}}(),applyConfig=$jscomp$destructuring$var21.applyConfig,updateConfig=$jscomp$destructuring$var21.updateConfig;
|
||||
function removeChildren(a){a.replaceChildren?a.replaceChildren():a.innerHTML=""}function isVisible(a){return null!==a.offsetParent}function setVisibility(a,b,c){c=void 0===c?!1:c;b?(a.style.removeProperty("display"),c&&a.focus()):a.style.display="none"}var isMac=function(){var a,b,c;return((null==(a=navigator)?void 0:null==(b=a.userAgentData)?void 0:b.platform)||(null==(c=navigator)?void 0:c.platform)||"unknown").match("Mac")}();
|
||||
function setTheme(a){document.documentElement.setAttribute("data-theme",a)}
|
||||
(function(){function a(){console&&console.log&&console.log.apply(console,["[diagram-storage]"].concat($jscomp.arrayFromIterable(arguments)))}function b(){var k=window.localStorage.getItem("plantuml.save.token")||"";k=window.prompt("D\u00e9finir le jeton de sauvegarde (laisser vide pour effacer) :",k||"")||"";k.trim()?(window.localStorage.setItem("plantuml.save.token",k.trim()),d("Jeton enregistr\u00e9 localement.","success")):(window.localStorage.removeItem("plantuml.save.token"),d("Jeton supprim\u00e9.",
|
||||
"success"))}function c(k){k=void 0===k?{}:k;var w=window.localStorage.getItem("plantuml.save.token")||"";w&&(k["X-PlantUML-Token"]=w);return k}function d(k,w){w=void 0===w?"":w;h.status&&(h.status.textContent=k||"",h.status.className="status-message "+w,k&&a("status:",k,w))}function e(k){if(!k||!k.trim())return d("Merci de saisir un nom ou un chemin.","error"),null;k=k.trim().replace(/^\/+|\/+$/g,"").replace(/\/{2,}/g,"/");return v.test(k)?k:(d("Chemin invalide. Utilisez lettres, chiffres, '-' ou '_' avec dossiers optionnels (ex : dossier/nom).",
|
||||
"error"),null)}function f(k){var w=function(){return k.json().catch(function(){return{}})};return k.ok?w():w().then(function(q){q=Error(q.message||k.statusText);q.status=k.status;throw q;})}function g(k){var w={name:"",children:new Map,item:null};k.forEach(function(q){var C=q.id.split("/"),x=w;C.forEach(function(z,A){A=A===C.length-1;x.children.has(z)||x.children.set(z,{name:z,children:new Map,item:null});x=x.children.get(z);A&&(x.item=q)})});return w}function n(k,w,q){var C=document.createElement("ul");
|
||||
C.className="tree";Array.from(k.children.values()).sort(function(x,z){var A=0<x.children.size||!x.item;return A!==(0<z.children.size||!z.item)?A?-1:1:x.name.localeCompare(z.name)}).forEach(function(x){var z=document.createElement("li");z.style.paddingLeft=1.2*q+"rem";var A=w?w+"/"+x.name:x.name,G=document.createElement("span");G.className="caret";var H=0<x.children.size||!x.item,B=0===q||y.has(A);G.textContent=H?B?"\u25be":"\u25b8":"";z.appendChild(G);B=document.createElement("span");B.className=
|
||||
"label "+(H?"icon-folder":"icon-file");B.textContent=x.name;z.appendChild(B);if(x.item){B=document.createElement("span");B.className="meta";var I=x.item.updatedAt?(new Date(x.item.updatedAt)).toLocaleString():"";B.textContent=[I,x.item.name].filter(Boolean).join(" \u2022 ");z.appendChild(B)}z.addEventListener("click",function(E){E.stopPropagation();h.path.value=A;var F;h.name.value=(null==(F=x.item)?void 0:F.name)||"";H&&(E=z.querySelector("ul"))&&(F="none"===E.style.display,E.style.display=F?"block":
|
||||
"none",G.textContent=F?"\u25be":"\u25b8",F?y.add(A):y.delete(A))});z.addEventListener("dblclick",function(E){E.stopPropagation();x.item&&u(A)});H&&(B=n(x,A,q+1),B.style.display=y.has(A)||0===q?"block":"none",z.appendChild(B));C.appendChild(z)});return C}function p(k){if(h.list)if(h.list.innerHTML="",k&&0!==k.length){y.clear();y.add("");var w;if(null==(w=h.path)?0:w.value){var q="";h.path.value.split("/").forEach(function(C,x){q=0===x?C:q+"/"+C;y.add(q)})}k=g(k);k=n(k,"",0);h.list.appendChild(k)}else k=
|
||||
document.createElement("div"),k.className="saved-item",k.textContent="Aucun diagramme sauvegarde.",h.list.appendChild(k)}function l(k){k=void 0===k?!1:k;var w=c({});a("refresh list");return fetch("api/diagrams",{method:"GET",headers:w}).then(f).then(function(q){p(q);k&&d("Liste rafraichie.","success")}).catch(function(q){d("Impossible de charger la liste : "+q.message,"error");console.error("[diagram-storage] list error",q)})}function t(){a("save click");if(document.editor&&"function"===typeof document.editor.getValue){var k=
|
||||
e(h.path.value);if(k){k={id:k,name:h.name.value||null,uml:document.editor.getValue()};var w=c({"Content-Type":"application/json"});fetch("api/diagrams",{method:"POST",headers:w,body:JSON.stringify(k)}).then(f).then(function(q){d('Diagramme "'+q.id+'" sauvegarde.',"success");l()}).catch(function(q){401===q.status?b():(d("Echec de la sauvegarde : "+q.message,"error"),console.error("[diagram-storage] save error",q))})}}else d("Editeur non pret.","error")}function u(k){a("load click",k||h.path.value);
|
||||
if(k=e(k||h.path.value)){var w=c({});fetch("api/diagrams/"+encodeURIComponent(k),{method:"GET",headers:w}).then(f).then(function(q){"function"===typeof setEditorValue&&document.editor&&setEditorValue(document.editor,q.uml,{suppressEditorChangedMessage:!1});h.path.value=q.id;h.name.value=q.name||"";d('Diagramme "'+q.id+'" charge.',"success")}).catch(function(q){401===q.status?b():(d("Echec du chargement : "+q.message,"error"),console.error("[diagram-storage] load error",q))})}}function m(){[{id:"menu-item-editor-save",
|
||||
label:"menu save click"},{id:"menu-item-editor-load",label:"menu load click"},{id:"header-save-btn",label:"header save click"},{id:"header-load-btn",label:"header load click"}].forEach(function(k){var w=document.getElementById(k.id);w&&w.addEventListener("click",function(){a(k.label);openModal("diagram-storage")})})}function r(){D?a("already initialized"):(a("init UI"),h={modal:document.getElementById("diagram-storage"),path:document.getElementById("diagram-storage-path"),name:document.getElementById("diagram-storage-name"),
|
||||
save:document.getElementById("diagram-storage-save"),load:document.getElementById("diagram-storage-load"),del:document.getElementById("diagram-storage-delete"),refresh:document.getElementById("diagram-storage-refresh"),token:document.getElementById("diagram-storage-token"),status:document.getElementById("diagram-storage-status"),list:document.getElementById("diagram-storage-list")},h.save&&h.save.addEventListener("click",t),h.load&&h.load.addEventListener("click",function(){return u()}),h.del&&h.del.addEventListener("click",
|
||||
function(){}),h.refresh&&h.refresh.addEventListener("click",function(){return l(!0)}),h.token&&h.token.addEventListener("click",b),registerModalListener("diagram-storage",function(){h.modal&&setVisibility(h.modal,!0,!0);d("");h.path&&!h.path.value&&(h.path.value="mon-diagramme");l();var k;null==(k=h.path)||k.focus();a("modal opened")},function(){h.modal&&setVisibility(h.modal,!1)}),m(),D=!0)}var v=/^([A-Za-z0-9_-]+\/)*[A-Za-z0-9_-]{1,64}$/,h={},y=new Set,D=!1;window.initDiagramStorage=r;window.addEventListener("load",
|
||||
r)})();function toggleFolder(a,b,c){if(a=a.querySelector("ul")){var d="none"===a.style.display;a.style.display=d?"block":"none";c.textContent=d?"\u25be":"\u25b8";d?expandedPaths.add(b):expandedPaths.delete(b)}}
|
||||
(function(){function a(b){var c=document.createElement("li");c.dataset.id=b.id||"";var d=document.createElement("span");d.className="caret";var e=b.children&&0<b.children.length;d.textContent=e?"\u25be":"";c.appendChild(d);d=document.createElement("span");d.className="label";d.textContent=b.label||b.id||"";c.appendChild(d);b.meta&&(d=document.createElement("span"),d.className="meta",d.textContent=b.meta,c.appendChild(d));if(e){var f=document.createElement("ul");f.className="tree";b.children.forEach(function(g){return f.appendChild(a(g))});
|
||||
c.appendChild(f)}return c}window.expandedPaths=window.expandedPaths||new Set;window.TreeView={create:function(b,c,d){d=void 0===d?{}:d;var e=d.onClick,f=d.onDblClick;if(b){b.innerHTML="";var g=document.createElement("ul");g.className="tree";c.forEach(function(n){return g.appendChild(a(n))});g.addEventListener("click",function(n){if(n=n.target.closest("li")){var p=n.querySelector(":scope \x3e ul");if(p){var l=n.querySelector(":scope \x3e .caret"),t="none"===p.style.display;p.style.display=t?"block":
|
||||
"none";l&&(l.textContent=t?"\u25be":"\u25b8");t?window.expandedPaths.add(n.dataset.id||""):window.expandedPaths.delete(n.dataset.id||"")}null==e||e(n.dataset.id||"")}});g.addEventListener("dblclick",function(n){(n=n.target.closest("li"))&&(n.querySelector(":scope \x3e ul")||null==f||f(n.dataset.id||""))});b.appendChild(g)}}}})();function removeChildren(a){a.replaceChildren?a.replaceChildren():a.innerHTML=""}function isVisible(a){return null!==a.offsetParent}
|
||||
function setVisibility(a,b,c){c=void 0===c?!1:c;b?(a.style.removeProperty("display"),c&&a.focus()):a.style.display="none"}var isMac=function(){var a,b,c;return((null==(a=navigator)?void 0:null==(b=a.userAgentData)?void 0:b.platform)||(null==(c=navigator)?void 0:c.platform)||"unknown").match("Mac")}();function setTheme(a){document.documentElement.setAttribute("data-theme",a)}
|
||||
function initTheme(){function a(d){"dark"===d&&"vs"===document.appConfig.editorCreateOptions.theme&&(document.appConfig.editorCreateOptions.theme="vs-dark");"light"===d&&"vs-dark"===document.appConfig.editorCreateOptions.theme&&(document.appConfig.editorCreateOptions.theme="vs")}var b=document.appConfig,c;(c=document.appConfig.theme)||(c=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":window.matchMedia("(prefers-color-scheme: light)").matches?"light":void 0);b.theme=c||"light";setTheme(document.appConfig.theme);
|
||||
a(document.appConfig.theme);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",function(d){d=d.matches?"dark":"light";document.appConfig.theme=d;a(d);updateConfig(document.appConfig)})}function resolvePath(a){return PlantUmlLanguageFeatures.absolutePath(a)}
|
||||
function prepareUrl(a){a instanceof URL||(a=new URL(resolvePath(a)));var b=(new URL((document.querySelector("base")||{}).href||window.location.origin)).pathname;"/"===b.slice(-1)&&(b=b.slice(0,-1));b=a.pathname.startsWith(b)?a.pathname.slice(b.length):a.pathname;var c=/\/\w+(?:\/(\d+))?(?:\/([^/]+))?\/?$/gm.exec(b);return[a,b,{idx:c[1],encoded:c[2]}]}
|
||||
|
||||
63
src/main/webapp/vendor/bootstrap-treeview/bootstrap-treeview.css
vendored
Normal file
63
src/main/webapp/vendor/bootstrap-treeview/bootstrap-treeview.css
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
.btv {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0.35rem 0.25rem;
|
||||
font-size: 0.95rem;
|
||||
background: #f7f9fc;
|
||||
border: 1px solid #d8e2ed;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.btv ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
.btv li {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btv .row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.25rem 0.5rem 0.25rem 0.25rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: #1f2a3c;
|
||||
}
|
||||
|
||||
.btv .row:hover {
|
||||
background: #e8f0fb;
|
||||
}
|
||||
|
||||
.btv .caret {
|
||||
width: 1rem;
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
color: #5a6c80;
|
||||
}
|
||||
|
||||
.btv .label {
|
||||
font-weight: 600;
|
||||
color: #1f2a3c;
|
||||
}
|
||||
|
||||
.btv .meta {
|
||||
color: #6b7280;
|
||||
font-size: 0.85rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.btv .icon-folder::before {
|
||||
content: "📂";
|
||||
}
|
||||
|
||||
.btv .icon-file::before {
|
||||
content: "📄";
|
||||
}
|
||||
85
src/main/webapp/vendor/bootstrap-treeview/bootstrap-treeview.js
vendored
Normal file
85
src/main/webapp/vendor/bootstrap-treeview/bootstrap-treeview.js
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
(function() {
|
||||
function isFolder(node) {
|
||||
return Array.isArray(node.nodes) && node.nodes.length > 0;
|
||||
}
|
||||
|
||||
function renderNode(node, expandedPaths, depth) {
|
||||
const li = document.createElement("li");
|
||||
li.dataset.id = node.id || "";
|
||||
const row = document.createElement("div");
|
||||
row.className = "row";
|
||||
row.style.paddingLeft = `${(depth + 1) * 1.2}rem`;
|
||||
|
||||
const caret = document.createElement("span");
|
||||
caret.className = "caret";
|
||||
const folder = isFolder(node);
|
||||
const expanded = expandedPaths.has(node.id) || depth === 0;
|
||||
caret.textContent = folder ? (expanded ? "▾" : "▸") : "";
|
||||
row.appendChild(caret);
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.className = "label " + (folder ? "icon-folder" : "icon-file");
|
||||
label.textContent = node.text || node.id || "";
|
||||
row.appendChild(label);
|
||||
|
||||
if (node.meta) {
|
||||
const meta = document.createElement("span");
|
||||
meta.className = "meta";
|
||||
meta.textContent = node.meta;
|
||||
row.appendChild(meta);
|
||||
}
|
||||
|
||||
li.appendChild(row);
|
||||
|
||||
if (folder) {
|
||||
const ul = document.createElement("ul");
|
||||
ul.className = "btv";
|
||||
ul.style.display = expanded ? "block" : "none";
|
||||
node.nodes.forEach(child => {
|
||||
ul.appendChild(renderNode(child, expandedPaths, depth + 1));
|
||||
});
|
||||
li.appendChild(ul);
|
||||
}
|
||||
return li;
|
||||
}
|
||||
|
||||
function create(container, data, { expandedPaths = new Set(), onClick, onDblClick } = {}) {
|
||||
if (!container) return;
|
||||
container.innerHTML = "";
|
||||
const ul = document.createElement("ul");
|
||||
ul.className = "btv";
|
||||
data.forEach(node => ul.appendChild(renderNode(node, expandedPaths, 0)));
|
||||
|
||||
ul.addEventListener("click", evt => {
|
||||
const row = evt.target.closest(".row");
|
||||
const li = evt.target.closest("li");
|
||||
if (!li) return;
|
||||
const sub = li.querySelector(":scope > ul");
|
||||
if (sub) {
|
||||
const caret = row?.querySelector(".caret") || li.querySelector(":scope > .row .caret");
|
||||
const hidden = sub.style.display === "none";
|
||||
sub.style.display = hidden ? "block" : "none";
|
||||
if (caret) caret.textContent = hidden ? "▾" : "▸";
|
||||
if (hidden) {
|
||||
expandedPaths.add(li.dataset.id);
|
||||
} else {
|
||||
expandedPaths.delete(li.dataset.id);
|
||||
}
|
||||
}
|
||||
onClick?.(li.dataset.id || "");
|
||||
});
|
||||
|
||||
ul.addEventListener("dblclick", evt => {
|
||||
const li = evt.target.closest("li");
|
||||
if (!li) return;
|
||||
const sub = li.querySelector(":scope > ul");
|
||||
if (!sub) {
|
||||
onDblClick?.(li.dataset.id || "");
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(ul);
|
||||
}
|
||||
|
||||
window.BootstrapTreeView = { create };
|
||||
})();
|
||||
Reference in New Issue
Block a user