mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-10-25 11:17:28 +02:00 
			
		
		
		
	lots of stuff
This commit is contained in:
		
							parent
							
								
									54e7998bf7
								
							
						
					
					
						commit
						320f56e473
					
				| @ -12,9 +12,7 @@ COPY build/libs/*.jar app.jar | ||||
| EXPOSE 8080 | ||||
| 
 | ||||
| # Set environment variables | ||||
| ENV APP_HOME_NAME="Stirling PDF" | ||||
| #ENV APP_HOME_DESCRIPTION="Personal PDF Website!" | ||||
| #ENV APP_NAVBAR_NAME="Stirling PDF" | ||||
| ENV GROUPS_TO_REMOVE=LibreOffice | ||||
| 
 | ||||
| # Run the application | ||||
| RUN chmod +x /scripts/init.sh | ||||
|  | ||||
							
								
								
									
										14
									
								
								Dockerfile-ultralite
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								Dockerfile-ultralite
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| # Build jbig2enc in a separate stage | ||||
| FROM openjdk:17-jdk-slim | ||||
| 
 | ||||
| # Copy the application JAR file | ||||
| COPY build/libs/*.jar app.jar | ||||
| 
 | ||||
| # Expose the application port | ||||
| EXPOSE 8080 | ||||
| 
 | ||||
| # Set environment variables | ||||
| ENV GROUPS_TO_REMOVE=LibreOffice,CLI | ||||
| 
 | ||||
| # Run the application | ||||
| CMD ["java", "-jar", "/app.jar"] | ||||
| @ -26,11 +26,11 @@ RUN git clone https://github.com/agl/jbig2enc && \ | ||||
| FROM openjdk:17-jdk-slim AS base | ||||
| RUN apt-get update && \ | ||||
|     apt-get install -y --no-install-recommends \ | ||||
|         libreoffice-core \ | ||||
|         libreoffice-core-nogui \ | ||||
|         libreoffice-common \ | ||||
|         libreoffice-writer \ | ||||
|         libreoffice-calc \ | ||||
|         libreoffice-impress \ | ||||
|         libreoffice-writer-nogui \ | ||||
|         libreoffice-calc-nogui \ | ||||
|         libreoffice-impress-nogui \ | ||||
|         python3-uno \ | ||||
|         python3-pip \ | ||||
|         unoconv \ | ||||
|  | ||||
| @ -7,11 +7,12 @@ import java.util.Map.Entry; | ||||
| import java.util.Set; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| 
 | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.stereotype.Service; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| @Service | ||||
| public class EndpointConfiguration { | ||||
| 
 | ||||
|     private static final Logger logger = LoggerFactory.getLogger(EndpointConfiguration.class); | ||||
|     private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>(); | ||||
|     private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>(); | ||||
| 
 | ||||
| @ -25,6 +26,7 @@ public class EndpointConfiguration { | ||||
|     } | ||||
| 
 | ||||
|     public void disableEndpoint(String endpoint) { | ||||
|         logger.info("Disabling {}", endpoint); | ||||
|         endpointStatuses.put(endpoint, false); | ||||
|     } | ||||
| 
 | ||||
| @ -162,6 +164,7 @@ public class EndpointConfiguration { | ||||
|         addEndpointToGroup("Javascript", "pdf-organizer"); | ||||
|         addEndpointToGroup("Javascript", "sign"); | ||||
|         addEndpointToGroup("Javascript", "compare"); | ||||
|          | ||||
|     } | ||||
|                  | ||||
|     private void processEnvironmentConfigs() { | ||||
|  | ||||
| @ -17,12 +17,10 @@ public class EndpointInterceptor implements HandlerInterceptor { | ||||
|     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) | ||||
|             throws Exception { | ||||
|         String requestURI = request.getRequestURI(); | ||||
|         System.out.println("trying " + requestURI); | ||||
|         if (!endpointConfiguration.isEndpointEnabled(requestURI)) { | ||||
|             response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled"); | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| } | ||||
| @ -18,7 +18,7 @@ public class MetricsConfig { | ||||
|         return new MeterFilter() { | ||||
|             @Override | ||||
|             public MeterFilterReply accept(Meter.Id id) { | ||||
|                 if (id.getName().equals("http.requests") || id.getName().equals("health")) { | ||||
|                 if (id.getName().equals("http.requests")) { | ||||
|                     return MeterFilterReply.NEUTRAL; | ||||
|                 } | ||||
|                 return MeterFilterReply.DENY; | ||||
|  | ||||
| @ -33,7 +33,7 @@ public class MetricsFilter extends OncePerRequestFilter { | ||||
|         String uri = request.getRequestURI(); | ||||
| 
 | ||||
|         // Ignore static resources | ||||
|         if (!(uri.startsWith("/css") || uri.startsWith("/js") || uri.startsWith("/images") || uri.endsWith(".ico") || uri.endsWith(".svg")|| uri.endsWith(".js"))) { | ||||
|         if (!(uri.startsWith("/js") || uri.startsWith("/images") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) { | ||||
|             Counter counter = Counter.builder("http.requests") | ||||
|                     .tag("uri", uri) | ||||
|                     .tag("method", request.getMethod()) | ||||
|  | ||||
| @ -0,0 +1,80 @@ | ||||
| package stirling.software.SPDF.controller.web; | ||||
| import io.micrometer.core.instrument.Counter; | ||||
| import io.micrometer.core.instrument.Meter; | ||||
| import io.micrometer.core.instrument.MeterRegistry; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| @RestController | ||||
| @RequestMapping("/api/v1") | ||||
| public class MetricsController { | ||||
| 
 | ||||
|     private final MeterRegistry meterRegistry; | ||||
| 
 | ||||
|     public MetricsController(MeterRegistry meterRegistry) { | ||||
|         this.meterRegistry = meterRegistry; | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/status") | ||||
|     @Operation(summary = "Application status and version", | ||||
|             description = "This endpoint returns the status of the application and its version number.") | ||||
|     public Map<String, String> getStatus() { | ||||
|         Map<String, String> status = new HashMap<>(); | ||||
|         status.put("status", "UP"); | ||||
|         status.put("version", getClass().getPackage().getImplementationVersion()); | ||||
|         return status; | ||||
|     } | ||||
|      | ||||
|     @GetMapping("/loads") | ||||
|     @Operation(summary = "GET request count", | ||||
|             description = "This endpoint returns the total count of GET requests or the count of GET requests for a specific endpoint.") | ||||
|     public Double getPageLoads(@RequestParam Optional<String> endpoint) { | ||||
|         try { | ||||
|             double count = 0.0; | ||||
| 
 | ||||
|             for (Meter meter : meterRegistry.getMeters()) { | ||||
|                 if (meter.getId().getName().equals("http.requests")) { | ||||
|                     String method = meter.getId().getTag("method"); | ||||
|                     if (method != null && method.equals("GET")) { | ||||
|                         if (meter instanceof Counter) { | ||||
|                             count += ((Counter) meter).count(); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return count; | ||||
|         } catch (Exception e) { | ||||
|             return -1.0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @GetMapping("/requests") | ||||
|     @Operation(summary = "POST request count", | ||||
|             description = "This endpoint returns the total count of POST requests or the count of POST requests for a specific endpoint.") | ||||
|     public Double getTotalRequests(@RequestParam Optional<String> endpoint) { | ||||
|         try { | ||||
|             Counter counter; | ||||
|             if (endpoint.isPresent()) { | ||||
|                 counter = meterRegistry.get("http.requests") | ||||
|                     .tags("method", "POST", "uri", endpoint.get()).counter(); | ||||
|             } else { | ||||
|                 counter = meterRegistry.get("http.requests") | ||||
|                     .tags("method", "POST").counter(); | ||||
|             } | ||||
|             return counter.count(); | ||||
|         } catch (Exception e) { | ||||
|             return -1.0; | ||||
|         } | ||||
|          | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -22,5 +22,4 @@ server.servlet.context-path=${APP_ROOT_PATH:/} | ||||
| spring.devtools.restart.enabled=true | ||||
| spring.devtools.livereload.enabled=true | ||||
| 
 | ||||
| spring.thymeleaf.encoding=UTF-8 | ||||
| 
 | ||||
| spring.thymeleaf.encoding=UTF-8 | ||||
| @ -151,11 +151,11 @@ function compareVersions(version1, version2) { | ||||
|     </a> | ||||
|     <div class="dropdown-menu" aria-labelledby="navbarDropdown"> | ||||
|         <!-- Existing menu items --> | ||||
|         <div th:replace="fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'images/union.svg', 'home.merge.title', 'home.merge.desc')"></div> | ||||
|         <div th:replace="fragments/navbarEntry :: navbarEntry ('split-pdfs', 'images/layout-split.svg', 'home.split.title', 'home.split.desc')"></div> | ||||
|         <div th:replace="fragments/navbarEntry :: navbarEntry ( 'pdf-organizer', 'images/sort-numeric-down.svg', 'home.pdfOrganiser.title', 'home.pdfOrganiser.desc')"></div> | ||||
|         <div th:replace="fragments/navbarEntry :: navbarEntry ( 'rotate-pdf', 'images/arrow-clockwise.svg', 'home.rotate.title', 'home.rotate.desc')"></div> | ||||
|         <div th:replace="fragments/navbarEntry :: navbarEntry ( 'remove-pages', 'images/file-earmark-x.svg', 'home.removePages.title', 'home.removePages.desc')"></div> | ||||
|         <div th:replace="~{fragments/navbarEntry :: navbarEntry ('merge-pdfs', 'images/union.svg', 'home.merge.title', 'home.merge.desc')}"></div> | ||||
|         <div th:replace="~{fragments/navbarEntry :: navbarEntry ('split-pdfs', 'images/layout-split.svg', 'home.split.title', 'home.split.desc')}"></div> | ||||
|         <div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'pdf-organizer', 'images/sort-numeric-down.svg', 'home.pdfOrganiser.title', 'home.pdfOrganiser.desc')}"></div> | ||||
|         <div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'rotate-pdf', 'images/arrow-clockwise.svg', 'home.rotate.title', 'home.rotate.desc')}"></div> | ||||
|         <div th:replace="~{fragments/navbarEntry :: navbarEntry ( 'remove-pages', 'images/file-earmark-x.svg', 'home.removePages.title', 'home.removePages.desc')}"></div> | ||||
| 
 | ||||
|     </div> | ||||
| </li> | ||||
| @ -167,16 +167,16 @@ function compareVersions(version1, version2) { | ||||
|                     </a> | ||||
|                         <div class="dropdown-menu" aria-labelledby="navbarDropdown"> | ||||
|                             <!-- Existing menu items --> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('img-to-pdf', 'images/image.svg', 'home.imageToPdf.title', 'home.imageToPdf.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('file-to-pdf', 'images/file.svg', 'home.fileToPDF.title', 'home.fileToPDF.desc')"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('img-to-pdf', 'images/image.svg', 'home.imageToPdf.title', 'home.imageToPdf.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('file-to-pdf', 'images/file.svg', 'home.fileToPDF.title', 'home.fileToPDF.desc')}"></div> | ||||
|                             <hr class="dropdown-divider"> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-img', 'images/image.svg', 'home.pdfToImage.title', 'home.pdfToImage.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-word', 'images/file-earmark-word.svg', 'home.PDFToWord.title', 'home.PDFToWord.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-presentation', 'images/file-earmark-ppt.svg', 'home.PDFToPresentation.title', 'home.PDFToPresentation.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-text', 'images/filetype-txt.svg', 'home.PDFToText.title', 'home.PDFToText.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-html', 'images/filetype-html.svg', 'home.PDFToHTML.title', 'home.PDFToHTML.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-xml', 'images/filetype-xml.svg', 'home.PDFToXML.title', 'home.PDFToXML.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('pdf-to-pdfa', 'images/file-earmark-pdf.svg', 'home.pdfToPDFA.title', 'home.pdfToPDFA.desc')"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-img', 'images/image.svg', 'home.pdfToImage.title', 'home.pdfToImage.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-word', 'images/file-earmark-word.svg', 'home.PDFToWord.title', 'home.PDFToWord.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-presentation', 'images/file-earmark-ppt.svg', 'home.PDFToPresentation.title', 'home.PDFToPresentation.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-text', 'images/filetype-txt.svg', 'home.PDFToText.title', 'home.PDFToText.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-html', 'images/filetype-html.svg', 'home.PDFToHTML.title', 'home.PDFToHTML.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-xml', 'images/filetype-xml.svg', 'home.PDFToXML.title', 'home.PDFToXML.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('pdf-to-pdfa', 'images/file-earmark-pdf.svg', 'home.pdfToPDFA.title', 'home.pdfToPDFA.desc')}"></div> | ||||
|                              | ||||
|                              | ||||
| 
 | ||||
| @ -191,10 +191,10 @@ function compareVersions(version1, version2) { | ||||
|                         <img class="icon" src="images/shield-check.svg" alt="icon" style="width: 16px; height: 16px; vertical-align: middle;"> <span class="icon-text"  th:text="#{navbar.security}"></span> | ||||
|                     </a> | ||||
|                     <div class="dropdown-menu" aria-labelledby="navbarDropdown"> | ||||
|                         <div th:replace="fragments/navbarEntry :: navbarEntry ('add-password', 'images/lock.svg', 'home.addPassword.title', 'home.addPassword.desc')"></div> | ||||
|                         <div th:replace="fragments/navbarEntry :: navbarEntry ('remove-password', 'images/unlock.svg', 'home.removePassword.title', 'home.removePassword.desc')"></div> | ||||
|                         <div th:replace="fragments/navbarEntry :: navbarEntry ('change-permissions', 'images/shield-lock.svg', 'home.permissions.title', 'home.permissions.desc')"></div> | ||||
|                         <div th:replace="fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc')"></div> | ||||
|                         <div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-password', 'images/lock.svg', 'home.addPassword.title', 'home.addPassword.desc')}"></div> | ||||
|                         <div th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-password', 'images/unlock.svg', 'home.removePassword.title', 'home.removePassword.desc')}"></div> | ||||
|                         <div th:replace="~{fragments/navbarEntry :: navbarEntry ('change-permissions', 'images/shield-lock.svg', 'home.permissions.title', 'home.permissions.desc')}"></div> | ||||
|                         <div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-watermark', 'images/droplet.svg', 'home.watermark.title', 'home.watermark.desc')}"></div> | ||||
| 
 | ||||
|                     </div> | ||||
|                 </li> | ||||
| @ -207,18 +207,17 @@ function compareVersions(version1, version2) { | ||||
|                          | ||||
|                         </a> | ||||
|                         <div class="dropdown-menu" aria-labelledby="navbarDropdown"> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('ocr-pdf', 'images/search.svg', 'home.ocr.title', 'home.ocr.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('add-image', 'images/file-earmark-richtext.svg', 'home.addImage.title', 'home.addImage.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('compress-pdf', 'images/file-zip.svg', 'home.compressPdfs.title', 'home.compressPdfs.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('extract-images', 'images/images.svg', 'home.extractImages.title', 'home.extractImages.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('change-metadata', 'images/clipboard-data.svg', 'home.changeMetadata.title', 'home.changeMetadata.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('extract-image-scans', 'images/scanner.svg', 'home.ScannerImageSplit.title', 'home.ScannerImageSplit.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('sign', 'images/sign.svg', 'home.sign.title', 'home.sign.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('flatten', 'images/flatten.svg', 'home.flatten.title', 'home.flatten.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('repair', 'images/wrench.svg', 'home.repair.title', 'home.repair.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('remove-blanks', 'images/blank-file.svg', 'home.removeBlanks.title', 'home.removeBlanks.desc')"></div> | ||||
|                             <div th:replace="fragments/navbarEntry :: navbarEntry ('compare', 'images/scales.svg', 'home.compare.title', 'home.compare.desc')"></div> | ||||
|                                                          | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('ocr-pdf', 'images/search.svg', 'home.ocr.title', 'home.ocr.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('add-image', 'images/file-earmark-richtext.svg', 'home.addImage.title', 'home.addImage.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('compress-pdf', 'images/file-zip.svg', 'home.compressPdfs.title', 'home.compressPdfs.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-images', 'images/images.svg', 'home.extractImages.title', 'home.extractImages.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('change-metadata', 'images/clipboard-data.svg', 'home.changeMetadata.title', 'home.changeMetadata.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('extract-image-scans', 'images/scanner.svg', 'home.ScannerImageSplit.title', 'home.ScannerImageSplit.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('sign', 'images/sign.svg', 'home.sign.title', 'home.sign.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('flatten', 'images/flatten.svg', 'home.flatten.title', 'home.flatten.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('repair', 'images/wrench.svg', 'home.repair.title', 'home.repair.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('remove-blanks', 'images/blank-file.svg', 'home.removeBlanks.title', 'home.removeBlanks.desc')}"></div> | ||||
|                             <div th:replace="~{fragments/navbarEntry :: navbarEntry ('compare', 'images/scales.svg', 'home.compare.title', 'home.compare.desc')}"></div> | ||||
|                         </div> | ||||
|                     </li> | ||||
| 
 | ||||
| @ -351,9 +350,13 @@ function compareVersions(version1, version2) { | ||||
|             <div class="modal-body"> | ||||
|                 <div class="d-flex justify-content-between align-items-center mb-3"> | ||||
|                     <p class="mb-0" th:utext="#{settings.appVersion} + ' ' + ${@appVersion}"></p> | ||||
|                     <a href="swagger-ui/index.html" target="_blank"> | ||||
|                         <button type="button" class="btn btn-sm btn-outline-primary"> API </button> | ||||
|                     </a> | ||||
|                     <a href="https://github.com/Frooodle/Stirling-PDF/releases" target="_blank"> | ||||
| 					    <button type="button" class="btn btn-sm btn-outline-primary" id="update-btn" th:utext="#{settings.update}"></button> | ||||
| 					</a> | ||||
|                      | ||||
| 
 | ||||
|                 </div> | ||||
|                 <div class="form-group"> | ||||
| @ -375,6 +378,7 @@ function compareVersions(version1, version2) { | ||||
|                     <label class="custom-control-label" for="boredWaiting" th:text="#{bored}"></label> | ||||
|                   </div> | ||||
|                 </div> | ||||
|   | ||||
|             </div> | ||||
|             <div class="modal-footer"> | ||||
|                 <button type="button" class="btn btn-secondary" data-dismiss="modal" th:text="#{close}"></button> | ||||
| @ -385,6 +389,17 @@ function compareVersions(version1, version2) { | ||||
| 
 | ||||
| 
 | ||||
|     <script> | ||||
|     $(document).ready(function() { | ||||
|         $(".nav-item.dropdown").each(function() { | ||||
|             var $dropdownMenu = $(this).find(".dropdown-menu"); | ||||
|             if ($dropdownMenu.children().length <= 2 && $dropdownMenu.children("hr.dropdown-divider").length === $dropdownMenu.children().length) { | ||||
|                 $(this).prev('.nav-item.nav-item-separator').remove(); | ||||
|                 $(this).remove(); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 					// Get the download option from local storage, or set it to 'sameWindow' if it doesn't exist | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user