From 884daee6629a30b7b84bfac5a334eaa803f2633c Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 13:31:15 +0200 Subject: [PATCH 01/14] fix(ci): setup prettier and eslint --- .github/FUNDING.yml | 6 +- .github/workflows/prs.yml | 17 + .prettierignore | 2 + .prettierrc | 3 + README.md | 545 +++++++++------ dist/js/winbox.min.js | 48 +- eslint.config.js | 7 + index.html | 1340 ++++++++++++++++++++----------------- package.json | 7 +- 9 files changed, 1115 insertions(+), 860 deletions(-) create mode 100644 .github/workflows/prs.yml create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 eslint.config.js diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f7d0fb4..21ce65e 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -4,4 +4,8 @@ github: [ts-thomas] open_collective: winboxjs liberapay: ts-thomas patreon: user?u=96245532 -custom: ["https://www.paypal.com/donate/?hosted_button_id=GEVR88FC9BWRW", "https://salt.bountysource.com/teams/ts-thomas"] +custom: + [ + "https://www.paypal.com/donate/?hosted_button_id=GEVR88FC9BWRW", + "https://salt.bountysource.com/teams/ts-thomas", + ] diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml new file mode 100644 index 0000000..4fc89e0 --- /dev/null +++ b/.github/workflows/prs.yml @@ -0,0 +1,17 @@ +name: PR Code checks +on: + pull_request: + branches: [master] +jobs: + lint: + timeout-minutes: 3 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 21 + cache: "npm" + - run: npm ci + - name: Run lint + run: npm run lint diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e17b023 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Ignore artifacts: +dist diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..757fd64 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "trailingComma": "es5" +} diff --git a/README.md b/README.md index 4dc01c2..668214c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Demo  •  Getting Started  •  Options  •  API  •  Themes  •  Customize  •  Changelog + ### Live Demo and Code Examples https://nextapps-de.github.io/winbox/ @@ -43,7 +44,9 @@ Thanks a lot, Thomas (ts-thomas) + ### Plugins / Add-ons / Extensions + Vue 2 and 3 (a wrapper component that adds the ability to mount Vue components in WinBox.js):
https://github.com/wobsoriano/vue-winbox @@ -51,9 +54,10 @@ A React controlled component for WinBox.js, with full Reactful props and state.: https://github.com/rickonono3/react-winbox + ## Getting Started -__Get Latest Stable Build (Recommended):__ +**Get Latest Stable Build (Recommended):** @@ -112,7 +116,7 @@ __Get Latest Stable Build (Recommended):__
-__Get Latest (NPM):__ +**Get Latest (NPM):** ```cmd npm install winbox @@ -126,10 +130,10 @@ A best practice is to load the library as async and use some modern preloading m ```html - - - - + + + + - + ``` @@ -151,11 +155,11 @@ The non-bundled version needs to load js and css separately (css already include ```html - - + + - - + + ``` @@ -167,7 +171,7 @@ The ES6 modules are located in `src/js/`. But you need to load the stylesheet fi ```html - + ``` @@ -188,6 +192,7 @@ You can also load modules via CDN, e.g.: The ES6 modules are not minified. Please use your favored bundler or build tool for this purpose. + ## Overview Constructor: @@ -271,6 +276,7 @@ Callback methods: - winbox.**onrestore** + ## Options @@ -482,6 +488,7 @@ Callback methods: ## Create and Setup Window + #### Basic Window > When no `root` was specified the window will append to the `document.body`. @@ -491,17 +498,20 @@ new WinBox("Window Title"); ``` Alternatively: + ```js WinBox.new("Window Title"); ``` Alternatively: + ```js new WinBox({ title: "Window Title" }); ``` Alternatively: + ```js var winbox = WinBox(); winbox.setTitle("Window Title"); @@ -513,7 +523,7 @@ winbox.setTitle("Window Title"); ```js new WinBox("Window Title", { - root: document.body + root: document.body, }); ``` @@ -523,12 +533,13 @@ new WinBox("Window Title", { ```js new WinBox("Custom Color", { - background: "#ff005d" + background: "#ff005d", }); ``` Alternatively: + ```js var winbox = new WinBox("Custom Color"); winbox.setBackground("#ff005d"); @@ -540,16 +551,17 @@ winbox.setBackground("#ff005d"); ```js new WinBox({ - title: "Custom Border", - border: "1em" + title: "Custom Border", + border: "1em", }); ``` You can also define multiple border values (the order is: top, right, bottom, left): + ```js new WinBox({ - title: "Custom Border", - border: "0 1em 15px 1em" + title: "Custom Border", + border: "0 1em 15px 1em", }); ``` @@ -559,13 +571,14 @@ new WinBox({ ```js new WinBox({ - title: "Custom Icon", - icon: "img/icon.svg" + title: "Custom Icon", + icon: "img/icon.svg", }); ``` Alternatively: + ```js var winbox = new WinBox("Custom Icon"); winbox.setIcon("img/icon.svg"); @@ -573,61 +586,64 @@ winbox.setIcon("img/icon.svg"); See below in the style section to find out how to easily customize the titlebar icon style via css. -#### Custom Viewport +#### Custom Viewport > Define the available area (relative to the document) in which the window can move or could be resized (supports units "px" and "%"). ```js new WinBox("Custom Viewport", { - top: "50px", - right: "5%", - bottom: 50, - left: "5%" + top: "50px", + right: "5%", + bottom: 50, + left: "5%", }); ``` Alternatively (but just support numbers!): + ```js var winbox = new WinBox("Custom Viewport"); winbox.top = 50; winbox.right = 200; winbox.bottom = 0; -winbox.left = 200 +winbox.left = 200; ``` + #### Custom Position / Size > Supports keywords `"right"` for x-axis, `"bottom"` for y-axis, `"center"` for both, units `px` and `%` also for both. ```js new WinBox("Custom Viewport", { - x: "center", - y: "center", - width: "50%", - height: "50%" + x: "center", + y: "center", + width: "50%", + height: "50%", }); ``` ```js new WinBox("Custom Viewport", { - x: "right", - y: "bottom", - width: "200px", - height: "200px" + x: "right", + y: "bottom", + width: "200px", + height: "200px", }); ``` Alternatively (also supports same units and keywords as above): + ```js var winbox = new WinBox("Custom Viewport"); -winbox.resize("50%", "50%") - .move("center", "center"); +winbox.resize("50%", "50%").move("center", "center"); ``` Alternatively (just support numeric values as `px` when directly assigned!): + ```js var winbox = new WinBox("Custom Viewport"); @@ -643,28 +659,31 @@ winbox.move(); > In some cases you need to execute `.resize()` before `.move()` to properly apply relative positions which is taking the windows size into account. + #### Overflow Window Allow the window to move outside the viewport borders on left, right and bottom (default is "false"). ```js new WinBox({ - title: "Overflow Window", - overflow: true + title: "Overflow Window", + overflow: true, }); ``` + #### Modal Window ```js new WinBox({ - title: "Modal Window", - modal: true + title: "Modal Window", + modal: true, }); ``` + #### Window Stack The window stack gets you an ordered list of every created window which wasn't already closed. The last focused windows is placed as last entry in the Array. @@ -680,6 +699,7 @@ const stack = WinBox.stack(); // [B, C, A] ``` + #### Themes > Feel free to create your own themes and share them with us. @@ -690,9 +710,9 @@ Load the corresponding css files (or use a bundler), e.g.: ```html - - - + + + ``` @@ -700,8 +720,8 @@ Just add the name of the theme via `class`: ```js var winbox = new WinBox({ - title: "Theme: Modern", - class: "modern" + title: "Theme: Modern", + class: "modern", }); ``` @@ -718,29 +738,31 @@ You can change themes during the lifetime of the window. #### Set innerHTML -> Do not forget to sanitize any user inputs which is part of the __html__ as this can lead to unintended XSS! +> Do not forget to sanitize any user inputs which is part of the **html** as this can lead to unintended XSS! ```js new WinBox("Set innerHTML", { - html: "

Lorem Ipsum

" + html: "

Lorem Ipsum

", }); ``` Alternatively: + ```js var winbox = new WinBox("Set innerHTML"); winbox.body.innerHTML = "

Lorem Ipsum

"; ``` + #### Mount DOM (Cloned) > When cloning you can easily create multiple window instances of the same content in parallel. ```html
-

Lorem Ipsum

-

Lorem ipsum [...]

+

Lorem Ipsum

+

Lorem ipsum [...]

``` @@ -748,11 +770,12 @@ winbox.body.innerHTML = "

Lorem Ipsum

"; var node = document.getElementById("content"); new WinBox("Mount DOM", { - mount: node.cloneNode(true) + mount: node.cloneNode(true), }); ``` Alternatively: + ```js var node = document.getElementById("content"); var winbox = new WinBox("Mount DOM"); @@ -770,10 +793,10 @@ You can simply use a hidden backstore to hold contents, as well you can use any ```html ``` @@ -781,13 +804,14 @@ You can simply use a hidden backstore to hold contents, as well you can use any var node = document.getElementById("content"); new WinBox("Mount DOM", { - mount: node + mount: node, }); ``` > Auto-Unmount is a helpful feature which automatically moves back the fragment to the backstore source when closing the window. Alternatively: + ```js var node = document.getElementById("content"); var winbox = new WinBox("Mount DOM"); @@ -796,14 +820,15 @@ winbox.mount(node); ``` + #### Explicit Unmount ```html ``` @@ -841,16 +866,17 @@ Override default auto-unmount behavior when closing the window: ```js new WinBox("Mount DOM", { - mount: node, - onclose: function(){ - this.unmount(document.getElementById("backstore-2")); - } + mount: node, + onclose: function () { + this.unmount(document.getElementById("backstore-2")); + }, }); ``` #### Manual Mount Contents Feel free to use the `winbox.body` directly: + ```js var node = document.getElementById("content"); var winbox = new WinBox("Mount DOM"); @@ -859,19 +885,23 @@ winbox.body.appendChild(node); ``` Or delegate it as a root to your templating engine, e.g.: + ```js Mikado(template).mount(winbox.body).render(data); ``` #### Open URI / URL -> Do not forget to sanitize any user inputs when it is part of the __url__ as this can lead to unintended XSS! +> Do not forget to sanitize any user inputs when it is part of the **url** as this can lead to unintended XSS! The onload callback is optionally. + ```js new WinBox("Open URL", { - url: "https://wikipedia.com", - onload: function(){ /* extern page loaded */} + url: "https://wikipedia.com", + onload: function () { + /* extern page loaded */ + }, }); ``` @@ -879,10 +909,11 @@ new WinBox("Open URL", { Alternatively: + ```js var winbox = new WinBox("Open URL"); -winbox.setUrl("https://wikipedia.com", function(){ - /* extern page loaded */ +winbox.setUrl("https://wikipedia.com", function () { + /* extern page loaded */ }); ``` @@ -890,6 +921,7 @@ winbox.setUrl("https://wikipedia.com", function(){ Window States / Information: + ```js var winbox = new WinBox(); @@ -904,6 +936,7 @@ console.log("Current Focused State:", winbox.focused); Window Size: + ```js var winbox = new WinBox(); @@ -917,6 +950,7 @@ console.log("Current Height:", winbox.height); Window Position: + ```js var winbox = new WinBox(); @@ -930,6 +964,7 @@ console.log("Current Position Y:", winbox.y); Window Viewport: + ```js var winbox = new WinBox(); @@ -949,6 +984,7 @@ console.log("Current Viewport Left:", winbox.left); The window body acts like the `document.body` and has a scroll pane: + ```js var winbox = new WinBox(); winbox.body.innerHTML = "

Lorem Ipsum

"; @@ -956,12 +992,14 @@ winbox.body.innerHTML = "

Lorem Ipsum

"; Get the DOM element from the window outer frame: + ```js var winbox = new WinBox(); var root = winbox.window; ``` You also can get the window element by DOM id: + ```js var winbox = new WinBox(); var root = document.getElementById(winbox.id); @@ -991,6 +1029,7 @@ winbox.toggleClass("my-toggle"); ``` You can grab the `winbox` instance from the window outer frame DOM element: + ```js var winbox = new WinBox(); // assume you have a DOM reference to the winbox window: @@ -1007,6 +1046,7 @@ var winbox = new WinBox(); Focus a window (bring up to front): + ```js winbox.focus(); winbox.focus(true); @@ -1015,6 +1055,7 @@ winbox.blur(false); Blur a focused window: + ```js winbox.blur(); winbox.blur(true); @@ -1023,6 +1064,7 @@ winbox.focus(false); Set the minimized state of a window: + ```js winbox.minimize(); winbox.minimize(true); @@ -1031,6 +1073,7 @@ winbox.minimize(false); Set the maximized state of a window: + ```js winbox.maximize(); winbox.maximize(true); @@ -1039,6 +1082,7 @@ winbox.maximize(false); Set the fullscreen state of a window: + ```js winbox.fullscreen(); winbox.fullscreen(true); @@ -1047,12 +1091,14 @@ winbox.fullscreen(false); Restore the state of a window: + ```js winbox.restore(); ``` Hide a specific window: + ```js winbox.hide(); winbox.hide(true); @@ -1061,6 +1107,7 @@ winbox.show(false); Show a specific hidden window: + ```js winbox.show(); winbox.show(true); @@ -1069,12 +1116,14 @@ winbox.hide(false); Close and destroy a window: + ```js winbox.close(); ``` Close and destroy a window depending on custom conditional (e.g. by a confirmation status): + ```js winbox.close(false || true); ``` @@ -1084,11 +1133,12 @@ winbox.close(false || true); ```js var winbox = WinBox(); -winbox.setTitle("Title") - .setBackground("#fff") - .resize("50%", "50%") - .move("center", "center") - .mount(document.getElementById("content")); +winbox + .setTitle("Title") + .setBackground("#fff") + .resize("50%", "50%") + .move("center", "center") + .mount(document.getElementById("content")); ``` > When using "center" as position you need to call `.resize()` before `.move()`. @@ -1101,84 +1151,88 @@ You can assign callbacks via the option payload when creating a window. ```js var winbox = WinBox({ - oncreate: function(options){ - options.autosize = true; - }, - onfocus: function(){ - this.setBackground("#fff"); - }, - onblur: function(){ - this.setBackground("#999"); - }, - onresize: function(width, height){ - console.log("width:", width, "height:", height); - }, - onfullscreen: function(){ - this.setBackground("#666"); - }, - onminimize: function(){ - this.setBackground("#999"); - }, - onmaximize: function(){ - this.setBackground("#AAA"); - }, - onrestore: function(){ - this.setBackground("#DDD"); - }, - onmove: function(x, y){ - console.log("x:", x, "y:", y); - }, - onclose: function(force){ - // return "true" to skip the closing - // return "false" to allow closing - // use delegated force parameter optionally - return !confirm("Close window?"); - } + oncreate: function (options) { + options.autosize = true; + }, + onfocus: function () { + this.setBackground("#fff"); + }, + onblur: function () { + this.setBackground("#999"); + }, + onresize: function (width, height) { + console.log("width:", width, "height:", height); + }, + onfullscreen: function () { + this.setBackground("#666"); + }, + onminimize: function () { + this.setBackground("#999"); + }, + onmaximize: function () { + this.setBackground("#AAA"); + }, + onrestore: function () { + this.setBackground("#DDD"); + }, + onmove: function (x, y) { + console.log("x:", x, "y:", y); + }, + onclose: function (force) { + // return "true" to skip the closing + // return "false" to allow closing + // use delegated force parameter optionally + return !confirm("Close window?"); + }, }); ``` #### The "onclose" callback -> The event `onclose` will be triggered right before closing and __stops__ closing when a callback was applied and returns a __truthy value__. +> The event `onclose` will be triggered right before closing and **stops** closing when a callback was applied and returns a **truthy value**. Within your callback function just return `true` to stops the closing or return `false` to perform closing as default. + ```js -new WinBox({ - onclose: function(){ - // return "true" to skip the closing - // return "false" to allow closing - if(do_some_checks()){ - return true; - } - } +new WinBox({ + onclose: function () { + // return "true" to skip the closing + // return "false" to allow closing + if (do_some_checks()) { + return true; + } + }, }); ``` The `force` parameter from the `winbox.close(boolean)` will be delegated to your callback function as the first parameter. You need to handle the "force" state in your callback function. ```js -var winbox = WinBox({ - onclose: function onclose(force){ - // use delegated force parameter optionally - return !force && !confirm("Close window?"); - } +var winbox = WinBox({ + onclose: function onclose(force) { + // use delegated force parameter optionally + return !force && !confirm("Close window?"); + }, }); ``` Close the window and execute callback as default (will show the prompt from example above): + ```js winbox.close(); ``` Force closing the window (does not show the prompt from example above): + ```js winbox.close(true); ``` + #### Use Control Classes -WinBox provides you some built-in control classes you can pass when creating a window instance. +WinBox provides you some built-in control classes you can pass when creating a window instance. > All control classes from this list could be added or removed during lifetime of the window (after creation). State classes like `max`, `min`, `full`, `hidden` and `focus` could not be changed manually! For this purpose use the WinBox member methods accordingly e.g. `maximize()`, `minimize()`, `hide()`. @@ -1237,18 +1291,20 @@ WinBox provides you some built-in control classes you can pass when creating a w > Without the header the user isn't able to move the window frame. It may be useful for creating fixed popups. Pass in classnames when creating the window to apply behaviour: + ```js const winbox = WinBox({ - class: [ "no-min", "no-max", "no-full", "no-resize", "no-move" ] + class: ["no-min", "no-max", "no-full", "no-resize", "no-move"], }); ``` > The example above is a good start to create classical popups. Alternatively you can use a whitespace separated string: + ```js const winbox = WinBox({ - class: "no-min no-max no-full no-resize no-move" + class: "no-min no-max no-full no-resize no-move", }); ``` @@ -1258,20 +1314,19 @@ You can add or remove all control classes from above along the window's lifetime ```js const winbox = WinBox(); -winbox.addClass("no-resize") - .addClass("no-move"); +winbox.addClass("no-resize").addClass("no-move"); ``` + ```js -winbox.removeClass("no-resize") - .removeClass("no-move"); +winbox.removeClass("no-resize").removeClass("no-move"); ``` + ```js -winbox.toggleClass("no-resize") - .toggleClass("no-move"); +winbox.toggleClass("no-resize").toggleClass("no-move"); ``` + ```js -const state = winbox.hasClass("no-resize") && - winbox.hasClass("no-move"); +const state = winbox.hasClass("no-resize") && winbox.hasClass("no-move"); ``` ## Custom Splitscreen @@ -1280,11 +1335,11 @@ Use the viewport limit to define your own splitscreen areas, e.g. for a simple v ```js new WinBox("Split Left", { - right: "50%" + right: "50%", }); new WinBox("Split Right", { - left: "50%" + left: "50%", }); ``` @@ -1321,58 +1376,64 @@ The splitscreen from above will look like this grid: You can set the values for the viewport dynamically, doing this makes it possible to size the grid dynamically and also change the grid schema. + ## Custom Controls This example will add a custom control button `.wb-like` to the window heading toolbar along some CSS for icon styling: + ```css .wb-like { - background-size: 20px auto; + background-size: 20px auto; } .wb-like.active { - background-image: url(demo/heart-filled.svg) !important; + background-image: url(demo/heart-filled.svg) !important; } ``` Attach a control to the window toolbar: + ```js winbox.addControl({ - // the position index - index: 1, - // classname to apply styling - class: "wb-like", - // icon url when not specified via classname - image: "demo/heart.svg", - // click listener - click: function(event, winbox){ - // the winbox instance will be passed as 2nd parameter - console.log(winbox.id); - // "this" refers to the button which was clicked: - this.classList.toggle("active"); - } + // the position index + index: 1, + // classname to apply styling + class: "wb-like", + // icon url when not specified via classname + image: "demo/heart.svg", + // click listener + click: function (event, winbox) { + // the winbox instance will be passed as 2nd parameter + console.log(winbox.id); + // "this" refers to the button which was clicked: + this.classList.toggle("active"); + }, }); ``` Remove a control from the window toolbar: + ```js -winbox.removeControl("wb-like") - .removeControl("wb-min"); +winbox.removeControl("wb-like").removeControl("wb-min"); ``` + ## Custom Template (Layout) You can fully customize the WinBox window layout by providing a custom `template` via the config during creation. This way you can add new elements to the window or re-arrange them. This example will add a control button `.wb-custom` to the window toolbar by using a custom template along some CSS: + ```css .wb-custom { - background-image: url(demo/icon-github.svg); - background-size: 17px auto; + background-image: url(demo/icon-github.svg); + background-size: 17px auto; } ``` Create by using a custom template: + ```js const template = document.createElement("div"); template.innerHTML = ` @@ -1392,6 +1453,7 @@ new WinBox("Custom Template", { template }); > The `.wb-drag` element needs to be existing when the user should be able to move the window by dragging the heading toolbar. + ## Customize Window > Additionally, take a look into the themes folder to get some ideas how to customize the window. @@ -1401,176 +1463,226 @@ The window boilerplate: WinBox Boilerplate Hide or disable specific icons: + ```css -.wb-min { display: none } -.wb-full { display: none } -.wb-max { display: none } -.wb-close { display: none } +.wb-min { + display: none; +} +.wb-full { + display: none; +} +.wb-max { + display: none; +} +.wb-close { + display: none; +} ``` Modify a specific icon: + ```css .wb-max { - background-image: url(src/img/max.png); - background-position: center; - background-size: 15px auto; + background-image: url(src/img/max.png); + background-position: center; + background-size: 15px auto; } ``` Use black standard icons (useful for bright backgrounds): + ```css -.wb-control { filter: invert(1) } +.wb-control { + filter: invert(1); +} ``` Modify or disable resizing areas on the window borders: + ```css /* north */ -.wb-n { display: none } +.wb-n { + display: none; +} /* east */ -.wb-e { display: none } +.wb-e { + display: none; +} /* south */ -.wb-s { display: none } +.wb-s { + display: none; +} /* west */ -.wb-w { display: none } +.wb-w { + display: none; +} /* north-west */ -.wb-nw { display: none } +.wb-nw { + display: none; +} /* north-east */ -.wb-ne { display: none } +.wb-ne { + display: none; +} /* south-west */ -.wb-sw { display: none } +.wb-sw { + display: none; +} /* south-east */ -.wb-se { display: none } +.wb-se { + display: none; +} ``` Modify or disable the window drop shadow: + ```css -.winbox { box-shadow: none } +.winbox { + box-shadow: none; +} ``` Style the header title: + ```css -.wb-title { font-size: 12px } +.wb-title { + font-size: 12px; +} ``` Style the titlebar icon: + ```css .wb-icon { - width: 35px; - background-size: 35px 35px; + width: 35px; + background-size: 35px 35px; } ``` Style the window background (frame): + ```css .winbox { - background: linear-gradient(90deg, #ff00f0, #0050ff); - border-radius: 12px 12px 0 0; + background: linear-gradient(90deg, #ff00f0, #0050ff); + border-radius: 12px 12px 0 0; } ``` Style the body of a window element and set the frame border: + ```css .wb-body { - /* the width of window frame border: */ - margin: 4px; - color: #fff; - background: #131820; + /* the width of window frame border: */ + margin: 4px; + color: #fff; + background: #131820; } ``` > The margin of `.wb-body` corresponds to the width of the window border. Apply styles when window is in "minimized" state: + ```css .winbox { - border-radius: 10px; + border-radius: 10px; } .winbox.min { - border-radius: 0; + border-radius: 0; } .winbox.min .windbox-title { - font-size: 12px; + font-size: 12px; } ``` Apply styles when window is NOT in "minimized" state: + ```css .winbox:not(.min) { - /* apply styles */ + /* apply styles */ } ``` Apply styles when window is in "maximized" state: + ```css .winbox { - border-radius: 10px; + border-radius: 10px; } .winbox.max { - border-radius: 0; + border-radius: 0; } .winbox.max .wb-max { - opacity: 0.5; + opacity: 0.5; } ``` Apply styles when window is NOT in "maximized" state: + ```css .winbox:not(.max) { - /* apply styles */ + /* apply styles */ } ``` Apply styles when window is in "fullscreen" state: + ```css .wb-body:fullscreen { - /* apply styles */ + /* apply styles */ } ``` Apply styles when window is in "focus" state: + ```css .winbox { - background: #999; + background: #999; } .winbox.focus { - background: #0050ff; + background: #0050ff; } .winbox .wb-control { - display: none; + display: none; } .winbox.focus .wb-control { - display: block; + display: block; } ``` Apply styles when window is NOT in "focus" state (the same logic from example above, but shorter): + ```css .winbox:not(.focus) { - background: #999; + background: #999; } .winbox:not(.focus) .wb-control { - display: none; + display: none; } ``` Apply styles when window is in "modal" state: + ```css -.winbox.modal .wb-close { display: none } +.winbox.modal .wb-close { + display: none; +} ``` Customize the modal background overlay: + ```css .winbox.modal:after { - background: #0d1117; - opacity: 0.5; - animation: none; + background: #0d1117; + opacity: 0.5; + animation: none; } ``` @@ -1578,20 +1690,20 @@ Customize the modal background overlay: ```css .wb-body::-webkit-scrollbar { - width: 12px; + width: 12px; } .wb-body::-webkit-scrollbar-track { - background: transparent; + background: transparent; } .wb-body::-webkit-scrollbar-thumb { - border-radius: 10px; - background: #263040; + border-radius: 10px; + background: #263040; } .wb-body::-webkit-scrollbar-thumb:window-inactive { - background: #181f2a; + background: #181f2a; } .wb-body::-webkit-scrollbar-corner { - background: transparent; + background: transparent; } ``` @@ -1603,13 +1715,13 @@ Now you can add the two classes `"wb-hide"` and `"wb-show"` to any element to co ```html -
-
Hide this header when in windowed mode
-
- -
-
Hide this footer when NOT in windowed mode
-
+
+
Hide this header when in windowed mode
+
+ +
+
Hide this footer when NOT in windowed mode
+
``` @@ -1617,7 +1729,7 @@ The `display` property when using `"wb-show"` might not fit your needs. Please c ```js new WinBox({ - mount: document.getElementById("content") + mount: document.getElementById("content"), }); ``` @@ -1643,13 +1755,13 @@ npm i @types/winbox --save-dev Step 3: Import WinBox in a component ```ts -import { Component } from '@angular/core'; -import 'winbox'; +import { Component } from "@angular/core"; +import "winbox"; declare const WinBox: WinBox.WinBoxConstructor; @Component({ - selector: 'my-app', - template: '' + selector: "my-app", + template: '', }) export class AppComponent { openWindow() { @@ -1664,7 +1776,6 @@ https://github.com/wobsoriano/vue-winbox It uses the native [teleport](https://v3.vuejs.org/api/built-in-components.html#teleport) component in Vue 3 and recommends https://github.com/LinusBorg/vue-simple-portal for Vue 2 users. - ## WinBox on React application https://github.com/rickonono3/react-winbox @@ -1674,11 +1785,13 @@ A React controlled component for WinBox.js, with full Reactful props and state. ## Custom Builds Go to the root directory of WinBox and run: + ```cmd npm install ``` Perform a build: + ```cmd npm run build ``` diff --git a/dist/js/winbox.min.js b/dist/js/winbox.min.js index 30b93e7..222ccc3 100644 --- a/dist/js/winbox.min.js +++ b/dist/js/winbox.min.js @@ -5,29 +5,29 @@ * Hosted by Nextapps GmbH * https://github.com/nextapps-de/winbox */ -(function(){'use strict';var e,aa=document.createElement("div");aa.innerHTML="
";function h(a,b,c,f){a&&a.addEventListener(b,c,f||!1)}function k(a,b){var c=window,f=l;c&&c.removeEventListener(a,b,f||!1)}function m(a,b){a.stopPropagation();b&&a.preventDefault()}function t(a,b,c){c=""+c;a["_s_"+b]!==c&&(a.style.setProperty(b,c),a["_s_"+b]=c)};/* +(function(){'use strict';var e,h=null;function k(a,b,c,f){a&&a.addEventListener(b,c,f||!1)}function l(a,b){var c=window,f=m;c&&c.removeEventListener(a,b,f||!1)}function t(a,b){a.stopPropagation();b&&a.preventDefault()}function u(a,b,c){c=""+c;a["_s_"+b]!==c&&(a.style.setProperty(b,c),a["_s_"+b]=c)};/* self.max &&*/ -var u=[],x=[],ba={capture:!0,passive:!1},l={capture:!0,passive:!0},A,ea=0,B=10,E,F,fa,J,K,ha; -function P(a,b){if(!(this instanceof P))return new P(a);A||ia();if(a){if(b){var c=a;a=b}if("string"===typeof a)c=a;else{var f=a.id;var d=a.index;var n=a.root;var p=a.template;c=c||a.title;var v=a.icon;var L=a.mount;var Q=a.html;var g=a.url;var q=a.width;var r=a.height;var w=a.minwidth;var C=a.minheight;var y=a.maxwidth;var z=a.maxheight;var ca=a.autosize;var D=a.overflow;var G=a.min;var H=a.max;var I=a.hidden;var da=a.modal;var W=a.x||(da?"center":0);var X=a.y||(da?"center":0);var M=a.top;var N=a.left; -var R=a.bottom;var S=a.right;var ja=a.background;var O=a.border;var T=a.header;var Y=a["class"];var ka=a.oncreate;var pa=a.onclose;var qa=a.onfocus;var ra=a.onblur;var sa=a.onmove;var ta=a.onresize;var ua=a.onfullscreen;var va=a.onmaximize;var wa=a.onminimize;var xa=a.onrestore;var ya=a.onhide;var za=a.onshow;var Aa=a.onload}}this.g=(p||aa).cloneNode(!0);this.g.id=this.id=f||"winbox-"+ ++ea;this.g.className="winbox"+(Y?" "+("string"===typeof Y?Y:Y.join(" ")):"")+(da?" modal":"");this.g.winbox=this; -this.window=this.g;this.body=this.g.getElementsByClassName("wb-body")[0];this.h=T||35;x.push(this);ja&&this.setBackground(ja);O?t(this.body,"margin",O+(isNaN(O)?"":"px")):O=0;T&&(b=this.g.getElementsByClassName("wb-header")[0],t(b,"height",T+"px"),t(b,"line-height",T+"px"),t(this.body,"top",T+"px"));c&&this.setTitle(c);v&&this.setIcon(v);L?this.mount(L):Q?this.body.innerHTML=Q:g&&this.setUrl(g,Aa);M=M?U(M,K):0;R=R?U(R,K):0;N=N?U(N,J):0;S=S?U(S,J):0;c=J-N-S;v=K-M-R;y=y?U(y,c):c;z=z?U(z,v):v;w=w?U(w, -y):150;C=C?U(C,z):this.h;ca?((n||A).appendChild(this.body),q=Math.max(Math.min(this.body.clientWidth+2*O+1,y),w),r=Math.max(Math.min(this.body.clientHeight+this.h+O+1,z),C),this.g.appendChild(this.body)):(q=q?U(q,y):Math.max(y/2,w)|0,r=r?U(r,z):Math.max(z/2,C)|0);W=W?U(W,c,q):N;X=X?U(X,v,r):M;this.x=W;this.y=X;this.width=q;this.height=r;this.s=w;this.o=C;this.m=y;this.l=z;this.top=M;this.right=S;this.bottom=R;this.left=N;this.index=d;this.j=D;this.focused=this.hidden=this.full=this.max=this.min=!1; -this.onclose=pa;this.onfocus=qa;this.onblur=ra;this.onmove=sa;this.onresize=ta;this.onfullscreen=ua;this.onmaximize=va;this.onminimize=wa;this.onrestore=xa;this.onhide=ya;this.onshow=za;I?this.hide():this.focus();if(d||0===d)this.index=d,t(this.g,"z-index",d),d>B&&(B=d);H?this.maximize():G?this.minimize():this.resize().move();la(this);(n||A).appendChild(this.g);ka&&ka.call(this,a)}P["new"]=function(a){return new P(a)};P.stack=function(){return x}; -function U(a,b,c){"string"===typeof a&&("center"===a?a=(b-c)/2+.5|0:"right"===a||"bottom"===a?a=b-c:(c=parseFloat(a),a="%"===(""+c!==a&&a.substring((""+c).length))?b/100*c+.5|0:c));return a} -function ia(){A=document.body;A[F="requestFullscreen"]||A[F="msRequestFullscreen"]||A[F="webkitRequestFullscreen"]||A[F="mozRequestFullscreen"]||(F="");fa=F&&F.replace("request","exit").replace("mozRequest","mozCancel").replace("Request","Exit");h(window,"resize",function(){ma();na()});h(A,"mousedown",function(){ha=!1},!0);h(A,"mousedown",function(){if(!ha){var a=x.length;if(a)for(--a;0<=a;a--){var b=x[a];if(b.focused){b.blur();break}}}});ma()} -function la(a){V(a,"drag");V(a,"n");V(a,"s");V(a,"w");V(a,"e");V(a,"nw");V(a,"ne");V(a,"se");V(a,"sw");h(a.g.getElementsByClassName("wb-min")[0],"click",function(b){m(b);a.min?a.restore().focus():a.minimize()});h(a.g.getElementsByClassName("wb-max")[0],"click",function(b){m(b);a.max?a.restore().focus():a.maximize().focus()});F?h(a.g.getElementsByClassName("wb-full")[0],"click",function(b){m(b);a.fullscreen().focus()}):a.addClass("no-full");h(a.g.getElementsByClassName("wb-close")[0],"click",function(b){m(b); -a.close()||(a=null)});h(a.g,"mousedown",function(){ha=!0},!0);h(a.body,"mousedown",function(){a.focus()},!0)}function Z(a){u.splice(u.indexOf(a),1);na();a.removeClass("min");a.min=!1;a.g.title=""}function na(){for(var a=u.length,b={},c={},f=0,d;fr){a.max?a.restore():a.maximize();return}}}a.min||(A.classList.add("wb-lock"),(p=g.touches)&&(p=p[0])?(g=p,h(window,"touchmove",f,l),h(window,"touchend",d,l)):(h(window,"mousemove",f,l),h(window,"mouseup",d,l)),v=g.pageX,L=g.pageY)}function f(g){m(g);p&&(g=g.touches[0]);var q=g.pageX;g=g.pageY;var r=q-v,w=g-L,C=a.width,y=a.height,z=a.x, -ca=a.y,D;if("drag"===b){if(a.g.classList.contains("no-move"))return;a.x+=r;a.y+=w;var G=D=1}else{if("e"===b||"se"===b||"ne"===b){a.width+=r;var H=1}else if("w"===b||"sw"===b||"nw"===b)a.x+=r,a.width-=r,G=H=1;if("s"===b||"se"===b||"sw"===b){a.height+=w;var I=1}else if("n"===b||"ne"===b||"nw"===b)a.y+=w,a.height-=w,D=I=1}H&&(a.width=Math.max(Math.min(a.width,a.m,J-a.x-a.right),a.s),H=a.width!==C);I&&(a.height=Math.max(Math.min(a.height,a.l,K-a.y-a.bottom),a.o),I=a.height!==y);(H||I)&&a.resize();G&& -(a.max&&(a.x=(qJ/3*2?J-a.width-a.right:J/2-a.width/2)+r),a.x=Math.max(Math.min(a.x,a.j?J-30:J-a.width-a.right),a.j?30-a.width:a.left),G=a.x!==z);D&&(a.max&&(a.y=a.top+w),a.y=Math.max(Math.min(a.y,a.j?K-a.h:K-a.height-a.bottom),a.top),D=a.y!==ca);if(G||D)a.max&&a.restore(),a.move();if(H||G)v=q;if(I||D)L=g}function d(g){m(g);A.classList.remove("wb-lock");p?(k("touchmove",f),k("touchend",d)):(k("mousemove",f),k("mouseup",d))}var n=a.g.getElementsByClassName("wb-"+b)[0];if(n){var p,v,L, -Q=0;h(n,"mousedown",c,ba);h(n,"touchstart",c,ba)}}function ma(){var a=document.documentElement;J=a.clientWidth;K=a.clientHeight}e=P.prototype;e.mount=function(a){this.unmount();a.i||(a.i=a.parentNode);this.body.textContent="";this.body.appendChild(a);return this};e.unmount=function(a){var b=this.body.firstChild;if(b){var c=a||b.i;c&&c.appendChild(b);b.i=a}return this}; -e.setTitle=function(a){var b=this.g.getElementsByClassName("wb-title")[0];a=this.title=a;var c=b.firstChild;c?c.nodeValue=a:b.textContent=a;return this};e.setIcon=function(a){var b=this.g.getElementsByClassName("wb-icon")[0];t(b,"background-image","url("+a+")");t(b,"display","inline-block");return this};e.setBackground=function(a){t(this.g,"background",a);return this}; -e.setUrl=function(a,b){var c=this.body.firstChild;c&&"iframe"===c.tagName.toLowerCase()?c.src=a:(this.body.innerHTML='',b&&(this.body.firstChild.onload=b));return this};e.focus=function(a){if(!1===a)return this.blur();if(!this.focused){a=x.length;if(1
"); +this.g=(p||h).cloneNode(!0);this.g.id=this.id=f||"winbox-"+ ++ba;this.g.className="winbox"+(Z?" "+("string"===typeof Z?Z:Z.join(" ")):"")+(ea?" modal":"");this.g.winbox=this;this.window=this.g;this.body=this.g.getElementsByClassName("wb-body")[0];this.h=T||35;A.push(this);ja&&this.setBackground(ja);O?u(this.body,"margin",O+(isNaN(O)?"":"px")):O=0;T&&(b=this.g.getElementsByClassName("wb-header")[0],u(b,"height",T+"px"),u(b,"line-height",T+"px"),u(this.body,"top",T+"px"));c&&this.setTitle(c);v&&this.setIcon(v); +L?this.mount(L):Q?this.body.innerHTML=Q:g&&this.setUrl(g,Aa);M=M?V(M,P):0;R=R?V(R,P):0;N=N?V(N,K):0;S=S?V(S,K):0;c=K-N-S;v=P-M-R;y=y?V(y,c):c;z=z?V(z,v):v;w=w?V(w,y):150;C=C?V(C,z):this.h;da?((n||B).appendChild(this.body),q=Math.max(Math.min(this.body.clientWidth+2*O+1,y),w),r=Math.max(Math.min(this.body.clientHeight+this.h+O+1,z),C),this.g.appendChild(this.body)):(q=q?V(q,y):Math.max(y/2,w)|0,r=r?V(r,z):Math.max(z/2,C)|0);X=X?V(X,c,q):N;Y=Y?V(Y,v,r):M;this.x=X;this.y=Y;this.width=q;this.height=r; +this.s=w;this.o=C;this.m=y;this.l=z;this.top=M;this.right=S;this.bottom=R;this.left=N;this.index=d;this.j=D;this.focused=this.hidden=this.full=this.max=this.min=!1;this.onclose=pa;this.onfocus=qa;this.onblur=ra;this.onmove=sa;this.onresize=ta;this.onfullscreen=ua;this.onmaximize=va;this.onminimize=wa;this.onrestore=xa;this.onhide=ya;this.onshow=za;I?this.hide():this.focus();if(d||0===d)this.index=d,u(this.g,"z-index",d),d>E&&(E=d);H?this.maximize():G?this.minimize():this.resize().move();ia(this); +(n||B).appendChild(this.g);ka&&ka.call(this,a)}U["new"]=function(a){return new U(a)};U.stack=function(){return A};function V(a,b,c){"string"===typeof a&&("center"===a?a=(b-c)/2+.5|0:"right"===a||"bottom"===a?a=b-c:(c=parseFloat(a),a="%"===(""+c!==a&&a.substring((""+c).length))?b/100*c+.5|0:c));return a} +function ha(){B=document.body;B[J="requestFullscreen"]||B[J="msRequestFullscreen"]||B[J="webkitRequestFullscreen"]||B[J="mozRequestFullscreen"]||(J="");ca=J&&J.replace("request","exit").replace("mozRequest","mozCancel").replace("Request","Exit");k(window,"resize",function(){la();ma()});k(B,"mousedown",function(){fa=!1},!0);k(B,"mousedown",function(){if(!fa){var a=A.length;if(a)for(--a;0<=a;a--){var b=A[a];if(b.focused){b.blur();break}}}});la()} +function ia(a){W(a,"drag");W(a,"n");W(a,"s");W(a,"w");W(a,"e");W(a,"nw");W(a,"ne");W(a,"se");W(a,"sw");k(a.g.getElementsByClassName("wb-min")[0],"click",function(b){t(b);a.min?a.restore().focus():a.minimize()});k(a.g.getElementsByClassName("wb-max")[0],"click",function(b){t(b);a.max?a.restore().focus():a.maximize().focus()});J?k(a.g.getElementsByClassName("wb-full")[0],"click",function(b){t(b);a.fullscreen().focus()}):a.addClass("no-full");k(a.g.getElementsByClassName("wb-close")[0],"click",function(b){t(b); +a.close()||(a=null)});k(a.g,"mousedown",function(){fa=!0},!0);k(a.body,"mousedown",function(){a.focus()},!0)}function na(a){x.splice(x.indexOf(a),1);ma();a.removeClass("min");a.min=!1;a.g.title=""}function ma(){for(var a=x.length,b={},c={},f=0,d;fr){a.max?a.restore():a.maximize();return}}}a.min||(B.classList.add("wb-lock"),(p=g.touches)&&(p=p[0])?(g=p,k(window,"touchmove",f,m),k(window,"touchend",d,m)):(k(window,"mousemove",f,m),k(window,"mouseup",d,m)),v=g.pageX,L=g.pageY)}function f(g){t(g);p&&(g=g.touches[0]);var q=g.pageX;g=g.pageY;var r=q-v,w=g-L,C=a.width,y=a.height,z=a.x, +da=a.y,D;if("drag"===b){if(a.g.classList.contains("no-move"))return;a.x+=r;a.y+=w;var G=D=1}else{if("e"===b||"se"===b||"ne"===b){a.width+=r;var H=1}else if("w"===b||"sw"===b||"nw"===b)a.x+=r,a.width-=r,G=H=1;if("s"===b||"se"===b||"sw"===b){a.height+=w;var I=1}else if("n"===b||"ne"===b||"nw"===b)a.y+=w,a.height-=w,D=I=1}H&&(a.width=Math.max(Math.min(a.width,a.m,K-a.x-a.right),a.s),H=a.width!==C);I&&(a.height=Math.max(Math.min(a.height,a.l,P-a.y-a.bottom),a.o),I=a.height!==y);(H||I)&&a.resize();G&& +(a.max&&(a.x=(qK/3*2?K-a.width-a.right:K/2-a.width/2)+r),a.x=Math.max(Math.min(a.x,a.j?K-30:K-a.width-a.right),a.j?30-a.width:a.left),G=a.x!==z);D&&(a.max&&(a.y=a.top+w),a.y=Math.max(Math.min(a.y,a.j?P-a.h:P-a.height-a.bottom),a.top),D=a.y!==da);if(G||D)a.max&&a.restore(),a.move();if(H||G)v=q;if(I||D)L=g}function d(g){t(g);B.classList.remove("wb-lock");p?(l("touchmove",f),l("touchend",d)):(l("mousemove",f),l("mouseup",d))}var n=a.g.getElementsByClassName("wb-"+b)[0];if(n){var p,v,L, +Q=0;k(n,"mousedown",c,aa);k(n,"touchstart",c,aa)}}function la(){var a=document.documentElement;K=a.clientWidth;P=a.clientHeight}e=U.prototype;e.mount=function(a){this.unmount();a.i||(a.i=a.parentNode);this.body.textContent="";this.body.appendChild(a);return this};e.unmount=function(a){var b=this.body.firstChild;if(b){var c=a||b.i;c&&c.appendChild(b);b.i=a}return this}; +e.setTitle=function(a){var b=this.g.getElementsByClassName("wb-title")[0];a=this.title=a;var c=b.firstChild;c?c.nodeValue=a:b.textContent=a;return this};e.setIcon=function(a){var b=this.g.getElementsByClassName("wb-icon")[0];u(b,"background-image","url("+a+")");u(b,"display","inline-block");return this};e.setBackground=function(a){u(this.g,"background",a);return this}; +e.setUrl=function(a,b){var c=this.body.firstChild;c&&"iframe"===c.tagName.toLowerCase()?c.src=a:(this.body.innerHTML='',b&&(this.body.firstChild.onload=b));return this};e.focus=function(a){if(!1===a)return this.blur();if(!this.focused){a=A.length;if(1 + - - - - - + + + + + WinBox.js – Modern HTML5 Window Manager - + - - + + - - - - - - - - - - - - - - - - - - -
-
+ + + + + + + + + + + + + + + + + + +
+
Goto Github -
-
-

WinBox.js: HTML5 Window Manager for the Web.

-

WinBox is a modern HTML5 window manager for the web. Lightweight, outstanding performance, no dependencies, fully customizable, free and open source!

-
-
Show Example
-
- -
-

- Please feel free to support me by making a personal donation which helps me a lot to keep this project alive and also to providing all the contribution to keep WinBox.js on a professional top-end level. -

- - Donate using Open Collective - - - Donate using Github Sponsors - - - Donate using Liberapay - - - Donate using Patreon - - - Donate using PayPal - - - Donate using Bountysource - -

- Thanks a lot, - Thomas (ts-thomas) +

+
+

+ WinBox.js: HTML5 Window Manager for the Web. +

+

+ WinBox is a modern HTML5 window manager for the web. Lightweight, + outstanding performance, no dependencies, fully customizable, free and + open source! +

+
+
+ Show Example +
+
+ +
+

+ Please feel free to support me by making a personal donation which + helps me a lot to keep this project alive and also to providing all + the contribution to keep WinBox.js on a professional top-end level. +

+ + Donate using Open Collective + + + Donate using Github Sponsors + + + Donate using Liberapay + + + Donate using Patreon + + + Donate using PayPal + + + Donate using Bountysource + +

+ Thanks a lot, Thomas (ts-thomas)

-
-
+
+
Load Library (Bundle) -

-
<head>
+        

+
<head>
     <script src="dist/winbox.bundle.min.js"></script>
 </head>
-
+
Load Library (Non-Bundle) -

+

<head>
     <link rel="stylesheet" href="dist/css/winbox.min.css">
     <script src="dist/js/winbox.min.js"></script>
 </head>
-
-
+
+
Class Constructor -

+

WinBox(title, options<key: value>)
-
+
-
-
-
You can open the browser dev tools and copy and paste the js-code blocks into the console and play with them.
+
+
+
+ You can open the browser dev tools and copy and paste the js-code + blocks into the console and play with them. +
-
-
+
+
Basic Window -

+

new WinBox("Basic Window");
Run Code
-

+

-
-
+
+
Custom Root -

+

new WinBox("Custom Root", {
     root: document.querySelector("main")
 });
Run Code
-

+

-
-
+
+
Custom Border -

+

new WinBox("Custom Border", {
     border: "0.3em"
 });
Run Code
-

+

-
-
+
+
Custom Color -

+

new WinBox({
     title: "Custom Color",
     background: "#ff005d",
@@ -244,12 +341,12 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin });

Run Code
-

+

-
-
+
+
Custom Icon (Titlebar) -

+

new WinBox({
     title: "Custom Icon",
     icon: "demo/wikipedia.svg",
@@ -257,12 +354,12 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin });

Run Code
-

+

-
-
+
+
Limit Viewport -

+

new WinBox("Limit Viewport", {
     top: 50,
     right: 50,
@@ -270,13 +367,15 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin left: 50 });

-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Splitscreen -

+

new WinBox("Splitscreen (Left)", {
     right: "50%",
     max: true
@@ -287,13 +386,15 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin max: true });

-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Custom Position / Size -

+

new WinBox({
     title: "Custom Position / Size",
     x: "center",
@@ -302,36 +403,40 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin height: "50%" });

-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Modal Window -

+

new WinBox("Modal Window", {
     modal: true
 });
Run Code
-

+

-
-
+
+
Set innerHTML -

+

new WinBox({
     title: "Set innerHTML",
     html: "<h1>Lorem Ipsum</h1>"
 });
-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Mount DOM (Cloned) -

+

<div id="backstore" style="display: none">
     <div id="content">
@@ -347,35 +452,39 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin .cloneNode(true) });

-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Mount DOM (Singleton) + Auto-Unmount -

+

new WinBox("Mount DOM", {
     mount: document.getElementById("content")
 });
-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Open URI / URL -

+

new WinBox("WinBox.js", {
     url: "https://nextapps-de.github.io/winbox/"
 });
Run Code
-

+

-
-
+
+
All Options -

+

new WinBox({
     // configuration:
     index: 1,
@@ -461,13 +570,15 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin } });

-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Control Programmatically -

+

<div id="controls">
     <button onclick="buttons.minimize()">Minimize (Toggle)</button>
     <button onclick="buttons.maximize()">Maximize (Toggle)</button>
@@ -478,8 +589,8 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin <button onclick="buttons.color()">Set Color</button> <button onclick="buttons.close()">Close</button> </div>

-
-
var winbox = new WinBox("Controls", {
+        
+
var winbox = new WinBox("Controls", {
     mount: document.getElementById("controls"),
     border: 4,
     onclose: function(force){
@@ -534,19 +645,21 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin } };

-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Window Boilerplate -

- WinBox Boilerplate +

+ WinBox Boilerplate -
-
+
+
Custom Styles (Global) -

+

.winbox {
     background: linear-gradient(90deg, #ff00f0, #0050ff);
     border-radius: 12px 12px 0 0;
@@ -572,7 +685,7 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin
Customize Control Icons -

+

.wb-control * {
     opacity: 0.65;
 }
@@ -600,7 +713,7 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin
Custom Scrollbars -

+

.wb-body::-webkit-scrollbar {
     width: 12px;
 }
@@ -623,13 +736,15 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin .cloneNode(true) });

-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Custom Styles By Classname -

+

.winbox.my-theme{
     background: #fff;
 }
@@ -650,13 +765,15 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin .cloneNode(true) });

-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Use Theme -

+

<head>
 <link rel="stylesheet" href="dist/css/winbox.min.css">
 <link rel="stylesheet" href="dist/css/themes/modern.min.css">
@@ -669,13 +786,15 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin .cloneNode(true) });

-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Custom Controls -

+

.wb-like {
     background-size: 20px auto;
 }
@@ -697,13 +816,15 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin } });

-
Run Code
-

+
+ Run Code +
+

-
-
+
+
Custom Template (Layout) -

+

.wb-custom {
     background-image: url(demo/icon-github.svg);
     background-size: 17px auto;
@@ -725,289 +846,291 @@ 

WinBox is a modern HTML5 window manager for the web. Lightweight, outstandin new WinBox("Custom Template", { template });

-
Run Code
-

- -

-
- - - + - + hljs.highlightAll(); + + diff --git a/package.json b/package.json index f3ed2fa..2316dbb 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,9 @@ "build:bundle": "node task/build --bundle", "build:svg": "node task/svgo", "build": "npm run clean && npm run copy && npm run build:svg && npm run build:css && npm run build:css:bundle && npm run build:css:theme-modern && npm run build:css:theme-white && node task/bundle --style && npm run build:js && npm run build:bundle && echo Build Complete. && exit 0", - "server": "node task/server.js" + "server": "node task/server.js", + "format": "prettier --write .", + "lint": "prettier --check . && eslint src/" }, "files": [ "dist/", @@ -50,8 +52,8 @@ "LICENSE" ], "readme": "README.md", - "dependencies": {}, "devDependencies": { + "@eslint/js": "^9.9.0", "base64-img": "^1.0.4", "cpx": "^1.5.0", "csso": "^5.0.4", @@ -60,6 +62,7 @@ "less": "^4.1.3", "less-plugin-autoprefix": "^2.0.0", "less-plugin-clean-css": "^1.5.1", + "prettier": "3.3.3", "svgo": "^2.8.0", "web-servo": "^0.5.1" } From d98f1d282e15989b9390b9d4fbe74092d764e7e0 Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 13:34:19 +0200 Subject: [PATCH 02/14] chore(cs): remove ci caching --- .github/workflows/prs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml index 4fc89e0..09d11da 100644 --- a/.github/workflows/prs.yml +++ b/.github/workflows/prs.yml @@ -11,7 +11,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 21 - cache: "npm" - run: npm ci - name: Run lint run: npm run lint From 3cd4094098b5467e97a29babdd19f2585b033fff Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 13:34:44 +0200 Subject: [PATCH 03/14] chore(cs): run prettier --- demo/style.css | 364 +++---- src/css/themes/modern.less | 3 +- src/css/themes/white.less | 2 +- src/css/winbox.css | 14 +- src/css/winbox.less | 42 +- src/js/helper.js | 88 +- src/js/template.js | 64 +- src/js/winbox.js | 1948 +++++++++++++++++------------------- task/build.js | 161 +-- task/bundle.js | 85 +- task/clean.js | 32 +- task/server.js | 75 +- task/svgo.js | 95 +- 13 files changed, 1436 insertions(+), 1537 deletions(-) diff --git a/demo/style.css b/demo/style.css index 68e8b6c..8cbaa1a 100644 --- a/demo/style.css +++ b/demo/style.css @@ -1,216 +1,230 @@ -*{ - box-sizing: border-box; - touch-action: manipulation; -} -html, body{ - margin: 0; - padding: 0; - border: 0; - background: linear-gradient(135deg, #0d1117, #131820); -} -body{ - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - font-family: -apple-system, BlinkMacSystemFont, Helvetica, Arial, "Open Sans", OpenSans, Roboto, Segoe UI, sans-serif; - text-align: center; - color: #fff; +* { + box-sizing: border-box; + touch-action: manipulation; +} +html, +body { + margin: 0; + padding: 0; + border: 0; + background: linear-gradient(135deg, #0d1117, #131820); +} +body { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + font-family: + -apple-system, + BlinkMacSystemFont, + Helvetica, + Arial, + "Open Sans", + OpenSans, + Roboto, + Segoe UI, + sans-serif; + text-align: center; + color: #fff; - -webkit-tap-highlight-color: transparent; - -webkit-text-size-adjust: 100%; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + -webkit-tap-highlight-color: transparent; + -webkit-text-size-adjust: 100%; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; - overflow: hidden; -} -main{ - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - overflow-x: hidden; - overflow-y: scroll; - -webkit-overflow-scrolling: touch; - overflow-scrolling: touch; - /*content-visibility: auto;*/ -} -header{ - position: sticky; - top: 0; - width: 100%; - height: 50px; - background: linear-gradient(to right, rgba(13, 17, 22, 0.94), rgba(17, 21, 28, 0.94)); - padding: 15px 0; - border-bottom: 1px solid #181f2a; - z-index: 2; -} -.wrapper{ - position: relative; - display: block; - margin: auto; - max-width: 600px; - text-align: left; - padding: 1.2em; - padding-top: 40px; - z-index: 1; -} -a{ - display: inline-block; - color: #3374ff; - text-decoration: none; - font-weight: 600; -} -b{ - display: block; - padding: 10px; -} -button{ - padding: 5px 10px; - margin-bottom: 5px; -} -h1{ - margin: 0; - padding: 0; -} -h1 img{ - width: 100%; - height: auto; -} -h2{ - font-size: 20px; - font-weight: 400; - line-height: 27px; -} -h3{ - font-size: 24px; - font-weight: 600; - line-height: 32px; - margin:0; -} -hr{ - border: 0; - border-bottom: 1px solid #1c2431; - margin-bottom: 20px; - padding-top: 20px; + overflow: hidden; +} +main { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow-x: hidden; + overflow-y: scroll; + -webkit-overflow-scrolling: touch; + overflow-scrolling: touch; + /*content-visibility: auto;*/ +} +header { + position: sticky; + top: 0; + width: 100%; + height: 50px; + background: linear-gradient( + to right, + rgba(13, 17, 22, 0.94), + rgba(17, 21, 28, 0.94) + ); + padding: 15px 0; + border-bottom: 1px solid #181f2a; + z-index: 2; +} +.wrapper { + position: relative; + display: block; + margin: auto; + max-width: 600px; + text-align: left; + padding: 1.2em; + padding-top: 40px; + z-index: 1; +} +a { + display: inline-block; + color: #3374ff; + text-decoration: none; + font-weight: 600; +} +b { + display: block; + padding: 10px; +} +button { + padding: 5px 10px; + margin-bottom: 5px; +} +h1 { + margin: 0; + padding: 0; +} +h1 img { + width: 100%; + height: auto; +} +h2 { + font-size: 20px; + font-weight: 400; + line-height: 27px; +} +h3 { + font-size: 24px; + font-weight: 600; + line-height: 32px; + margin: 0; +} +hr { + border: 0; + border-bottom: 1px solid #1c2431; + margin-bottom: 20px; + padding-top: 20px; } .button { - display: inline-block; - background-color: #0050ff; - color: #fff; - width: auto; - border-radius: 10px; - padding: 15px 25px; - cursor: pointer; -} -pre{ - display: inline-block; - margin: auto; - text-align: left; - width: 100%; - max-width: calc(100vw - 1.7em); - line-height: 19px; - -webkit-touch-callout: default !important; - -webkit-user-select: text !important; - -khtml-user-select: text !important; - -moz-user-select: text !important; - -ms-user-select: text !important; - user-select: text !important; -} -pre code.hljs{ - background: #181f2a; - padding: 1em 1.5em; - border-radius: 10px; - width: 100%; - max-width: calc(100vw - 1.7em); -} -.break{ - height: 18px; + display: inline-block; + background-color: #0050ff; + color: #fff; + width: auto; + border-radius: 10px; + padding: 15px 25px; + cursor: pointer; +} +pre { + display: inline-block; + margin: auto; + text-align: left; + width: 100%; + max-width: calc(100vw - 1.7em); + line-height: 19px; + -webkit-touch-callout: default !important; + -webkit-user-select: text !important; + -khtml-user-select: text !important; + -moz-user-select: text !important; + -ms-user-select: text !important; + user-select: text !important; +} +pre code.hljs { + background: #181f2a; + padding: 1em 1.5em; + border-radius: 10px; + width: 100%; + max-width: calc(100vw - 1.7em); +} +.break { + height: 18px; } @media (min-width: 600px) { + main::-webkit-scrollbar { + width: 12px; + height: 12px; + } - main::-webkit-scrollbar { - width: 12px; - height: 12px; - } + main::-webkit-scrollbar-track { + background: transparent; + } - main::-webkit-scrollbar-track { - background: transparent; - } - - main::-webkit-scrollbar-thumb { - border-radius: 10px; - background: #263040; - } + main::-webkit-scrollbar-thumb { + border-radius: 10px; + background: #263040; + } - main::-webkit-scrollbar-thumb:window-inactive { - background: #181f2a; - } + main::-webkit-scrollbar-thumb:window-inactive { + background: #181f2a; + } - main::-webkit-scrollbar-corner { - background: transparent; - } + main::-webkit-scrollbar-corner { + background: transparent; + } } @media (max-width: 600px) { - .wrapper{ - padding-top: 30px; - } - .wrapper, pre{ - display: block; - } + .wrapper { + padding-top: 30px; + } + .wrapper, + pre { + display: block; + } } @media (max-height: 550px) { - header{ - position: absolute; - } + header { + position: absolute; + } } .wb-body { - color: #000; + color: #000; } -.wb-body > *:not(iframe){ - padding: 1.2em; +.wb-body > *:not(iframe) { + padding: 1.2em; } -.winbox.iframe .wb-body > *{ - padding: 0; +.winbox.iframe .wb-body > * { + padding: 0; } -.winbox.iframe .wb-body{ - background: linear-gradient(135deg, #0d1117, #131820); +.winbox.iframe .wb-body { + background: linear-gradient(135deg, #0d1117, #131820); } -.hljs{ - color: #fff4ec; +.hljs { + color: #fff4ec; } .hljs-attr { - color:#79c0ff + color: #79c0ff; } .hljs-name, .hljs-built_in { - color: #ffa657; + color: #ffa657; } -.hljs-attribute{ - color: #e0e7ff +.hljs-attribute { + color: #e0e7ff; } .hljs-selector-class { - color: #79c0ff + color: #79c0ff; } .hljs-keyword, .hljs-selector-tag { - color: #f92672 + color: #f92672; } -.hljs-comment{ - color: #888; +.hljs-comment { + color: #888; } .hljs-bullet, @@ -219,5 +233,5 @@ pre code.hljs{ .hljs-number, .hljs-quote, .hljs-regexp { - color: #d2a8ff + color: #d2a8ff; } diff --git a/src/css/themes/modern.less b/src/css/themes/modern.less index d66c66f..0845246 100644 --- a/src/css/themes/modern.less +++ b/src/css/themes/modern.less @@ -6,7 +6,7 @@ } //&.min:not(:hover), - &:not(.min,.focus) { + &:not(.min, .focus) { background: #666; } @@ -44,7 +44,6 @@ } .wb-body { - & { /* width of window border: */ margin: 4px; diff --git a/src/css/themes/white.less b/src/css/themes/white.less index 23a0d10..30835e6 100644 --- a/src/css/themes/white.less +++ b/src/css/themes/white.less @@ -10,4 +10,4 @@ .wb-control { filter: invert(1); } -} \ No newline at end of file +} diff --git a/src/css/winbox.css b/src/css/winbox.css index 9660acf..99d0441 100644 --- a/src/css/winbox.css +++ b/src/css/winbox.css @@ -3,9 +3,15 @@ left: 0; top: 0; background: #0050ff; - box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22); + box-shadow: + 0 14px 28px rgba(0, 0, 0, 0.25), + 0 10px 10px rgba(0, 0, 0, 0.22); /* using transform make contents blur when applied and requires more gpu memory */ - transition: width 0.3s, height 0.3s, left 0.3s, top 0.3s; + transition: + width 0.3s, + height 0.3s, + left 0.3s, + top 0.3s; transition-timing-function: cubic-bezier(0.3, 1, 0.3, 1); /* contain "strict" does not make overflow contents selectable */ contain: layout size; @@ -236,7 +242,7 @@ body.wb-lock iframe { pointer-events: none; } .winbox.modal:before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; @@ -246,7 +252,7 @@ body.wb-lock iframe { border-radius: inherit; } .winbox.modal:after { - content: ''; + content: ""; position: absolute; top: -50vh; left: -50vw; diff --git a/src/css/winbox.less b/src/css/winbox.less index 897b765..67630d7 100644 --- a/src/css/winbox.less +++ b/src/css/winbox.less @@ -6,10 +6,16 @@ left: 0; top: 0; background: #0050ff; - box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22); + box-shadow: + 0 14px 28px rgba(0, 0, 0, 0.25), + 0 10px 10px rgba(0, 0, 0, 0.22); /* using transform make contents blur when applied and requires more gpu memory */ //transition: width .3s, height .3s, transform .3s; - transition: width .3s, height .3s, left .3s, top .3s; + transition: + width 0.3s, + height 0.3s, + left 0.3s, + top 0.3s; transition-timing-function: cubic-bezier(0.3, 1, 0.3, 1); //transform-origin: bottom center; /* contain "strict" does not make overflow contents selectable */ @@ -60,14 +66,14 @@ body > .wb-body{ } */ -.wb-drag{ +.wb-drag { height: 100%; padding-left: 10px; cursor: move; } -.wb-title{ - font-family : Arial, sans-serif; +.wb-title { + font-family: Arial, sans-serif; font-size: 14px; white-space: nowrap; overflow: hidden; @@ -165,7 +171,7 @@ body > .wb-body{ z-index: 2; } -.wb-control{ +.wb-control { float: right; height: 100%; max-width: 100%; @@ -229,7 +235,7 @@ body > .wb-body{ .winbox.max .wb-body ~ div { pointer-events: none; } -.winbox.max .wb-drag{ +.winbox.max .wb-drag { cursor: default; } @@ -241,7 +247,7 @@ body > .wb-body{ .wb-drag { cursor: default; } - .wb-body > *{ + .wb-body > * { display: none; } } @@ -259,14 +265,14 @@ body > .wb-body{ margin: 0 !important; } -.winbox iframe{ +.winbox iframe { position: absolute; width: 100%; height: 100%; border: 0; } -body.wb-lock .winbox{ +body.wb-lock .winbox { will-change: left, top, width, height; transition: none; } @@ -274,9 +280,9 @@ body.wb-lock iframe { pointer-events: none; } -.winbox.modal{ - &:before{ - content: ''; +.winbox.modal { + &:before { + content: ""; position: absolute; top: 0; left: 0; @@ -285,8 +291,8 @@ body.wb-lock iframe { background: inherit; border-radius: inherit; } - &:after{ - content: ''; + &:after { + content: ""; position: absolute; top: -50vh; left: -50vw; @@ -298,16 +304,16 @@ body.wb-lock iframe { } .wb-min, .wb-max, - .wb-full{ + .wb-full { display: none; } } @keyframes wb-fade-in { - 0%{ + 0% { opacity: 0; } - 100%{ + 100% { opacity: 0.85; } } diff --git a/src/js/helper.js b/src/js/helper.js index d664789..772761d 100644 --- a/src/js/helper.js +++ b/src/js/helper.js @@ -5,9 +5,8 @@ * @param {AddEventListenerOptions|boolean=} opt */ -export function addListener(node, event, fn, opt){ - - node && node.addEventListener(event, fn, opt || false); +export function addListener(node, event, fn, opt) { + node && node.addEventListener(event, fn, opt || false); } /** @@ -17,9 +16,8 @@ export function addListener(node, event, fn, opt){ * @param {AddEventListenerOptions|boolean=} opt */ -export function removeListener(node, event, fn, opt){ - - node && node.removeEventListener(event, fn, opt || false); +export function removeListener(node, event, fn, opt) { + node && node.removeEventListener(event, fn, opt || false); } /** @@ -27,68 +25,56 @@ export function removeListener(node, event, fn, opt){ * @param {boolean=} prevent */ -export function preventEvent(event, prevent){ +export function preventEvent(event, prevent) { + event.stopPropagation(); + prevent && /*event.cancelable &&*/ event.preventDefault(); - event.stopPropagation(); - prevent && /*event.cancelable &&*/ event.preventDefault(); - - //event.stopImmediatePropagation(); - //event.returnValue = false; + //event.stopImmediatePropagation(); + //event.returnValue = false; } -export function getByClass(root, name){ - - return root.getElementsByClassName(name)[0]; +export function getByClass(root, name) { + return root.getElementsByClassName(name)[0]; } -export function addClass(node, classname){ - - node.classList.add(classname); +export function addClass(node, classname) { + node.classList.add(classname); } -export function hasClass(node, classname){ - - return node.classList.contains(classname); +export function hasClass(node, classname) { + return node.classList.contains(classname); } -export function removeClass(node, classname){ - - node.classList.remove(classname); +export function removeClass(node, classname) { + node.classList.remove(classname); } -export function setStyle(node, style, value){ +export function setStyle(node, style, value) { + value = "" + value; - value = "" + value; - - if(node["_s_" + style] !== value){ - - node.style.setProperty(style, value); - node["_s_" + style] = value; - } + if (node["_s_" + style] !== value) { + node.style.setProperty(style, value); + node["_s_" + style] = value; + } } -export function setAttribute(node, key, value){ - - value = "" + value; - - if(node["_a_" + key] !== value){ +export function setAttribute(node, key, value) { + value = "" + value; - node.setAttribute(key, value); - node["_a_" + key] = value; - } + if (node["_a_" + key] !== value) { + node.setAttribute(key, value); + node["_a_" + key] = value; + } } -export function removeAttribute(node, key){ - - if(node["_a_" + key] !== null){ - - node.removeAttribute(key); - node["_a_" + key] = null; - } +export function removeAttribute(node, key) { + if (node["_a_" + key] !== null) { + node.removeAttribute(key); + node["_a_" + key] = null; + } } -export function setText(node, value){ - - const textnode = node.firstChild; - textnode ? textnode.nodeValue = value : node.textContent = value; +export function setText(node, value) { + const textnode = node.firstChild; + textnode ? (textnode.nodeValue = value) : (node.textContent = value); } diff --git a/src/js/template.js b/src/js/template.js index 8a706d2..5038126 100644 --- a/src/js/template.js +++ b/src/js/template.js @@ -2,42 +2,38 @@ @type null|HTMLDivElement */ let template = null; -const templateHTML = ( +const templateHTML = + //'
' + - //'
' + + "
" + + "
" + + "" + + "" + + "" + + "" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
"; - '
' + - '
' + - '' + - '' + - '' + - '' + - '
' + - '
'+ - '
' + - '
' + - '
' + - '
' + + //'
' - '
' + +export default function (tpl) { + if (!template) { + template = document.createElement("div"); + template.innerHTML = templateHTML; + } - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' - - //'
' -); - -export default function(tpl){ - if (!template) { - template = document.createElement('div'); - template.innerHTML = templateHTML; - } - - return (tpl || template).cloneNode(true); + return (tpl || template).cloneNode(true); } diff --git a/src/js/winbox.js b/src/js/winbox.js index dba4963..fe059ba 100644 --- a/src/js/winbox.js +++ b/src/js/winbox.js @@ -9,7 +9,17 @@ // TODO: rename control amd state classes (min, max, modal, focus, ...) #62 import template from "./template.js"; -import { addListener, removeListener, setStyle, setText, getByClass, addClass, removeClass, hasClass, preventEvent } from "./helper.js"; +import { + addListener, + removeListener, + setStyle, + setText, + getByClass, + addClass, + removeClass, + hasClass, + preventEvent, +} from "./helper.js"; //const ios = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window["MSStream"]; @@ -17,8 +27,8 @@ const use_raf = false; const stack_min = []; const stack_win = []; // use passive for touch and mouse wheel -const eventOptions = { "capture": true, "passive": false }; -const eventOptionsPassive = { "capture": true, "passive": true }; +const eventOptions = { capture: true, passive: false }; +const eventOptionsPassive = { capture: true, passive: true }; let body; let id_counter = 0; let index_counter = 10; @@ -35,295 +45,271 @@ let window_clicked; * @this WinBox */ -function WinBox(params, _title){ - - if(!(this instanceof WinBox)) { - - return new WinBox(params); - } - - body || setup(); - - let id, - index, - root, - tpl, - title, - icon, - mount, - html, - url, - - width, - height, - minwidth, - minheight, - maxwidth, - maxheight, - autosize, - overflow, - - x, - y, - - top, - left, - bottom, - right, - - min, - max, - hidden, - modal, - - background, - border, - header, - classname, - - oncreate, - onclose, - onfocus, - onblur, - onmove, - onresize, - onfullscreen, - onmaximize, - onminimize, - onrestore, - onhide, - onshow, - onload; - - if(params){ - - if(_title){ - - title = params; - params = _title; - } - - if(typeof params === "string"){ - - title = params; - } - else{ - - id = params["id"]; - index = params["index"]; - root = params["root"]; - tpl = params["template"]; - title = title || params["title"]; - icon = params["icon"]; - mount = params["mount"]; - html = params["html"]; - url = params["url"]; - - width = params["width"]; - height = params["height"]; - minwidth = params["minwidth"]; - minheight = params["minheight"]; - maxwidth = params["maxwidth"]; - maxheight = params["maxheight"]; - autosize = params["autosize"]; - overflow = params["overflow"]; - - min = params["min"]; - max = params["max"]; - hidden = params["hidden"]; - modal = params["modal"]; - - x = params["x"] || (modal ? "center" : 0); - y = params["y"] || (modal ? "center" : 0); - - top = params["top"]; - left = params["left"]; - bottom = params["bottom"]; - right = params["right"]; - - background = params["background"]; - border = params["border"]; - header = params["header"]; - classname = params["class"]; - - oncreate = params["oncreate"]; - onclose = params["onclose"]; - onfocus = params["onfocus"]; - onblur = params["onblur"]; - onmove = params["onmove"]; - onresize = params["onresize"]; - onfullscreen = params["onfullscreen"]; - onmaximize = params["onmaximize"]; - onminimize = params["onminimize"]; - onrestore = params["onrestore"]; - onhide = params["onhide"]; - onshow = params["onshow"]; - onload = params["onload"]; - } - } - - this.dom = template(tpl); - this.dom.id = this.id = id || ("winbox-" + (++id_counter)); - this.dom.className = "winbox" + (classname ? " " + (typeof classname === "string" ? classname : classname.join(" ")) : "") + (modal ? " modal" : ""); - this.dom["winbox"] = this; - this.window = this.dom; - this.body = getByClass(this.dom, "wb-body"); - this.header = header || 35; - //this.plugins = []; - - stack_win.push(this); - - if(background){ - - this.setBackground(background); - } - - if(border){ - - setStyle(this.body, "margin", border + (isNaN(border) ? "" : "px")); - } - else{ - - border = 0; - } - - if(header){ - - const node = getByClass(this.dom, "wb-header"); - setStyle(node, "height", header + "px"); - setStyle(node, "line-height", header + "px"); - setStyle(this.body, "top", header + "px"); - } - - if(title){ - - this.setTitle(title); - } - - if(icon){ - - this.setIcon(icon); - } - - if(mount){ - - this.mount(mount); - } - else if(html){ - - this.body.innerHTML = html; - } - else if(url){ - - this.setUrl(url, onload); - } - - top = top ? parse(top, root_h) : 0; - bottom = bottom ? parse(bottom, root_h) : 0; - left = left ? parse(left, root_w) : 0; - right = right ? parse(right, root_w) : 0; - - const viewport_w = root_w - left - right; - const viewport_h = root_h - top - bottom; - - maxwidth = maxwidth ? parse(maxwidth, viewport_w) : viewport_w; - maxheight = maxheight ? parse(maxheight, viewport_h) : viewport_h; - minwidth = minwidth ? parse(minwidth, maxwidth) : 150; - minheight = minheight ? parse(minheight, maxheight) : this.header; - - if(autosize){ - - (root || body).appendChild(this.body); - - width = Math.max(Math.min(this.body.clientWidth + border * 2 + 1, maxwidth), minwidth); - height = Math.max(Math.min(this.body.clientHeight + this.header + border + 1, maxheight), minheight); - - this.dom.appendChild(this.body); +function WinBox(params, _title) { + if (!(this instanceof WinBox)) { + return new WinBox(params); + } + + body || setup(); + + let id, + index, + root, + tpl, + title, + icon, + mount, + html, + url, + width, + height, + minwidth, + minheight, + maxwidth, + maxheight, + autosize, + overflow, + x, + y, + top, + left, + bottom, + right, + min, + max, + hidden, + modal, + background, + border, + header, + classname, + oncreate, + onclose, + onfocus, + onblur, + onmove, + onresize, + onfullscreen, + onmaximize, + onminimize, + onrestore, + onhide, + onshow, + onload; + + if (params) { + if (_title) { + title = params; + params = _title; } - else{ - width = width ? parse(width, maxwidth) : Math.max(maxwidth / 2, minwidth) | 0; - height = height ? parse(height, maxheight) : Math.max(maxheight / 2, minheight) | 0; + if (typeof params === "string") { + title = params; + } else { + id = params["id"]; + index = params["index"]; + root = params["root"]; + tpl = params["template"]; + title = title || params["title"]; + icon = params["icon"]; + mount = params["mount"]; + html = params["html"]; + url = params["url"]; + + width = params["width"]; + height = params["height"]; + minwidth = params["minwidth"]; + minheight = params["minheight"]; + maxwidth = params["maxwidth"]; + maxheight = params["maxheight"]; + autosize = params["autosize"]; + overflow = params["overflow"]; + + min = params["min"]; + max = params["max"]; + hidden = params["hidden"]; + modal = params["modal"]; + + x = params["x"] || (modal ? "center" : 0); + y = params["y"] || (modal ? "center" : 0); + + top = params["top"]; + left = params["left"]; + bottom = params["bottom"]; + right = params["right"]; + + background = params["background"]; + border = params["border"]; + header = params["header"]; + classname = params["class"]; + + oncreate = params["oncreate"]; + onclose = params["onclose"]; + onfocus = params["onfocus"]; + onblur = params["onblur"]; + onmove = params["onmove"]; + onresize = params["onresize"]; + onfullscreen = params["onfullscreen"]; + onmaximize = params["onmaximize"]; + onminimize = params["onminimize"]; + onrestore = params["onrestore"]; + onhide = params["onhide"]; + onshow = params["onshow"]; + onload = params["onload"]; } + } + + this.dom = template(tpl); + this.dom.id = this.id = id || "winbox-" + ++id_counter; + this.dom.className = + "winbox" + + (classname + ? " " + (typeof classname === "string" ? classname : classname.join(" ")) + : "") + + (modal ? " modal" : ""); + this.dom["winbox"] = this; + this.window = this.dom; + this.body = getByClass(this.dom, "wb-body"); + this.header = header || 35; + //this.plugins = []; + + stack_win.push(this); + + if (background) { + this.setBackground(background); + } + + if (border) { + setStyle(this.body, "margin", border + (isNaN(border) ? "" : "px")); + } else { + border = 0; + } + + if (header) { + const node = getByClass(this.dom, "wb-header"); + setStyle(node, "height", header + "px"); + setStyle(node, "line-height", header + "px"); + setStyle(this.body, "top", header + "px"); + } + + if (title) { + this.setTitle(title); + } + + if (icon) { + this.setIcon(icon); + } + + if (mount) { + this.mount(mount); + } else if (html) { + this.body.innerHTML = html; + } else if (url) { + this.setUrl(url, onload); + } + + top = top ? parse(top, root_h) : 0; + bottom = bottom ? parse(bottom, root_h) : 0; + left = left ? parse(left, root_w) : 0; + right = right ? parse(right, root_w) : 0; + + const viewport_w = root_w - left - right; + const viewport_h = root_h - top - bottom; + + maxwidth = maxwidth ? parse(maxwidth, viewport_w) : viewport_w; + maxheight = maxheight ? parse(maxheight, viewport_h) : viewport_h; + minwidth = minwidth ? parse(minwidth, maxwidth) : 150; + minheight = minheight ? parse(minheight, maxheight) : this.header; + + if (autosize) { + (root || body).appendChild(this.body); + + width = Math.max( + Math.min(this.body.clientWidth + border * 2 + 1, maxwidth), + minwidth + ); + height = Math.max( + Math.min(this.body.clientHeight + this.header + border + 1, maxheight), + minheight + ); - x = x ? parse(x, viewport_w, width) : left; - y = y ? parse(y, viewport_h, height) : top; - - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.minwidth = minwidth; - this.minheight = minheight; - this.maxwidth = maxwidth; - this.maxheight = maxheight; - this.top = top; - this.right = right; - this.bottom = bottom; - this.left = left; + this.dom.appendChild(this.body); + } else { + width = width + ? parse(width, maxwidth) + : Math.max(maxwidth / 2, minwidth) | 0; + height = height + ? parse(height, maxheight) + : Math.max(maxheight / 2, minheight) | 0; + } + + x = x ? parse(x, viewport_w, width) : left; + y = y ? parse(y, viewport_h, height) : top; + + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.minwidth = minwidth; + this.minheight = minheight; + this.maxwidth = maxwidth; + this.maxheight = maxheight; + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + this.index = index; + this.overflow = overflow; + //this.border = border; + this.min = false; + this.max = false; + this.full = false; + this.hidden = false; + this.focused = false; + + this.onclose = onclose; + this.onfocus = onfocus; + this.onblur = onblur; + this.onmove = onmove; + this.onresize = onresize; + this.onfullscreen = onfullscreen; + this.onmaximize = onmaximize; + this.onminimize = onminimize; + this.onrestore = onrestore; + this.onhide = onhide; + this.onshow = onshow; + + if (hidden) { + this.hide(); + } else { + this.focus(); + } + + if (index || index === 0) { this.index = index; - this.overflow = overflow; - //this.border = border; - this.min = false; - this.max = false; - this.full = false; - this.hidden = false; - this.focused = false; - - this.onclose = onclose; - this.onfocus = onfocus; - this.onblur = onblur; - this.onmove = onmove; - this.onresize = onresize; - this.onfullscreen = onfullscreen; - this.onmaximize = onmaximize; - this.onminimize = onminimize; - this.onrestore = onrestore; - this.onhide = onhide; - this.onshow = onshow; - - if(hidden){ - - this.hide(); - } - else{ - - this.focus(); - } - - if(index || (index === 0)){ - - this.index = index; - setStyle(this.dom, "z-index", index); - if(index > index_counter) index_counter = index; - } - - if(max){ - - this.maximize(); - } - else if(min){ - - this.minimize(); - } - else{ - - this.resize().move(); - } - - register(this); - (root || body).appendChild(this.dom); - oncreate && oncreate.call(this, params); + setStyle(this.dom, "z-index", index); + if (index > index_counter) index_counter = index; + } + + if (max) { + this.maximize(); + } else if (min) { + this.minimize(); + } else { + this.resize().move(); + } + + register(this); + (root || body).appendChild(this.dom); + oncreate && oncreate.call(this, params); } -WinBox["new"] = function(params){ - - return new WinBox(params); +WinBox["new"] = function (params) { + return new WinBox(params); }; -WinBox["stack"] = function(){ - - return stack_win; +WinBox["stack"] = function () { + return stack_win; }; export default WinBox; @@ -335,204 +321,189 @@ export default WinBox; * @return number */ -function parse(num, base, center){ - - if(typeof num === "string"){ - - if(num === "center"){ - - num = ((base - center) / 2 + 0.5) | 0; - } - else if(num === "right" || num === "bottom"){ - - num = (base - center); - } - else{ - - const value = parseFloat(num); - const unit = (("" + value) !== num) && num.substring(("" + value).length); - - if(unit === "%"){ - - num = (base / 100 * value + 0.5) | 0; - } - else{ - - num = value; - } - } +function parse(num, base, center) { + if (typeof num === "string") { + if (num === "center") { + num = ((base - center) / 2 + 0.5) | 0; + } else if (num === "right" || num === "bottom") { + num = base - center; + } else { + const value = parseFloat(num); + const unit = "" + value !== num && num.substring(("" + value).length); + + if (unit === "%") { + num = ((base / 100) * value + 0.5) | 0; + } else { + num = value; + } } + } - return num; + return num; } -function setup(){ +function setup() { + body = document.body; - body = document.body; - - body[prefix_request = "requestFullscreen"] || - body[prefix_request = "msRequestFullscreen"] || - body[prefix_request = "webkitRequestFullscreen"] || - body[prefix_request = "mozRequestFullscreen"] || + body[(prefix_request = "requestFullscreen")] || + body[(prefix_request = "msRequestFullscreen")] || + body[(prefix_request = "webkitRequestFullscreen")] || + body[(prefix_request = "mozRequestFullscreen")] || (prefix_request = ""); - prefix_exit = prefix_request && ( - - prefix_request.replace("request", "exit") - .replace("mozRequest", "mozCancel") - .replace("Request", "Exit") - ); - - addListener(window, "resize", function(){ - - init(); - update_min_stack(); - - // TODO adjust window sizes #151 - - // for(let i = 0; i < stack_win.length; i++){ - // - // stack_win[i].resize().move(); - // } - }); - - addListener(body, "mousedown", function(event){ - - window_clicked = false; - - }, true); + prefix_exit = + prefix_request && + prefix_request + .replace("request", "exit") + .replace("mozRequest", "mozCancel") + .replace("Request", "Exit"); - addListener(body, "mousedown", function(event){ - - if(!window_clicked){ - - const stack_length = stack_win.length; - - if(stack_length){ - - for(let i = stack_length - 1; i >= 0; i--){ - - const last_focus = stack_win[i]; + addListener(window, "resize", function () { + init(); + update_min_stack(); - if(last_focus.focused){ + // TODO adjust window sizes #151 - last_focus.blur(); - break; - } - } - } + // for(let i = 0; i < stack_win.length; i++){ + // + // stack_win[i].resize().move(); + // } + }); + + addListener( + body, + "mousedown", + function (event) { + window_clicked = false; + }, + true + ); + + addListener(body, "mousedown", function (event) { + if (!window_clicked) { + const stack_length = stack_win.length; + + if (stack_length) { + for (let i = stack_length - 1; i >= 0; i--) { + const last_focus = stack_win[i]; + + if (last_focus.focused) { + last_focus.blur(); + break; + } } - }); + } + } + }); - init(); + init(); } /** * @param {WinBox} self */ -function register(self){ - - addWindowListener(self, "drag"); - addWindowListener(self, "n"); - addWindowListener(self, "s"); - addWindowListener(self, "w"); - addWindowListener(self, "e"); - addWindowListener(self, "nw"); - addWindowListener(self, "ne"); - addWindowListener(self, "se"); - addWindowListener(self, "sw"); - - addListener(getByClass(self.dom, "wb-min"), "click", function(event){ - - preventEvent(event); - self.min ? self.restore().focus() : self.minimize(); - }); - - addListener(getByClass(self.dom, "wb-max"), "click", function(event){ - - preventEvent(event); - self.max ? self.restore().focus() : self.maximize().focus(); +function register(self) { + addWindowListener(self, "drag"); + addWindowListener(self, "n"); + addWindowListener(self, "s"); + addWindowListener(self, "w"); + addWindowListener(self, "e"); + addWindowListener(self, "nw"); + addWindowListener(self, "ne"); + addWindowListener(self, "se"); + addWindowListener(self, "sw"); + + addListener(getByClass(self.dom, "wb-min"), "click", function (event) { + preventEvent(event); + self.min ? self.restore().focus() : self.minimize(); + }); + + addListener(getByClass(self.dom, "wb-max"), "click", function (event) { + preventEvent(event); + self.max ? self.restore().focus() : self.maximize().focus(); + }); + + if (prefix_request) { + addListener(getByClass(self.dom, "wb-full"), "click", function (event) { + preventEvent(event); + self.fullscreen().focus(); }); - - if(prefix_request){ - - addListener(getByClass(self.dom, "wb-full"), "click", function(event){ - - preventEvent(event); - self.fullscreen().focus(); - }); - } - else{ - - self.addClass("no-full"); - } - - addListener(getByClass(self.dom, "wb-close"), "click", function(event){ - - preventEvent(event); - self.close() || (self = null); - }); - - addListener(self.dom, "mousedown", function(event){ - - window_clicked = true; - - }, true); - - addListener(self.body, "mousedown", function(event){ - - // stop propagation would disable global listeners used inside window contents - // use event bubbling for this listener to skip this handler by the other click listeners - self.focus(); - - }, true); + } else { + self.addClass("no-full"); + } + + addListener(getByClass(self.dom, "wb-close"), "click", function (event) { + preventEvent(event); + self.close() || (self = null); + }); + + addListener( + self.dom, + "mousedown", + function (event) { + window_clicked = true; + }, + true + ); + + addListener( + self.body, + "mousedown", + function (event) { + // stop propagation would disable global listeners used inside window contents + // use event bubbling for this listener to skip this handler by the other click listeners + self.focus(); + }, + true + ); } /** * @param {WinBox} self */ -function remove_min_stack(self){ - - stack_min.splice(stack_min.indexOf(self), 1); - update_min_stack(); - self.removeClass("min"); - self.min = false; - self.dom.title = ""; +function remove_min_stack(self) { + stack_min.splice(stack_min.indexOf(self), 1); + update_min_stack(); + self.removeClass("min"); + self.min = false; + self.dom.title = ""; } -function update_min_stack(){ +function update_min_stack() { + const length = stack_min.length; + const splitscreen_index = {}; + const splitscreen_length = {}; - const length = stack_min.length; - const splitscreen_index = {}; - const splitscreen_length = {}; + for (let i = 0, self, key; i < length; i++) { + self = stack_min[i]; + key = self.left + ":" + self.top; - for(let i = 0, self, key; i < length; i++){ - - self = stack_min[i]; - key = self.left + ":" + self.top; - - if(splitscreen_length[key]){ - - splitscreen_length[key]++; - } - else{ - - splitscreen_index[key] = 0; - splitscreen_length[key] = 1; - } - } - - for(let i = 0, self, key, width; i < length; i++){ - - self = stack_min[i] - key = self.left + ":" + self.top; - width = Math.min((root_w - self.left - self.right) / splitscreen_length[key], 250); - self.resize((width + 1) | 0, self.header, true) - .move((self.left + splitscreen_index[key] * width) | 0, root_h - self.bottom - self.header, true); - splitscreen_index[key]++; + if (splitscreen_length[key]) { + splitscreen_length[key]++; + } else { + splitscreen_index[key] = 0; + splitscreen_length[key] = 1; } + } + + for (let i = 0, self, key, width; i < length; i++) { + self = stack_min[i]; + key = self.left + ":" + self.top; + width = Math.min( + (root_w - self.left - self.right) / splitscreen_length[key], + 250 + ); + self + .resize((width + 1) | 0, self.header, true) + .move( + (self.left + splitscreen_index[key] * width) | 0, + root_h - self.bottom - self.header, + true + ); + splitscreen_index[key]++; + } } /** @@ -540,270 +511,263 @@ function update_min_stack(){ * @param {string} dir */ -function addWindowListener(self, dir){ - - const node = getByClass(self.dom, "wb-" + dir); - if(!node) return; +function addWindowListener(self, dir) { + const node = getByClass(self.dom, "wb-" + dir); + if (!node) return; - let touch, x, y; - let raf_timer, raf_move, raf_resize; - let dblclick_timer = 0; + let touch, x, y; + let raf_timer, raf_move, raf_resize; + let dblclick_timer = 0; - addListener(node, "mousedown", mousedown, eventOptions); - addListener(node, "touchstart", mousedown, eventOptions); + addListener(node, "mousedown", mousedown, eventOptions); + addListener(node, "touchstart", mousedown, eventOptions); - function loop(){ + function loop() { + raf_timer = requestAnimationFrame(loop); - raf_timer = requestAnimationFrame(loop); - - if(raf_resize){ - - self.resize(); - raf_resize = false; - } - - if(raf_move){ - - self.move(); - raf_move = false; - } + if (raf_resize) { + self.resize(); + raf_resize = false; } - function mousedown(event){ - - // prevent the full iteration through the fallback chain of a touch event (touch > mouse > click) - preventEvent(event, true); - //window_clicked = true; - self.focus(); - - if(dir === "drag"){ - - if(self.min){ - - self.restore(); - return; - } - - if(!self.hasClass("no-max")){ - - const now = Date.now(); - const diff = now - dblclick_timer; - - dblclick_timer = now; - - if(diff < 300){ - - self.max ? self.restore() : self.maximize(); - return; - } - } - } - - if(/*!self.max &&*/ !self.min){ - - addClass(body, "wb-lock"); - use_raf && loop(); - - if((touch = event.touches) && (touch = touch[0])){ - - event = touch; - - // TODO: fix when touch events bubbles up to the document body - //addListener(self.dom, "touchmove", preventEvent); - addListener(window, "touchmove", handler_mousemove, eventOptionsPassive); - addListener(window, "touchend", handler_mouseup, eventOptionsPassive); - } - else{ - - //addListener(this, "mouseleave", handler_mouseup); - addListener(window, "mousemove", handler_mousemove, eventOptionsPassive); - addListener(window, "mouseup", handler_mouseup, eventOptionsPassive); - } - - x = event.pageX; - y = event.pageY; - - // appearing scrollbars on the root element does not trigger "window.onresize", - // force refresh window size via init(), also force layout recalculation (layout trashing) - // it is probably very rare that the body overflow changes between window open and close - - //init(); - } + if (raf_move) { + self.move(); + raf_move = false; } + } - function handler_mousemove(event){ - - preventEvent(event); - - if(touch){ - - event = event.touches[0]; - } - - const pageX = event.pageX; - const pageY = event.pageY; - const offsetX = pageX - x; - const offsetY = pageY - y; - - const old_w = self.width; - const old_h = self.height; - const old_x = self.x; - const old_y = self.y; - - let resize_w, resize_h, move_x, move_y; - - if(dir === "drag"){ - - if(self.hasClass("no-move")) return; - - self.x += offsetX; - self.y += offsetY; - move_x = move_y = 1; - } - else{ - - if(dir === "e" || dir === "se" || dir === "ne"){ - - self.width += offsetX; - resize_w = 1; - } - else if(dir === "w" || dir === "sw" || dir === "nw"){ - - self.x += offsetX; - self.width -= offsetX; - resize_w = 1; - move_x = 1; - } - - if(dir === "s" || dir === "se" || dir === "sw"){ - - self.height += offsetY; - resize_h = 1; - } - else if(dir === "n" || dir === "ne" || dir === "nw"){ - - self.y += offsetY; - self.height -= offsetY; - resize_h = 1; - move_y = 1; - } - } - - if(resize_w){ - - self.width = Math.max(Math.min(self.width, self.maxwidth, root_w - self.x - self.right), self.minwidth); - resize_w = self.width !== old_w; - } + function mousedown(event) { + // prevent the full iteration through the fallback chain of a touch event (touch > mouse > click) + preventEvent(event, true); + //window_clicked = true; + self.focus(); - if(resize_h){ + if (dir === "drag") { + if (self.min) { + self.restore(); + return; + } - self.height = Math.max(Math.min(self.height, self.maxheight, root_h - self.y - self.bottom), self.minheight); - resize_h = self.height !== old_h; - } + if (!self.hasClass("no-max")) { + const now = Date.now(); + const diff = now - dblclick_timer; - if(resize_w || resize_h){ + dblclick_timer = now; - use_raf ? raf_resize = true : self.resize(); + if (diff < 300) { + self.max ? self.restore() : self.maximize(); + return; } + } + } - if(move_x){ - - if(self.max){ - - self.x = ( - - pageX < root_w / 3 ? - - self.left - : - pageX > root_w / 3 * 2 ? - - root_w - self.width - self.right - : - root_w / 2 - self.width / 2 - - ) + offsetX; - } + if (/*!self.max &&*/ !self.min) { + addClass(body, "wb-lock"); + use_raf && loop(); - self.x = Math.max(Math.min(self.x, self.overflow ? root_w - 30 : root_w - self.width - self.right), self.overflow ? 30 - self.width : self.left); - move_x = self.x !== old_x; - } + if ((touch = event.touches) && (touch = touch[0])) { + event = touch; - if(move_y){ - - if(self.max){ + // TODO: fix when touch events bubbles up to the document body + //addListener(self.dom, "touchmove", preventEvent); + addListener( + window, + "touchmove", + handler_mousemove, + eventOptionsPassive + ); + addListener(window, "touchend", handler_mouseup, eventOptionsPassive); + } else { + //addListener(this, "mouseleave", handler_mouseup); + addListener( + window, + "mousemove", + handler_mousemove, + eventOptionsPassive + ); + addListener(window, "mouseup", handler_mouseup, eventOptionsPassive); + } - self.y = self.top + offsetY; - } + x = event.pageX; + y = event.pageY; - self.y = Math.max(Math.min(self.y, self.overflow ? root_h - self.header : root_h - self.height - self.bottom), self.top); - move_y = self.y !== old_y; - } + // appearing scrollbars on the root element does not trigger "window.onresize", + // force refresh window size via init(), also force layout recalculation (layout trashing) + // it is probably very rare that the body overflow changes between window open and close - if(move_x || move_y){ + //init(); + } + } - if(self.max){ + function handler_mousemove(event) { + preventEvent(event); - self.restore(); - } + if (touch) { + event = event.touches[0]; + } - use_raf ? raf_move = true : self.move(); - } + const pageX = event.pageX; + const pageY = event.pageY; + const offsetX = pageX - x; + const offsetY = pageY - y; + + const old_w = self.width; + const old_h = self.height; + const old_x = self.x; + const old_y = self.y; + + let resize_w, resize_h, move_x, move_y; + + if (dir === "drag") { + if (self.hasClass("no-move")) return; + + self.x += offsetX; + self.y += offsetY; + move_x = move_y = 1; + } else { + if (dir === "e" || dir === "se" || dir === "ne") { + self.width += offsetX; + resize_w = 1; + } else if (dir === "w" || dir === "sw" || dir === "nw") { + self.x += offsetX; + self.width -= offsetX; + resize_w = 1; + move_x = 1; + } + + if (dir === "s" || dir === "se" || dir === "sw") { + self.height += offsetY; + resize_h = 1; + } else if (dir === "n" || dir === "ne" || dir === "nw") { + self.y += offsetY; + self.height -= offsetY; + resize_h = 1; + move_y = 1; + } + } - if(resize_w || move_x){ + if (resize_w) { + self.width = Math.max( + Math.min(self.width, self.maxwidth, root_w - self.x - self.right), + self.minwidth + ); + resize_w = self.width !== old_w; + } - x = pageX; - } + if (resize_h) { + self.height = Math.max( + Math.min(self.height, self.maxheight, root_h - self.y - self.bottom), + self.minheight + ); + resize_h = self.height !== old_h; + } - if(resize_h || move_y){ + if (resize_w || resize_h) { + use_raf ? (raf_resize = true) : self.resize(); + } - y = pageY; - } + if (move_x) { + if (self.max) { + self.x = + (pageX < root_w / 3 + ? self.left + : pageX > (root_w / 3) * 2 + ? root_w - self.width - self.right + : root_w / 2 - self.width / 2) + offsetX; + } + + self.x = Math.max( + Math.min( + self.x, + self.overflow ? root_w - 30 : root_w - self.width - self.right + ), + self.overflow ? 30 - self.width : self.left + ); + move_x = self.x !== old_x; } - function handler_mouseup(event){ + if (move_y) { + if (self.max) { + self.y = self.top + offsetY; + } + + self.y = Math.max( + Math.min( + self.y, + self.overflow + ? root_h - self.header + : root_h - self.height - self.bottom + ), + self.top + ); + move_y = self.y !== old_y; + } - preventEvent(event); - removeClass(body, "wb-lock"); - use_raf && cancelAnimationFrame(raf_timer); + if (move_x || move_y) { + if (self.max) { + self.restore(); + } - if(touch){ + use_raf ? (raf_move = true) : self.move(); + } - //removeListener(self.dom, "touchmove", preventEvent); - removeListener(window, "touchmove", handler_mousemove, eventOptionsPassive); - removeListener(window, "touchend", handler_mouseup, eventOptionsPassive); - } - else{ + if (resize_w || move_x) { + x = pageX; + } - //removeListener(this, "mouseleave", handler_mouseup); - removeListener(window, "mousemove", handler_mousemove, eventOptionsPassive); - removeListener(window, "mouseup", handler_mouseup, eventOptionsPassive); - } + if (resize_h || move_y) { + y = pageY; + } + } + + function handler_mouseup(event) { + preventEvent(event); + removeClass(body, "wb-lock"); + use_raf && cancelAnimationFrame(raf_timer); + + if (touch) { + //removeListener(self.dom, "touchmove", preventEvent); + removeListener( + window, + "touchmove", + handler_mousemove, + eventOptionsPassive + ); + removeListener(window, "touchend", handler_mouseup, eventOptionsPassive); + } else { + //removeListener(this, "mouseleave", handler_mouseup); + removeListener( + window, + "mousemove", + handler_mousemove, + eventOptionsPassive + ); + removeListener(window, "mouseup", handler_mouseup, eventOptionsPassive); } + } } -function init(){ +function init() { + // TODO: the window height of iOS isn't determined correctly when the bottom toolbar disappears - // TODO: the window height of iOS isn't determined correctly when the bottom toolbar disappears + // the bounding rect provides more precise dimensions (float values) + // //const rect = doc.getBoundingClientRect(); + // this.root_w = doc.clientWidth; //rect.width || (rect.right - rect.left); + // this.root_h = doc.clientHeight; //rect.height || (rect.top - rect.bottom); - // the bounding rect provides more precise dimensions (float values) - // //const rect = doc.getBoundingClientRect(); - // this.root_w = doc.clientWidth; //rect.width || (rect.right - rect.left); - // this.root_h = doc.clientHeight; //rect.height || (rect.top - rect.bottom); + // if(ios){ + // this.root_h = window.innerHeight * (this.root_w / window.innerWidth); + // } - // if(ios){ - // this.root_h = window.innerHeight * (this.root_w / window.innerWidth); - // } + // root_w = doc.clientWidth; + // root_h = doc.clientHeight; - // root_w = doc.clientWidth; - // root_h = doc.clientHeight; + // root_w = body.clientWidth; + // root_h = body.clientHeight; - // root_w = body.clientWidth; - // root_h = body.clientHeight; - - const doc = document.documentElement; - root_w = doc.clientWidth; - root_h = doc.clientHeight; + const doc = document.documentElement; + root_w = doc.clientWidth; + root_h = doc.clientHeight; } /** @@ -811,16 +775,15 @@ function init(){ * @this WinBox */ -WinBox.prototype.mount = function(src){ - - // handles mounting over: - this.unmount(); +WinBox.prototype.mount = function (src) { + // handles mounting over: + this.unmount(); - src._backstore || (src._backstore = src.parentNode); - this.body.textContent = ""; - this.body.appendChild(src); + src._backstore || (src._backstore = src.parentNode); + this.body.textContent = ""; + this.body.appendChild(src); - return this; + return this; }; /** @@ -828,74 +791,65 @@ WinBox.prototype.mount = function(src){ * @this WinBox */ -WinBox.prototype.unmount = function(dest){ - - const node = this.body.firstChild; - - if(node){ +WinBox.prototype.unmount = function (dest) { + const node = this.body.firstChild; - const root = dest || node._backstore; + if (node) { + const root = dest || node._backstore; - root && root.appendChild(node); - node._backstore = dest; - } + root && root.appendChild(node); + node._backstore = dest; + } - return this; + return this; }; /** * @this WinBox */ -WinBox.prototype.setTitle = function(title){ - - const node = getByClass(this.dom, "wb-title"); - setText(node, this.title = title); - return this; +WinBox.prototype.setTitle = function (title) { + const node = getByClass(this.dom, "wb-title"); + setText(node, (this.title = title)); + return this; }; /** * @this WinBox */ -WinBox.prototype.setIcon = function(src){ - - const img = getByClass(this.dom, "wb-icon"); - setStyle(img, "background-image", "url(" + src + ")"); - setStyle(img, "display", "inline-block"); +WinBox.prototype.setIcon = function (src) { + const img = getByClass(this.dom, "wb-icon"); + setStyle(img, "background-image", "url(" + src + ")"); + setStyle(img, "display", "inline-block"); - return this; + return this; }; /** * @this WinBox */ -WinBox.prototype.setBackground = function(background){ - - setStyle(this.dom, "background", background); - return this; +WinBox.prototype.setBackground = function (background) { + setStyle(this.dom, "background", background); + return this; }; /** * @this WinBox */ -WinBox.prototype.setUrl = function(url, onload){ +WinBox.prototype.setUrl = function (url, onload) { + const node = this.body.firstChild; - const node = this.body.firstChild; + if (node && node.tagName.toLowerCase() === "iframe") { + node.src = url; + } else { + this.body.innerHTML = ''; + onload && (this.body.firstChild.onload = onload); + } - if(node && (node.tagName.toLowerCase() === "iframe")){ - - node.src = url; - } - else{ - - this.body.innerHTML = ''; - onload && (this.body.firstChild.onload = onload); - } - - return this; + return this; }; /** @@ -903,41 +857,35 @@ WinBox.prototype.setUrl = function(url, onload){ * @this WinBox */ -WinBox.prototype.focus = function(state){ - - if(state === false){ - - return this.blur(); - } - - if(!this.focused){ - - const stack_length = stack_win.length; +WinBox.prototype.focus = function (state) { + if (state === false) { + return this.blur(); + } - if(stack_length > 1){ - - for(let i = 1; i <= stack_length; i++){ - - const last_focus = stack_win[stack_length - i]; + if (!this.focused) { + const stack_length = stack_win.length; - if(last_focus.focused /*&& last_focus !== this*/){ + if (stack_length > 1) { + for (let i = 1; i <= stack_length; i++) { + const last_focus = stack_win[stack_length - i]; - last_focus.blur(); - stack_win.push(stack_win.splice(stack_win.indexOf(this), 1)[0]); + if (last_focus.focused /*&& last_focus !== this*/) { + last_focus.blur(); + stack_win.push(stack_win.splice(stack_win.indexOf(this), 1)[0]); - break; - } - } + break; } - - setStyle(this.dom, "z-index", ++index_counter); - this.index = index_counter; - this.addClass("focus"); - this.focused = true; - this.onfocus && this.onfocus(); + } } - return this; + setStyle(this.dom, "z-index", ++index_counter); + this.index = index_counter; + this.addClass("focus"); + this.focused = true; + this.onfocus && this.onfocus(); + } + + return this; }; /** @@ -945,21 +893,18 @@ WinBox.prototype.focus = function(state){ * @this WinBox */ -WinBox.prototype.blur = function(state){ +WinBox.prototype.blur = function (state) { + if (state === false) { + return this.focus(); + } - if(state === false){ - - return this.focus(); - } - - if(this.focused){ - - this.removeClass("focus"); - this.focused = false; - this.onblur && this.onblur(); - } + if (this.focused) { + this.removeClass("focus"); + this.focused = false; + this.onblur && this.onblur(); + } - return this; + return this; }; /** @@ -967,19 +912,16 @@ WinBox.prototype.blur = function(state){ * @this WinBox */ -WinBox.prototype.hide = function(state){ - - if(state === false){ - - return this.show(); - } - - if(!this.hidden){ +WinBox.prototype.hide = function (state) { + if (state === false) { + return this.show(); + } - this.onhide && this.onhide(); - this.hidden = true; - return this.addClass("hide"); - } + if (!this.hidden) { + this.onhide && this.onhide(); + this.hidden = true; + return this.addClass("hide"); + } }; /** @@ -987,19 +929,16 @@ WinBox.prototype.hide = function(state){ * @this WinBox */ -WinBox.prototype.show = function(state){ - - if(state === false){ - - return this.hide(); - } - - if(this.hidden){ +WinBox.prototype.show = function (state) { + if (state === false) { + return this.hide(); + } - this.onshow && this.onshow(); - this.hidden = false; - return this.removeClass("hide"); - } + if (this.hidden) { + this.onshow && this.onshow(); + this.hidden = false; + return this.removeClass("hide"); + } }; /** @@ -1007,89 +946,75 @@ WinBox.prototype.show = function(state){ * @this WinBox */ -WinBox.prototype.minimize = function(state){ - - if(state === false){ +WinBox.prototype.minimize = function (state) { + if (state === false) { + return this.restore(); + } - return this.restore(); - } - - if(is_fullscreen){ + if (is_fullscreen) { + cancel_fullscreen(); + } - cancel_fullscreen(); - } + if (this.max) { + this.removeClass("max"); + this.max = false; + } - if(this.max){ + if (!this.min) { + stack_min.push(this); + update_min_stack(); + this.dom.title = this.title; + this.addClass("min"); + this.min = true; - this.removeClass("max"); - this.max = false; + if (this.focused) { + this.blur(); + focus_next(); } - if(!this.min){ - - stack_min.push(this); - update_min_stack(); - this.dom.title = this.title; - this.addClass("min"); - this.min = true; - - if(this.focused){ - - this.blur(); - focus_next(); - } - - this.onminimize && this.onminimize(); - } + this.onminimize && this.onminimize(); + } - return this; + return this; }; -function focus_next(){ - - const stack_length = stack_win.length; - - if(stack_length){ - - for(let i = stack_length - 1; i >= 0; i--){ - - const last_focus = stack_win[i]; +function focus_next() { + const stack_length = stack_win.length; - if(!last_focus.min /*&& last_focus !== this*/){ + if (stack_length) { + for (let i = stack_length - 1; i >= 0; i--) { + const last_focus = stack_win[i]; - last_focus.focus(); - break; - } - } + if (!last_focus.min /*&& last_focus !== this*/) { + last_focus.focus(); + break; + } } + } } /** * @this WinBox */ -WinBox.prototype.restore = function(){ - - if(is_fullscreen){ +WinBox.prototype.restore = function () { + if (is_fullscreen) { + cancel_fullscreen(); + } - cancel_fullscreen(); - } + if (this.min) { + remove_min_stack(this); + this.resize().move(); + this.onrestore && this.onrestore(); + } - if(this.min){ - - remove_min_stack(this); - this.resize().move(); - this.onrestore && this.onrestore(); - } - - if(this.max){ - - this.max = false; - this.removeClass("max").resize().move(); - this.onrestore && this.onrestore(); - } + if (this.max) { + this.max = false; + this.removeClass("max").resize().move(); + this.onrestore && this.onrestore(); + } - return this; + return this; }; /** @@ -1097,43 +1022,33 @@ WinBox.prototype.restore = function(){ * @this WinBox */ -WinBox.prototype.maximize = function(state){ - - if(state === false){ - - return this.restore(); - } - - if(is_fullscreen){ - - cancel_fullscreen(); - } - - if(this.min){ - - remove_min_stack(this); - } - - if(!this.max){ - - this.addClass("max").resize( - - root_w - this.left - this.right, - root_h - this.top - this.bottom /* - 1 */, - true - - ).move( - - this.left, - this.top, - true - ); - - this.max = true; - this.onmaximize && this.onmaximize(); - } - - return this; +WinBox.prototype.maximize = function (state) { + if (state === false) { + return this.restore(); + } + + if (is_fullscreen) { + cancel_fullscreen(); + } + + if (this.min) { + remove_min_stack(this); + } + + if (!this.max) { + this.addClass("max") + .resize( + root_w - this.left - this.right, + root_h - this.top - this.bottom /* - 1 */, + true + ) + .move(this.left, this.top, true); + + this.max = true; + this.onmaximize && this.onmaximize(); + } + + return this; }; /** @@ -1141,62 +1056,53 @@ WinBox.prototype.maximize = function(state){ * @this WinBox */ -WinBox.prototype.fullscreen = function(state){ - - if(this.min){ +WinBox.prototype.fullscreen = function (state) { + if (this.min) { + remove_min_stack(this); + this.resize().move(); + } - remove_min_stack(this); - this.resize().move(); - } + // fullscreen could be changed by user manually! - // fullscreen could be changed by user manually! + if (!is_fullscreen || !cancel_fullscreen()) { + // requestFullscreen is executed as async and returns promise. + // in this case it is better to set the state to "this.full" after the requestFullscreen was fired, + // because it may break when browser does not support fullscreen properly and bypass it silently. - if(!is_fullscreen || !cancel_fullscreen()){ - - // requestFullscreen is executed as async and returns promise. - // in this case it is better to set the state to "this.full" after the requestFullscreen was fired, - // because it may break when browser does not support fullscreen properly and bypass it silently. - - this.body[prefix_request](); - is_fullscreen = this; - this.full = true; - this.onfullscreen && this.onfullscreen(); - } - else if(state === false){ - - return this.restore(); - } + this.body[prefix_request](); + is_fullscreen = this; + this.full = true; + this.onfullscreen && this.onfullscreen(); + } else if (state === false) { + return this.restore(); + } - return this; + return this; }; -function has_fullscreen(){ - - return ( - - document["fullscreen"] || - document["fullscreenElement"] || - document["webkitFullscreenElement"] || - document["mozFullScreenElement"] - ); +function has_fullscreen() { + return ( + document["fullscreen"] || + document["fullscreenElement"] || + document["webkitFullscreenElement"] || + document["mozFullScreenElement"] + ); } /** * @return {boolean|void} */ -function cancel_fullscreen(){ - - is_fullscreen.full = false; - - if(has_fullscreen()){ +function cancel_fullscreen() { + is_fullscreen.full = false; - // exitFullscreen is executed as async and returns promise. - // the important part is that the promise callback runs before the event "onresize" was fired! + if (has_fullscreen()) { + // exitFullscreen is executed as async and returns promise. + // the important part is that the promise callback runs before the event "onresize" was fired! - document[prefix_exit](); - return true; - } + document[prefix_exit](); + return true; + } } /** @@ -1204,27 +1110,24 @@ function cancel_fullscreen(){ * @this WinBox */ -WinBox.prototype.close = function(force) { - - if(this.onclose && this.onclose(force)){ - - return true; - } - - if(this.min){ +WinBox.prototype.close = function (force) { + if (this.onclose && this.onclose(force)) { + return true; + } - remove_min_stack(this); - } + if (this.min) { + remove_min_stack(this); + } - stack_win.splice(stack_win.indexOf(this), 1); + stack_win.splice(stack_win.indexOf(this), 1); - this.unmount(); - this.dom.remove(); - this.dom.textContent = ""; - this.dom["winbox"] = null; - this.body = null; - this.dom = null; - this.focused && focus_next(); + this.unmount(); + this.dom.remove(); + this.dom.textContent = ""; + this.dom["winbox"] = null; + this.body = null; + this.dom = null; + this.focused && focus_next(); }; /** @@ -1234,25 +1137,25 @@ WinBox.prototype.close = function(force) { * @this WinBox */ -WinBox.prototype.move = function(x, y, _skip_update){ - - if(!x && (x !== 0)){ - - x = this.x; - y = this.y; - } - else if(!_skip_update){ - - this.x = x ? x = parse(x, root_w - this.left - this.right, this.width) : 0; - this.y = y ? y = parse(y, root_h - this.top - this.bottom, this.height) : 0; - } - - //setStyle(this.dom, "transform", "translate(" + x + "px," + y + "px)"); - setStyle(this.dom, "left", x + "px"); - setStyle(this.dom, "top", y + "px"); - - this.onmove && this.onmove(x, y); - return this; +WinBox.prototype.move = function (x, y, _skip_update) { + if (!x && x !== 0) { + x = this.x; + y = this.y; + } else if (!_skip_update) { + this.x = x + ? (x = parse(x, root_w - this.left - this.right, this.width)) + : 0; + this.y = y + ? (y = parse(y, root_h - this.top - this.bottom, this.height)) + : 0; + } + + //setStyle(this.dom, "transform", "translate(" + x + "px," + y + "px)"); + setStyle(this.dom, "left", x + "px"); + setStyle(this.dom, "top", y + "px"); + + this.onmove && this.onmove(x, y); + return this; }; /** @@ -1262,27 +1165,27 @@ WinBox.prototype.move = function(x, y, _skip_update){ * @this WinBox */ -WinBox.prototype.resize = function(w, h, _skip_update){ - - if(!w && (w !== 0)){ - - w = this.width; - h = this.height; - } - else if(!_skip_update){ - - this.width = w ? w = parse(w, this.maxwidth /*- this.left - this.right*/) : 0; - this.height = h ? h = parse(h, this.maxheight /*- this.top - this.bottom*/) : 0; - - w = Math.max(w, this.minwidth); - h = Math.max(h, this.minheight); - } - - setStyle(this.dom, "width", w + "px"); - setStyle(this.dom, "height", h + "px"); - - this.onresize && this.onresize(w, h); - return this; +WinBox.prototype.resize = function (w, h, _skip_update) { + if (!w && w !== 0) { + w = this.width; + h = this.height; + } else if (!_skip_update) { + this.width = w + ? (w = parse(w, this.maxwidth /*- this.left - this.right*/)) + : 0; + this.height = h + ? (h = parse(h, this.maxheight /*- this.top - this.bottom*/)) + : 0; + + w = Math.max(w, this.minwidth); + h = Math.max(h, this.minheight); + } + + setStyle(this.dom, "width", w + "px"); + setStyle(this.dom, "height", h + "px"); + + this.onresize && this.onresize(w, h); + return this; }; /** @@ -1290,23 +1193,25 @@ WinBox.prototype.resize = function(w, h, _skip_update){ * @this WinBox */ -WinBox.prototype.addControl = function(control){ - - const classname = control["class"]; - const image = control.image; - const click = control.click; - const index = control.index; - const node = document.createElement("span"); - const icons = getByClass(this.dom, "wb-control"); - const self = this; - - if(classname) node.className = classname; - if(image) setStyle(node, "background-image", "url(" + image + ")"); - if(click) node.onclick = function(event){ click.call(this, event, self) }; - - icons.insertBefore(node, icons.childNodes[index || 0]); - - return this; +WinBox.prototype.addControl = function (control) { + const classname = control["class"]; + const image = control.image; + const click = control.click; + const index = control.index; + const node = document.createElement("span"); + const icons = getByClass(this.dom, "wb-control"); + const self = this; + + if (classname) node.className = classname; + if (image) setStyle(node, "background-image", "url(" + image + ")"); + if (click) + node.onclick = function (event) { + click.call(this, event, self); + }; + + icons.insertBefore(node, icons.childNodes[index || 0]); + + return this; }; /** @@ -1314,11 +1219,10 @@ WinBox.prototype.addControl = function(control){ * @this WinBox */ -WinBox.prototype.removeControl = function(control){ - - control = getByClass(this.dom, control); - control && control.remove(); - return this; +WinBox.prototype.removeControl = function (control) { + control = getByClass(this.dom, control); + control && control.remove(); + return this; }; /** @@ -1326,10 +1230,9 @@ WinBox.prototype.removeControl = function(control){ * @this WinBox */ -WinBox.prototype.addClass = function(classname){ - - addClass(this.dom, classname); - return this; +WinBox.prototype.addClass = function (classname) { + addClass(this.dom, classname); + return this; }; /** @@ -1337,21 +1240,18 @@ WinBox.prototype.addClass = function(classname){ * @this WinBox */ -WinBox.prototype.removeClass = function(classname){ - - removeClass(this.dom, classname); - return this; +WinBox.prototype.removeClass = function (classname) { + removeClass(this.dom, classname); + return this; }; - /** * @param {string} classname * @this WinBox */ -WinBox.prototype.hasClass = function(classname){ - - return hasClass(this.dom, classname); +WinBox.prototype.hasClass = function (classname) { + return hasClass(this.dom, classname); }; /** @@ -1359,16 +1259,16 @@ WinBox.prototype.hasClass = function(classname){ * @this WinBox */ -WinBox.prototype.toggleClass = function(classname){ - - return this.hasClass(classname) ? this.removeClass(classname) : this.addClass(classname); +WinBox.prototype.toggleClass = function (classname) { + return this.hasClass(classname) + ? this.removeClass(classname) + : this.addClass(classname); }; - /* WinBox.prototype.use = function(plugin){ this.plugins.push(plugin); return this; }; -*/ \ No newline at end of file +*/ diff --git a/task/build.js b/task/build.js index fb064dd..84eb57c 100644 --- a/task/build.js +++ b/task/build.js @@ -1,5 +1,5 @@ -const child_process = require('child_process'); -const fs = require('fs'); +const child_process = require("child_process"); +const fs = require("fs"); console.log("Start build ....."); console.log(); @@ -37,95 +37,96 @@ const options = (function(argv){ const bundle = process.argv[2] === "--bundle"; //const extern = process.argv[2] === "--extern"; -const parameter = (function(opt){ +const parameter = (function (opt) { + let parameter = ""; - let parameter = ''; - - for(let index in opt){ - - if(opt.hasOwnProperty(index)){ - - parameter += ' --' + index + '=' + opt[index]; - } + for (let index in opt) { + if (opt.hasOwnProperty(index)) { + parameter += " --" + index + "=" + opt[index]; } + } - return parameter; + return parameter; })({ - - compilation_level: "ADVANCED_OPTIMIZATIONS", //"WHITESPACE" - use_types_for_optimization: true, - //new_type_inf: true, - //jscomp_warning: "newCheckTypes", - //jscomp_error: "strictCheckTypes", - //jscomp_error: "newCheckTypesExtraChecks", - generate_exports: true, - export_local_property_definitions: true, - language_in: "ECMASCRIPT6_STRICT", - language_out: "ECMASCRIPT5_STRICT", - process_closure_primitives: true, - summary_detail_level: 3, - warning_level: "VERBOSE", - emit_use_strict: true, - - output_manifest: "log/manifest.log", - //output_module_dependencies: "log/module_dependencies.log", - property_renaming_report: "log/renaming_report.log", - strict_mode_input: true, - assume_function_wrapper: true, - - //transform_amd_modules: true, - process_common_js_modules: true, - module_resolution: "BROWSER", - dependency_mode: "PRUNE_LEGACY", - rewrite_polyfills: false, - //js_module_root: "./", - entry_point: "./src/js/webpack.js", - //manage_closure_dependencies: true, - //dependency_mode: "PRUNE_LEGACY", - - isolation_mode: "IIFE" - //output_wrapper: "(function(){%output%}());" - - //formatting: "PRETTY_PRINT" + compilation_level: "ADVANCED_OPTIMIZATIONS", //"WHITESPACE" + use_types_for_optimization: true, + //new_type_inf: true, + //jscomp_warning: "newCheckTypes", + //jscomp_error: "strictCheckTypes", + //jscomp_error: "newCheckTypesExtraChecks", + generate_exports: true, + export_local_property_definitions: true, + language_in: "ECMASCRIPT6_STRICT", + language_out: "ECMASCRIPT5_STRICT", + process_closure_primitives: true, + summary_detail_level: 3, + warning_level: "VERBOSE", + emit_use_strict: true, + + output_manifest: "log/manifest.log", + //output_module_dependencies: "log/module_dependencies.log", + property_renaming_report: "log/renaming_report.log", + strict_mode_input: true, + assume_function_wrapper: true, + + //transform_amd_modules: true, + process_common_js_modules: true, + module_resolution: "BROWSER", + dependency_mode: "PRUNE_LEGACY", + rewrite_polyfills: false, + //js_module_root: "./", + entry_point: "./src/js/webpack.js", + //manage_closure_dependencies: true, + //dependency_mode: "PRUNE_LEGACY", + + isolation_mode: "IIFE", + //output_wrapper: "(function(){%output%}());" + + //formatting: "PRETTY_PRINT" }); -exec((/^win/.test(process.platform) ? - - "\"node_modules/google-closure-compiler-windows/compiler.exe\"" -: - "java -jar node_modules/google-closure-compiler-java/compiler.jar" - -) + parameter + (bundle ? " --js='tmp/**.js'" : "") + " --js='src/js/**.js' --js_output_file='" + (bundle ? "dist/winbox.bundle.min.js" : "dist/js/winbox.min.js") + "' && exit 0", function(){ - - let build = fs.readFileSync((bundle ? "dist/winbox.bundle.min.js" : "dist/js/winbox.min.js")); +exec( + (/^win/.test(process.platform) + ? '"node_modules/google-closure-compiler-windows/compiler.exe"' + : "java -jar node_modules/google-closure-compiler-java/compiler.jar") + + parameter + + (bundle ? " --js='tmp/**.js'" : "") + + " --js='src/js/**.js' --js_output_file='" + + (bundle ? "dist/winbox.bundle.min.js" : "dist/js/winbox.min.js") + + "' && exit 0", + function () { + let build = fs.readFileSync( + bundle ? "dist/winbox.bundle.min.js" : "dist/js/winbox.min.js" + ); let preserve = fs.readFileSync("src/js/winbox.js", "utf8"); const package_json = require("../package.json"); - preserve = preserve.replace("* WinBox.js", "* WinBox.js v" + package_json.version + (bundle ? " (Bundle)" : "")); - build = preserve.substring(0, preserve.indexOf('*/') + 2) + "\n" + build; - fs.writeFileSync((bundle ? "dist/winbox.bundle.min.js" : "dist/js/winbox.min.js"), build); + preserve = preserve.replace( + "* WinBox.js", + "* WinBox.js v" + package_json.version + (bundle ? " (Bundle)" : "") + ); + build = preserve.substring(0, preserve.indexOf("*/") + 2) + "\n" + build; + fs.writeFileSync( + bundle ? "dist/winbox.bundle.min.js" : "dist/js/winbox.min.js", + build + ); console.log("Build Complete."); -}); - -function exec(prompt, callback){ - - const child = child_process.exec(prompt, function(err, stdout, stderr){ - - if(err){ - - console.error(err); - } - else{ - - if(callback){ - - callback(); - } - } - }); + } +); + +function exec(prompt, callback) { + const child = child_process.exec(prompt, function (err, stdout, stderr) { + if (err) { + console.error(err); + } else { + if (callback) { + callback(); + } + } + }); - child.stdout.pipe(process.stdout); - child.stderr.pipe(process.stderr); + child.stdout.pipe(process.stdout); + child.stderr.pipe(process.stderr); } diff --git a/task/bundle.js b/task/bundle.js index 260ab87..242cd9b 100644 --- a/task/bundle.js +++ b/task/bundle.js @@ -1,5 +1,5 @@ -const { base64Sync } = require('base64-img'); -const fs = require('fs'); +const { base64Sync } = require("base64-img"); +const fs = require("fs"); fs.existsSync("log") || fs.mkdirSync("log"); fs.existsSync("tmp") || fs.mkdirSync("tmp"); @@ -10,47 +10,46 @@ const image = process.argv[2] === "--image"; //const template = process.argv[2] === "--template"; const style = process.argv[2] === "--style"; -(function(){ - - if(image){ - - // TODO provide custom filenames - - const compressed = { - - max: base64Sync('dist/img/max.svg'), - close: base64Sync('dist/img/close.svg'), - full: base64Sync('dist/img/full.svg'), - //restore: base64Sync('dist/img/restore.svg'), - //exit: base64Sync('dist/img/exit.svg'), - min: base64Sync('dist/img/min.svg') - }; - - let tmp = ""; - - for(let key in compressed){ - - if(compressed.hasOwnProperty(key)){ - - tmp += ("@" + key + ": \"" + compressed[key] + "\";\n"); - } - } - - fs.writeFileSync("tmp/images.less", tmp); - fs.writeFileSync("tmp/bundle.less", '@import "../src/css/winbox.less"; @import "images.less";'); // @import "../src/css/themes/modern.less"; @import "../src/css/themes/white.less"; - } - - // ---------------------- - - if(style){ - - fs.writeFileSync("tmp/style.js", - - 'const style = document.createElement("style");' + - 'style.innerHTML = "' + fs.readFileSync("dist/css/winbox.min.css", "utf8").replace(/"/g, "'") + '";' + - 'const head = document.getElementsByTagName("head")[0];' + - 'if(head.firstChild) head.insertBefore(style, head.firstChild); else head.appendChild(style);' - ); +(function () { + if (image) { + // TODO provide custom filenames + + const compressed = { + max: base64Sync("dist/img/max.svg"), + close: base64Sync("dist/img/close.svg"), + full: base64Sync("dist/img/full.svg"), + //restore: base64Sync('dist/img/restore.svg'), + //exit: base64Sync('dist/img/exit.svg'), + min: base64Sync("dist/img/min.svg"), + }; + + let tmp = ""; + + for (let key in compressed) { + if (compressed.hasOwnProperty(key)) { + tmp += "@" + key + ': "' + compressed[key] + '";\n'; + } } + fs.writeFileSync("tmp/images.less", tmp); + fs.writeFileSync( + "tmp/bundle.less", + '@import "../src/css/winbox.less"; @import "images.less";' + ); // @import "../src/css/themes/modern.less"; @import "../src/css/themes/white.less"; + } + + // ---------------------- + + if (style) { + fs.writeFileSync( + "tmp/style.js", + + 'const style = document.createElement("style");' + + 'style.innerHTML = "' + + fs.readFileSync("dist/css/winbox.min.css", "utf8").replace(/"/g, "'") + + '";' + + 'const head = document.getElementsByTagName("head")[0];' + + "if(head.firstChild) head.insertBefore(style, head.firstChild); else head.appendChild(style);" + ); + } })(); diff --git a/task/clean.js b/task/clean.js index 46d7a68..5408791 100644 --- a/task/clean.js +++ b/task/clean.js @@ -1,26 +1,26 @@ const fs = require("fs"); const path = require("path"); -const removeDir = function(path) { - if (fs.existsSync(path)) { - const files = fs.readdirSync(path); +const removeDir = function (path) { + if (fs.existsSync(path)) { + const files = fs.readdirSync(path); - if (files.length > 0) { - files.forEach(function(filename) { - if (fs.statSync(path + "/" + filename).isDirectory()) { - removeDir(path + "/" + filename); - } else { - fs.unlinkSync(path + "/" + filename); - } - }); - - fs.rmdirSync(path); + if (files.length > 0) { + files.forEach(function (filename) { + if (fs.statSync(path + "/" + filename).isDirectory()) { + removeDir(path + "/" + filename); } else { - fs.rmdirSync(path); + fs.unlinkSync(path + "/" + filename); } + }); + + fs.rmdirSync(path); + } else { + fs.rmdirSync(path); } -} + } +}; -const distPath = path.join('dist') +const distPath = path.join("dist"); removeDir(distPath); diff --git a/task/server.js b/task/server.js index 7c9ce01..503f5e8 100644 --- a/task/server.js +++ b/task/server.js @@ -2,51 +2,46 @@ var port = process.argv[2]; -if(!port){ - - if(/^win/.test(process.platform)){ - - port = 80; - } - else{ - - port = 8080; - } +if (!port) { + if (/^win/.test(process.platform)) { + port = 80; + } else { + port = 8080; + } } -var ws = require('web-servo'); +var ws = require("web-servo"); ws.config({ - - "server": { - "port": port, - "dir": "/", - "exitOnError": false, - "ssl": { - "enabled": false, - "key": "", - "cert": "" - } - }, - "page": { - "default": "index.html" + server: { + port: port, + dir: "/", + exitOnError: false, + ssl: { + enabled: false, + key: "", + cert: "", }, - "methods": { - "allowed": [ - "OPTIONS", - "GET", - "POST", - "HEAD", - "PUT", - "PATCH", - "DELETE" - //"COPY", - //"LINK", - //"UNLINK", - //"TRACE", - //"CONNECT" - ] - } + }, + page: { + default: "index.html", + }, + methods: { + allowed: [ + "OPTIONS", + "GET", + "POST", + "HEAD", + "PUT", + "PATCH", + "DELETE", + //"COPY", + //"LINK", + //"UNLINK", + //"TRACE", + //"CONNECT" + ], + }, }); //ws.setConfigVar('server.port', port); diff --git a/task/svgo.js b/task/svgo.js index db366af..a0c1a41 100644 --- a/task/svgo.js +++ b/task/svgo.js @@ -1,59 +1,56 @@ -const fs = require('fs'); -const path = require('path'); -let { optimize } = require('svgo'); +const fs = require("fs"); +const path = require("path"); +let { optimize } = require("svgo"); const plugins = [ - 'cleanupAttrs', - 'removeDoctype', - 'removeXMLProcInst', - 'removeComments', - 'removeMetadata', - 'removeTitle', - 'removeDesc', - 'removeUselessDefs', - 'removeEditorsNSData', - 'removeEmptyAttrs', - 'removeHiddenElems', - 'removeEmptyText', - 'removeEmptyContainers', - // 'removeViewBox', - 'cleanupEnableBackground', - 'convertStyleToAttrs', - 'convertColors', - 'convertPathData', - 'convertTransform', - 'removeUnknownsAndDefaults', - 'removeNonInheritableGroupAttrs', - 'removeUselessStrokeAndFill', - 'removeUnusedNS', - 'cleanupIDs', - 'cleanupNumericValues', - 'moveElemsAttrsToGroup', - 'moveGroupAttrsToElems', - 'collapseGroups', - // 'removeRasterImages', - 'mergePaths', - 'convertShapeToPath', - 'sortAttrs', - 'removeDimensions', - //{ name: 'removeAttrs', params: { attrs: '(stroke|fill)' } }, + "cleanupAttrs", + "removeDoctype", + "removeXMLProcInst", + "removeComments", + "removeMetadata", + "removeTitle", + "removeDesc", + "removeUselessDefs", + "removeEditorsNSData", + "removeEmptyAttrs", + "removeHiddenElems", + "removeEmptyText", + "removeEmptyContainers", + // 'removeViewBox', + "cleanupEnableBackground", + "convertStyleToAttrs", + "convertColors", + "convertPathData", + "convertTransform", + "removeUnknownsAndDefaults", + "removeNonInheritableGroupAttrs", + "removeUselessStrokeAndFill", + "removeUnusedNS", + "cleanupIDs", + "cleanupNumericValues", + "moveElemsAttrsToGroup", + "moveGroupAttrsToElems", + "collapseGroups", + // 'removeRasterImages', + "mergePaths", + "convertShapeToPath", + "sortAttrs", + "removeDimensions", + //{ name: 'removeAttrs', params: { attrs: '(stroke|fill)' } }, ]; - -const directoryPath = 'dist/img'; +const directoryPath = "dist/img"; const files = fs.readdirSync(directoryPath); -files.forEach(function(filepath){ - - if(filepath.endsWith(".svg")){ - - filepath = path.resolve(__dirname, "..", directoryPath, filepath); +files.forEach(function (filepath) { + if (filepath.endsWith(".svg")) { + filepath = path.resolve(__dirname, "..", directoryPath, filepath); - console.log(filepath); + console.log(filepath); - const data = fs.readFileSync(filepath, 'utf8'); - const result = optimize(data, { path: filepath, plugins: plugins }); + const data = fs.readFileSync(filepath, "utf8"); + const result = optimize(data, { path: filepath, plugins: plugins }); - fs.writeFileSync(filepath, result.data); - } + fs.writeFileSync(filepath, result.data); + } }); From b6e40459ca4fe3bc810305c23b0485fca5e071c3 Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 13:35:32 +0200 Subject: [PATCH 04/14] chore(cs): run npm install instead of npm ci --- .github/workflows/prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml index 09d11da..af3dc5d 100644 --- a/.github/workflows/prs.yml +++ b/.github/workflows/prs.yml @@ -11,6 +11,6 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 21 - - run: npm ci + - run: npm install - name: Run lint run: npm run lint From 4f9f57169f3a2b8e99b6152a5e4ead67ef828d1c Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 13:36:53 +0200 Subject: [PATCH 05/14] chore(cs): fix cs --- src/js/template.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/template.js b/src/js/template.js index 5038126..fb13c62 100644 --- a/src/js/template.js +++ b/src/js/template.js @@ -27,7 +27,7 @@ const templateHTML = "
" + "
"; - //'' +//'' export default function (tpl) { if (!template) { From fa6bb8676c40aed0216fd1c5a35f7b1214eefa03 Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 13:40:05 +0200 Subject: [PATCH 06/14] chore(cs): remove unused parameters --- eslint.config.js | 5 +++-- package.json | 2 ++ src/js/winbox.js | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index d78fb53..2edbef8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,7 +1,8 @@ import globals from "globals"; import pluginJs from "@eslint/js"; + export default [ - { languageOptions: { globals: globals.browser } }, + {languageOptions: { globals: globals.browser }}, pluginJs.configs.recommended, -]; +]; \ No newline at end of file diff --git a/package.json b/package.json index 2316dbb..dcc74e5 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,8 @@ "cpx": "^1.5.0", "csso": "^5.0.4", "csso-cli": "^4.0.1", + "eslint": "^9.9.0", + "globals": "^15.9.0", "google-closure-compiler": "^20220719.0.0", "less": "^4.1.3", "less-plugin-autoprefix": "^2.0.0", diff --git a/src/js/winbox.js b/src/js/winbox.js index fe059ba..f8c6251 100644 --- a/src/js/winbox.js +++ b/src/js/winbox.js @@ -373,13 +373,13 @@ function setup() { addListener( body, "mousedown", - function (event) { + function () { window_clicked = false; }, true ); - addListener(body, "mousedown", function (event) { + addListener(body, "mousedown", function () { if (!window_clicked) { const stack_length = stack_win.length; @@ -441,7 +441,7 @@ function register(self) { addListener( self.dom, "mousedown", - function (event) { + function () { window_clicked = true; }, true @@ -450,7 +450,7 @@ function register(self) { addListener( self.body, "mousedown", - function (event) { + function () { // stop propagation would disable global listeners used inside window contents // use event bubbling for this listener to skip this handler by the other click listeners self.focus(); From d08e7c016614a09cbd1ce172dd455dc70ae2a461 Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 13:43:09 +0200 Subject: [PATCH 07/14] chore(cs): fix eslint config cs --- eslint.config.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 2edbef8..d78fb53 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,8 +1,7 @@ import globals from "globals"; import pluginJs from "@eslint/js"; - export default [ - {languageOptions: { globals: globals.browser }}, + { languageOptions: { globals: globals.browser } }, pluginJs.configs.recommended, -]; \ No newline at end of file +]; From c1daf53a14ebdda162e1c0bff133147772e3fb2b Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 13:50:29 +0200 Subject: [PATCH 08/14] fix(ci): add build step --- .github/workflows/prs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml index af3dc5d..ba1b604 100644 --- a/.github/workflows/prs.yml +++ b/.github/workflows/prs.yml @@ -14,3 +14,5 @@ jobs: - run: npm install - name: Run lint run: npm run lint + - name: Run build + run: npm run build From cdfe81834b5661f22a2dea7a57129c012bc5e643 Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 13:59:40 +0200 Subject: [PATCH 09/14] feat: automated releases --- .github/workflows/release.yml | 46 +++++++++++++++++++++++++++++++++++ .releaserc.json | 16 ++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .releaserc.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..cce08f4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: Automated release +on: + push: + branches: + - master +jobs: + lint: + timeout-minutes: 3 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm install + - name: Run lint + run: npm run lint + release: + name: Automated release + permissions: + contents: write + issues: write + pull-requests: write + id-token: write + packages: write + needs: + - lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm install + - run: > + npx + -p "@semantic-release/commit-analyzer" + -p "@semantic-release/exec" + -p "@codedependant/semantic-release-docker" + -p "@semantic-release/release-notes-generator" + -p conventional-changelog-conventionalcommits + -p semantic-release + -- semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..3205ad6 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,16 @@ +{ + "branches": ["master"], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/github" + ], + "@semantic-release/npm", + "@semantic-release/release-notes-generator" + ] +} From 4f9e26bee4af3b81867fe527b4bfc742a595bcff Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 14:07:58 +0200 Subject: [PATCH 10/14] chore(ci): remove unused semantic-release plugins --- .github/workflows/release.yml | 2 -- .releaserc.json | 26 ++++++++++++-------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cce08f4..c6b7942 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,8 +35,6 @@ jobs: - run: > npx -p "@semantic-release/commit-analyzer" - -p "@semantic-release/exec" - -p "@codedependant/semantic-release-docker" -p "@semantic-release/release-notes-generator" -p conventional-changelog-conventionalcommits -p semantic-release diff --git a/.releaserc.json b/.releaserc.json index 3205ad6..d1f0936 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -1,16 +1,14 @@ { - "branches": ["master"], - "plugins": [ - [ - "@semantic-release/commit-analyzer", - { - "preset": "conventionalcommits" - } - ], - [ - "@semantic-release/github" - ], - "@semantic-release/npm", - "@semantic-release/release-notes-generator" - ] + "branches": ["master"], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits" + } + ], + ["@semantic-release/github"], + "@semantic-release/npm", + "@semantic-release/release-notes-generator" + ] } From 2ae25af52e6b622c46ec8d275c1c71c8a39e221f Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 14:23:44 +0200 Subject: [PATCH 11/14] chore: set up release preview comment --- .github/workflows/prs.yml | 80 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml index ba1b604..26b9708 100644 --- a/.github/workflows/prs.yml +++ b/.github/workflows/prs.yml @@ -16,3 +16,83 @@ jobs: run: npm run lint - name: Run build run: npm run build + release-preview: + name: Automated release preview + needs: [lint] + permissions: + issues: write + runs-on: ubuntu-latest + steps: + - name: 📀 Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: 🖥️ Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: 🔗 Install Dependencies + shell: bash + run: npm install --no-fund --no-audit --ignore-scripts + - name: 🔬 Check semantic versioning + id: semantic-release + run: | + GITHUB_REF=${{ github.head_ref }} + npx semantic-release --no-ci --dry-run --plugins @semantic-release/commit-analyzer,@semantic-release/release-notes-generator --branches ${{ github.head_ref }} > output.txt + OUTPUT=$(cat output.txt | base64 -w 0) + echo "::set-output name=releaseNote::$OUTPUT" + - name: 📝 Report semantic versioning + uses: actions/github-script@v3 + if: ${{ steps.semantic-release.outputs.releaseNote != '' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + // build release note + const semanticReleaseOutput = Buffer.from('${{ steps.semantic-release.outputs.releaseNote }}', 'base64').toString('utf8'); + const semanticReleaseLogMatch = /^[[0-9:\sAMPM]+\]\s\[semantic-release\].*$/; + const lines = semanticReleaseOutput.split('\n'); + const lastSemanticReleaseLogIndex = [...lines] + .reverse() + .findIndex((line) => line.match(semanticReleaseLogMatch)); + + const releaseNoteIndex = lines.length - lastSemanticReleaseLogIndex; + const releaseNote = lines.slice(releaseNoteIndex); + + let res = releaseNote.join('\n'); + if (!releaseNote.length || !res) { + res = '### No release note would be generated.'; + } + + const SEMANTIC_RELEASE_BODY_HEADER = '## 📝 Semantic Release Report'; + const body = [SEMANTIC_RELEASE_BODY_HEADER, res].join('\n'); + + // get last comment + const comments = await github.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo + }); + + // find comments to delete + const commentsToDelete = comments.data.filter((comment) => + comment.body.startsWith(SEMANTIC_RELEASE_BODY_HEADER) + ); + + // delete comments + const prms = commentsToDelete.map((comment) => + github.issues.deleteComment({ + comment_id: comment.id, + owner: context.repo.owner, + repo: context.repo.repo + }) + ); + + await Promise.all(prms); + + // create new comment for release note + github.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body + }); From c0908d8bd8a5f7edfeefeb695baedf51f55786a2 Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 14:31:39 +0200 Subject: [PATCH 12/14] chore(ci): try quoting github ref --- .github/workflows/prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml index 26b9708..0036488 100644 --- a/.github/workflows/prs.yml +++ b/.github/workflows/prs.yml @@ -38,7 +38,7 @@ jobs: id: semantic-release run: | GITHUB_REF=${{ github.head_ref }} - npx semantic-release --no-ci --dry-run --plugins @semantic-release/commit-analyzer,@semantic-release/release-notes-generator --branches ${{ github.head_ref }} > output.txt + npx semantic-release --no-ci --dry-run --plugins @semantic-release/commit-analyzer,@semantic-release/release-notes-generator --branches "${{ github.head_ref }}"" > output.txt OUTPUT=$(cat output.txt | base64 -w 0) echo "::set-output name=releaseNote::$OUTPUT" - name: 📝 Report semantic versioning From 06f2783ca0eb3703f2538379151a13ca5a0339e6 Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 14:33:31 +0200 Subject: [PATCH 13/14] chore(ci): typo --- .github/workflows/prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml index 0036488..e0c0133 100644 --- a/.github/workflows/prs.yml +++ b/.github/workflows/prs.yml @@ -38,7 +38,7 @@ jobs: id: semantic-release run: | GITHUB_REF=${{ github.head_ref }} - npx semantic-release --no-ci --dry-run --plugins @semantic-release/commit-analyzer,@semantic-release/release-notes-generator --branches "${{ github.head_ref }}"" > output.txt + npx semantic-release --no-ci --dry-run --plugins @semantic-release/commit-analyzer,@semantic-release/release-notes-generator --branches "${{ github.head_ref }}" > output.txt OUTPUT=$(cat output.txt | base64 -w 0) echo "::set-output name=releaseNote::$OUTPUT" - name: 📝 Report semantic versioning From 82dff3c1c5dad2d558b0136a68409e8a70ac6f49 Mon Sep 17 00:00:00 2001 From: Sam Mousa Date: Wed, 14 Aug 2024 14:46:58 +0200 Subject: [PATCH 14/14] chore(ci): try without explicit permissions --- .github/workflows/prs.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/prs.yml b/.github/workflows/prs.yml index e0c0133..9c01198 100644 --- a/.github/workflows/prs.yml +++ b/.github/workflows/prs.yml @@ -19,8 +19,6 @@ jobs: release-preview: name: Automated release preview needs: [lint] - permissions: - issues: write runs-on: ubuntu-latest steps: - name: 📀 Checkout