@cloudron/pankow 4.4.2 → 4.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,18 @@
1
1
  <script setup>
2
2
 
3
3
  import { copyToClipboard } from '../utils';
4
- import Button from './Button.vue';
5
4
 
6
5
  const props = defineProps({
7
6
  value: String,
8
7
  doneHint: String,
8
+ multiline: {
9
+ type: Boolean,
10
+ default: false
11
+ },
9
12
  });
10
13
 
11
14
  function onClipboard() {
12
- copyToClipboard(props.value);
15
+ copyToClipboard(props.value, { multiline: props.multiline });
13
16
  window.pankow.notify({
14
17
  text: props.doneHint || 'Copied',
15
18
  type: 'success',
@@ -6,10 +6,14 @@ import Button from './Button.vue';
6
6
  const props = defineProps({
7
7
  value: String,
8
8
  doneHint: String,
9
+ multiline: {
10
+ type: Boolean,
11
+ default: false
12
+ },
9
13
  });
10
14
 
11
15
  function onClipboard() {
12
- copyToClipboard(props.value);
16
+ copyToClipboard(props.value, { multiline: props.multiline });
13
17
  window.pankow.notify({
14
18
  text: props.doneHint || 'Copied',
15
19
  type: 'success',
@@ -69,6 +69,10 @@ function onChange(event) {
69
69
  transition: all 250ms;
70
70
  }
71
71
 
72
+ .pankow-switch.pankow-switch-disabled span::before {
73
+ background-color: color-mix(in srgb, var(--pankow-text-color), transparent 60%);
74
+ }
75
+
72
76
  .pankow-switch-input-label {
73
77
  margin-left: 8px;
74
78
  cursor: pointer;
@@ -87,6 +91,10 @@ input:checked + span::before {
87
91
  background-color: white;
88
92
  }
89
93
 
94
+ .pankow-switch.pankow-switch-disabled > input:checked + span::before {
95
+ background-color: color-mix(in srgb, white, transparent 60%);
96
+ }
97
+
90
98
  .pankow-switch input {
91
99
  display: none;
92
100
  }
@@ -0,0 +1,80 @@
1
+ <template>
2
+ <Transition name="pankow-version-banner">
3
+ <div v-if="mismatch" class="pankow-version-banner">
4
+ <span>A new version is available.</span>
5
+ <Button plain @click="reload">Reload</Button>
6
+ <Button plain tool @click="dismiss" aria-label="Dismiss">&#x2715;</Button>
7
+ </div>
8
+ </Transition>
9
+ </template>
10
+
11
+ <script setup>
12
+ import { ref, onMounted, onUnmounted } from 'vue';
13
+ import Button from './Button.vue';
14
+
15
+ const mismatch = ref(false);
16
+
17
+ function setMismatch() {
18
+ mismatch.value = true;
19
+ }
20
+
21
+ function reload() {
22
+ window.location.reload(true);
23
+ }
24
+
25
+ function dismiss() {
26
+ mismatch.value = false;
27
+ }
28
+
29
+ function checkFlag() {
30
+ if (window.__pankow_version_mismatch) setMismatch();
31
+ }
32
+
33
+ onMounted(() => {
34
+ if (window.__pankow_version_mismatch) setMismatch();
35
+ window.addEventListener('pankow:version-mismatch', checkFlag);
36
+ });
37
+
38
+ onUnmounted(() => {
39
+ window.removeEventListener('pankow:version-mismatch', checkFlag);
40
+ });
41
+ </script>
42
+
43
+ <style>
44
+ .pankow-version-banner {
45
+ position: fixed;
46
+ top: 0;
47
+ left: 0;
48
+ right: 0;
49
+ z-index: 30002;
50
+ display: flex;
51
+ justify-content: center;
52
+ align-items: center;
53
+ gap: 16px;
54
+ padding: 10px 24px;
55
+ background: var(--pankow-color-primary);
56
+ color: white;
57
+ font-family: var(--pankow-font-family, Inter, sans-serif);
58
+ font-size: 13px;
59
+ font-weight: 500;
60
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
61
+ }
62
+
63
+ .pankow-version-banner .pankow-button[plain] {
64
+ color: white;
65
+ }
66
+
67
+ .pankow-version-banner .pankow-button[plain]:hover {
68
+ color: white;
69
+ }
70
+
71
+ .pankow-version-banner-enter-active,
72
+ .pankow-version-banner-leave-active {
73
+ transition: transform 0.3s ease;
74
+ }
75
+
76
+ .pankow-version-banner-enter-from,
77
+ .pankow-version-banner-leave-to {
78
+ transform: translateY(-100%);
79
+ }
80
+ </style>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudron/pankow",
3
3
  "private": false,
4
- "version": "4.4.2",
4
+ "version": "4.4.4",
5
5
  "description": "Vue 3 UI component library for Cloudron apps",
6
6
  "main": "index.js",
7
7
  "types": "types/index.d.ts",
package/plugin.js CHANGED
@@ -5,8 +5,10 @@ import textOverflow from './text-overflow.js';
5
5
  import fetcher from './fetcher.js';
6
6
  import { setNotifyPosition } from './notifyState.js';
7
7
  import Notification from './components/Notification.vue';
8
+ import VersionCheckBanner from './components/VersionCheckBanner.vue';
8
9
 
9
- const CONTAINER_ID = 'pankow-notification-root';
10
+ const NOTIFICATION_CONTAINER_ID = 'pankow-notification-root';
11
+ const VERSION_BANNER_CONTAINER_ID = 'pankow-version-banner-root';
10
12
 
11
13
  export default {
12
14
  install(app, options = {}) {
@@ -28,14 +30,25 @@ export default {
28
30
  }
29
31
 
30
32
  if (typeof document !== 'undefined' && !import.meta.env.SSR) {
31
- const existing = document.getElementById(CONTAINER_ID);
32
- if (existing) existing.remove();
33
+ const existingNotification = document.getElementById(NOTIFICATION_CONTAINER_ID);
34
+ if (existingNotification) existingNotification.remove();
33
35
 
34
- const container = document.createElement('div');
35
- container.id = CONTAINER_ID;
36
- document.body.appendChild(container);
36
+ const notificationContainer = document.createElement('div');
37
+ notificationContainer.id = NOTIFICATION_CONTAINER_ID;
38
+ document.body.appendChild(notificationContainer);
37
39
 
38
- createApp(Notification).mount(container);
40
+ createApp(Notification).mount(notificationContainer);
41
+ }
42
+
43
+ if (typeof document !== 'undefined' && !import.meta.env.SSR) {
44
+ const existingBanner = document.getElementById(VERSION_BANNER_CONTAINER_ID);
45
+ if (existingBanner) existingBanner.remove();
46
+
47
+ const bannerContainer = document.createElement('div');
48
+ bannerContainer.id = VERSION_BANNER_CONTAINER_ID;
49
+ document.body.appendChild(bannerContainer);
50
+
51
+ createApp(VersionCheckBanner).mount(bannerContainer);
39
52
  }
40
53
  }
41
54
  };
package/types/utils.d.ts CHANGED
@@ -127,12 +127,15 @@ declare function getExtension(entry: {
127
127
  fileName: string;
128
128
  }): string;
129
129
  /**
130
- * Copies a string to the system clipboard using a temporary input element.
130
+ * Copies a string to the system clipboard using a temporary form element.
131
131
  * @param {string} value
132
+ * @param {{ multiline?: boolean }} [options] - When multiline is true, uses a textarea so newlines are preserved
132
133
  * @returns {void}
133
134
  * @deprecated Prefer the native `navigator.clipboard.writeText()` API.
134
135
  */
135
- declare function copyToClipboard(value: string): void;
136
+ declare function copyToClipboard(value: string, options?: {
137
+ multiline?: boolean;
138
+ }): void;
136
139
  /**
137
140
  * Parses the current page's URL query string into a key/value object.
138
141
  * @returns {Object<string, string>}
package/utils.js CHANGED
@@ -332,13 +332,15 @@ function getExtension(entry) {
332
332
  }
333
333
 
334
334
  /**
335
- * Copies a string to the system clipboard using a temporary input element.
335
+ * Copies a string to the system clipboard using a temporary form element.
336
336
  * @param {string} value
337
+ * @param {{ multiline?: boolean }} [options] - When multiline is true, uses a textarea so newlines are preserved
337
338
  * @returns {void}
338
339
  * @deprecated Prefer the native `navigator.clipboard.writeText()` API.
339
340
  */
340
- function copyToClipboard(value) {
341
- var elem = document.createElement('input');
341
+ function copyToClipboard(value, options) {
342
+ var multiline = options && options.multiline;
343
+ var elem = document.createElement(multiline ? 'textarea' : 'input');
342
344
  elem.value = value;
343
345
  document.body.append(elem);
344
346
  elem.select();
package/vite-plugin.js CHANGED
@@ -1,12 +1,44 @@
1
1
  import { dirname, resolve } from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
- import { existsSync } from 'node:fs';
3
+ import { existsSync, writeFileSync } from 'node:fs';
4
4
 
5
5
  const __dirname = dirname(fileURLToPath(import.meta.url));
6
6
 
7
7
  const PANKOW_PACKAGE = '@cloudron/pankow';
8
8
 
9
- export default function pankowPlugin() {
9
+ const versionCheckScript = `<script>
10
+ (function () {
11
+ var meta = document.querySelector('meta[name="build-id"]');
12
+ if (!meta) return;
13
+
14
+ var id = meta.content;
15
+
16
+ function check() {
17
+ fetch('/build.json')
18
+ .then(function (r) { return r.ok ? r.json() : null; })
19
+ .then(function (d) {
20
+ if (d && d.buildId && d.buildId !== id) {
21
+ window.__pankow_version_mismatch = true;
22
+ window.dispatchEvent(new CustomEvent('pankow:version-mismatch'));
23
+ }
24
+ })
25
+ .catch(function () {});
26
+ }
27
+
28
+ setInterval(check, 30000);
29
+
30
+ document.addEventListener('visibilitychange', function () {
31
+ if (document.visibilityState === 'visible') check();
32
+ });
33
+
34
+ setTimeout(check, 5000);
35
+ })();
36
+ </script>`;
37
+
38
+ export default function pankowPlugin({ versionCheck = false } = {}) {
39
+ let buildId = null;
40
+ let config = null;
41
+
10
42
  return {
11
43
  name: 'vite-plugin-pankow',
12
44
  config(config) {
@@ -21,6 +53,9 @@ export default function pankowPlugin() {
21
53
  },
22
54
  };
23
55
  },
56
+ configResolved(resolvedConfig) {
57
+ config = resolvedConfig;
58
+ },
24
59
  configureServer(server) {
25
60
  const allow = server.config.server.fs.allow;
26
61
  if (Array.isArray(allow) && !allow.includes(__dirname)) {
@@ -36,5 +71,16 @@ export default function pankowPlugin() {
36
71
  }
37
72
  }
38
73
  },
74
+ transformIndexHtml(html) {
75
+ if (!versionCheck) return html;
76
+ if (!buildId) buildId = Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
77
+ return html.replace('</head>', `<meta name="build-id" content="${buildId}">\n${versionCheckScript}\n</head>`);
78
+ },
79
+ closeBundle() {
80
+ if (!versionCheck || !buildId || !config) return;
81
+ const outDir = config.build.outDir || 'dist';
82
+ const outPath = resolve(config.root, outDir, 'build.json');
83
+ writeFileSync(outPath, JSON.stringify({ buildId }));
84
+ },
39
85
  };
40
86
  }