diff --git a/.stylelintrc.json b/.stylelintrc.json index d6689cc01..c91107595 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,6 +1,5 @@ { "extends": [ - "stylelint-rscss/config", "stylelint-config-standard", "stylelint-config-recommended-scss", "stylelint-config-html", @@ -8,15 +7,6 @@ ], "rules": { "declaration-no-important": true, - "rscss/no-descendant-combinator": false, - "rscss/class-format": [ - false, - { - "component": "pascal-case", - "variant": "^-[a-z]\\w+", - "element": "^[a-z]\\w+" - } - ], "selector-class-pattern": null, "import-notation": null, "custom-property-pattern": null, diff --git a/package.json b/package.json index d8177ec3d..a6f431874 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "body-scroll-lock": "3.1.5", "chromatism": "3.0.0", "click-outside-vue3": "4.0.1", - "cropperjs": "1.6.2", + "cropperjs": "2.0.0", "escape-html": "1.0.3", "globals": "^16.0.0", "hash-sum": "^2.0.0", @@ -95,19 +95,19 @@ "postcss": "8.5.3", "postcss-html": "^1.5.0", "postcss-scss": "^4.0.6", - "sass": "1.86.0", + "sass": "1.86.1", "selenium-server": "3.141.59", "semver": "7.7.1", "serve-static": "2.2.0", "shelljs": "0.9.2", "sinon": "20.0.0", "sinon-chai": "4.0.0", - "stylelint": "14.16.1", + "stylelint": "16.17.0", "stylelint-config-html": "^1.1.0", - "stylelint-config-recommended-scss": "^8.0.0", - "stylelint-config-recommended-vue": "^1.4.0", - "stylelint-config-standard": "29.0.0", - "stylelint-rscss": "0.4.0", + "stylelint-config-recommended": "^14.0.0", + "stylelint-config-recommended-scss": "^14.0.0", + "stylelint-config-recommended-vue": "^1.6.0", + "stylelint-config-standard": "37.0.0", "vite": "^6.1.0", "vite-plugin-eslint2": "^5.0.3", "vite-plugin-stylelint": "^6.0.0", diff --git a/src/App.scss b/src/App.scss index 521be05c6..704d51cea 100644 --- a/src/App.scss +++ b/src/App.scss @@ -34,8 +34,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; overscroll-behavior-y: none; - overflow-x: clip; - overflow-y: scroll; + overflow: clip scroll; &.hidden { display: none; @@ -224,9 +223,8 @@ nav { grid-template-rows: 1fr; box-sizing: border-box; margin: 0 auto; - align-content: flex-start; + place-content: flex-start center; flex-wrap: wrap; - justify-content: center; min-height: 100vh; overflow-x: clip; @@ -262,8 +260,7 @@ nav { position: sticky; top: var(--navbar-height); max-height: calc(100vh - var(--navbar-height)); - overflow-y: auto; - overflow-x: hidden; + overflow: hidden auto; margin-left: calc(var(--___paddingIncrease) * -1); padding-left: calc(var(--___paddingIncrease) + var(--___columnMargin) / 2); @@ -832,7 +829,7 @@ option { .login-hint { text-align: center; - @media all and (min-width: 801px) { + @media all and (width >= 801px) { display: none; } @@ -854,7 +851,7 @@ option { flex: 1; } -@media all and (max-width: 800px) { +@media all and (width <= 800px) { .mobile-hidden { display: none; } diff --git a/src/components/announcement_editor/announcement_editor.vue b/src/components/announcement_editor/announcement_editor.vue index 31473331f..f1b016183 100644 --- a/src/components/announcement_editor/announcement_editor.vue +++ b/src/components/announcement_editor/announcement_editor.vue @@ -56,7 +56,7 @@ .post-textarea { resize: vertical; height: 10em; - overflow: none; + overflow: visible; box-sizing: content-box; } } diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss index f7254ea35..c058f8172 100644 --- a/src/components/chat_message/chat_message.scss +++ b/src/components/chat_message/chat_message.scss @@ -107,8 +107,7 @@ .outgoing { display: flex; flex-flow: row wrap; - align-content: end; - justify-content: flex-end; + place-content: end flex-end; .chat-message-inner { align-items: flex-end; diff --git a/src/components/component_preview/component_preview.vue b/src/components/component_preview/component_preview.vue index 6aee90a2d..6a77d6c5d 100644 --- a/src/components/component_preview/component_preview.vue +++ b/src/components/component_preview/component_preview.vue @@ -190,20 +190,15 @@ export default { .header { grid-area: header; - justify-self: center; - align-self: baseline; + place-self: baseline center; line-height: 2; } .invalid-container { position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; display: grid; - align-items: center; - justify-items: center; + place-items: center center; background-color: rgba(100 0 0 / 50%); .alert { @@ -214,7 +209,7 @@ export default { .assists { grid-area: assists; display: grid; - grid-auto-flow: rows; + grid-auto-flow: row; grid-auto-rows: 2em; grid-gap: 0.5em; } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index f1206e644..c37a2ca26 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -322,10 +322,7 @@ content: ""; display: block; position: absolute; - top: calc(var(--___margin) * -1); - bottom: calc(var(--___margin) * -1); - left: calc(var(--___margin) * -1); - right: calc(var(--___margin) * -1); + inset: calc(var(--___margin) * -1); background: var(--background); backdrop-filter: var(--__panel-backdrop-filter); } diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss index 0c094ce28..35a57d9fe 100644 --- a/src/components/desktop_nav/desktop_nav.scss +++ b/src/components/desktop_nav/desktop_nav.scss @@ -59,7 +59,7 @@ transition-timing-function: ease-out; transition-duration: 100ms; - @media all and (min-width: 800px) { + @media all and (width >= 800px) { /* stylelint-disable-next-line declaration-no-important */ opacity: 1 !important; } @@ -70,10 +70,7 @@ mask-size: contain; background-color: var(--text); position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; } img { diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue index 1eb88efb5..939655f65 100644 --- a/src/components/dialog_modal/dialog_modal.vue +++ b/src/components/dialog_modal/dialog_modal.vue @@ -29,14 +29,11 @@ // TODO: unify with other modals. .dark-overlay { &::before { - bottom: 0; + inset: 0; content: " "; display: block; cursor: default; - left: 0; position: fixed; - right: 0; - top: 0; background: rgb(27 31 35 / 50%); z-index: 2000; } @@ -45,13 +42,9 @@ .dialog-container { display: grid; position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; justify-content: center; - align-items: center; - justify-items: center; + place-items: center center; } .dialog-modal.panel { @@ -98,8 +91,7 @@ #modal.-mobile { .dialog-container { justify-content: stretch; - align-items: end; - justify-items: stretch; + place-items: end stretch; &.-center-mobile { align-items: center; @@ -114,7 +106,6 @@ flex-direction: column; justify-content: flex-end; grid-template-columns: 1fr; - grid-auto-columns: none; grid-auto-rows: auto; grid-auto-flow: row dense; diff --git a/src/components/draft/draft.vue b/src/components/draft/draft.vue index 7b0878112..9f8d8c14e 100644 --- a/src/components/draft/draft.vue +++ b/src/components/draft/draft.vue @@ -135,8 +135,7 @@ .poll-indicator-container { border-radius: var(--roundness); display: grid; - justify-items: center; - align-items: center; + place-items: center center; align-self: start; height: 0; padding-bottom: 62.5%; @@ -147,13 +146,9 @@ box-sizing: border-box; border: 1px solid var(--border); position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; + inset: 0; display: grid; - justify-items: center; - align-items: center; + place-items: center center; width: 100%; height: 100%; } diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue index c90f079d9..f9788d874 100644 --- a/src/components/emoji_input/emoji_input.vue +++ b/src/components/emoji_input/emoji_input.vue @@ -159,10 +159,7 @@ opacity: 0; pointer-events: none; position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; + inset: 0; overflow: hidden; /* DEBUG STUFF */ diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss index 0c9a66008..05600c790 100644 --- a/src/components/emoji_picker/emoji_picker.scss +++ b/src/components/emoji_picker/emoji_picker.scss @@ -64,8 +64,7 @@ flex-grow: 1; display: flex; flex-flow: row nowrap; - overflow-x: auto; - overflow-y: hidden; + overflow: auto hidden; } .additional-tabs { @@ -153,7 +152,13 @@ transition: mask-size 150ms; mask-size: 100% 20px, 100% 20px, auto; // Autoprefixed seem to ignore this one, and also syntax is different + /* stylelint-disable mask-composite */ + /* stylelint-disable declaration-property-value-no-unknown */ + + /* TODO check if this is still needed */ mask-composite: xor; + /* stylelint-enable declaration-property-value-no-unknown */ + /* stylelint-enable mask-composite */ mask-composite: exclude; &.scrolled { @@ -197,8 +202,7 @@ &-group { display: grid; grid-template-columns: repeat(var(--__amount), 1fr); - align-items: center; - justify-items: center; + place-items: center center; justify-content: center; grid-template-rows: repeat(1, auto); diff --git a/src/components/flash/flash.vue b/src/components/flash/flash.vue index c9fc730be..3196e9bc4 100644 --- a/src/components/flash/flash.vue +++ b/src/components/flash/flash.vue @@ -77,7 +77,7 @@ .hidden { display: none; - visibility: "hidden"; + visibility: hidden; } } diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue index 8ed318704..6043e64e9 100644 --- a/src/components/gallery/gallery.vue +++ b/src/components/gallery/gallery.vue @@ -101,10 +101,7 @@ .gallery-row-inner { position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + inset: 0; display: flex; flex-flow: row wrap; align-content: stretch; @@ -160,7 +157,13 @@ linear-gradient(to top, white, white); /* Autoprefixed seem to ignore this one, and also syntax is different */ + /* stylelint-disable mask-composite */ + /* stylelint-disable declaration-property-value-no-unknown */ + + /* TODO check if this is still needed */ mask-composite: xor; + /* stylelint-enable declaration-property-value-no-unknown */ + /* stylelint-enable mask-composite */ mask-composite: exclude; } } diff --git a/src/components/image_cropper/image_cropper.js b/src/components/image_cropper/image_cropper.js index 55e901a03..3cb4f6fd1 100644 --- a/src/components/image_cropper/image_cropper.js +++ b/src/components/image_cropper/image_cropper.js @@ -1,5 +1,4 @@ -import Cropper from 'cropperjs' -import 'cropperjs/dist/cropper.css' +import 'cropperjs' // This adds all of the cropperjs's components into DOM import { library } from '@fortawesome/fontawesome-svg-core' import { faCircleNotch @@ -19,19 +18,6 @@ const ImageCropper = { type: Function, required: true }, - cropperOptions: { - type: Object, - default () { - return { - aspectRatio: 1, - autoCropArea: 1, - viewMode: 1, - movable: false, - zoomable: false, - guides: false - } - } - }, mimes: { type: String, default: 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon' @@ -48,7 +34,6 @@ const ImageCropper = { }, data () { return { - cropper: undefined, dataUrl: undefined, filename: undefined, submitting: false @@ -67,27 +52,30 @@ const ImageCropper = { }, methods: { destroy () { - if (this.cropper) { - this.cropper.destroy() - } this.$refs.input.value = '' this.dataUrl = undefined this.$emit('close') }, submit (cropping = true) { this.submitting = true - this.submitHandler(cropping && this.cropper, this.file) - .then(() => this.destroy()) - .finally(() => { - this.submitting = false - }) + + let cropperPromise + if (cropping) { + cropperPromise = this.$refs.cropperSelection.$toCanvas() + } else { + cropperPromise = Promise.resolve() + } + cropperPromise.then(canvas => { + this.submitHandler(canvas, this.file) + .then(() => this.destroy()) + .finally(() => { + this.submitting = false + }) + }) }, pickImage () { this.$refs.input.click() }, - createCropper () { - this.cropper = new Cropper(this.$refs.img, this.cropperOptions) - }, getTriggerDOM () { return typeof this.trigger === 'object' ? this.trigger : document.querySelector(this.trigger) }, @@ -103,6 +91,29 @@ const ImageCropper = { reader.readAsDataURL(this.file) this.$emit('changed', this.file, reader) } + }, + inSelection(selection, maxSelection) { + return ( + selection.x >= maxSelection.x + && selection.y >= maxSelection.y + && (selection.x + selection.width) <= (maxSelection.x + maxSelection.width) + && (selection.y + selection.height) <= (maxSelection.y + maxSelection.height) + ) + }, + onCropperSelectionChange(event) { + const cropperCanvas = this.$refs.cropperCanvas + const cropperCanvasRect = cropperCanvas.getBoundingClientRect() + const selection = event.detail + const maxSelection = { + x: 0, + y: 0, + width: cropperCanvasRect.width, + height: cropperCanvasRect.height, + } + + if (!this.inSelection(selection, maxSelection)) { + event.preventDefault(); + } } }, mounted () { diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue index 8647ed4d7..6d11ad4ec 100644 --- a/src/components/image_cropper/image_cropper.vue +++ b/src/components/image_cropper/image_cropper.vue @@ -1,14 +1,43 @@