Fix main menu sizing and add shell aliases
This commit is contained in:
parent
1db5069b9c
commit
f1cbfa66c0
|
|
@ -44,7 +44,7 @@ The installer will build, install, and set up everything automatically with a gu
|
||||||
|
|
||||||
**After installation:**
|
**After installation:**
|
||||||
```bash
|
```bash
|
||||||
source ~/.bashrc # (or ~/.zshrc for zsh)
|
source ~/.bashrc # (or ~/.zshrc, or ~/.config/fish/config.fish)
|
||||||
VideoTools
|
VideoTools
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -91,6 +91,7 @@ Output is professional quality, ready for:
|
||||||
- **DVD_IMPLEMENTATION_SUMMARY.md** - Technical specifications
|
- **DVD_IMPLEMENTATION_SUMMARY.md** - Technical specifications
|
||||||
- **INTEGRATION_GUIDE.md** - System architecture and integration
|
- **INTEGRATION_GUIDE.md** - System architecture and integration
|
||||||
- **QUEUE_SYSTEM_GUIDE.md** - Queue system reference
|
- **QUEUE_SYSTEM_GUIDE.md** - Queue system reference
|
||||||
|
- **localization-policy.md** - Localization strategy and implementation guide
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|
@ -112,7 +113,9 @@ VideoTools has a modular architecture:
|
||||||
### Build & Run
|
### Build & Run
|
||||||
```bash
|
```bash
|
||||||
# One-time setup
|
# One-time setup
|
||||||
source scripts/alias.sh
|
source scripts/alias.sh # bash
|
||||||
|
# source scripts/alias.zsh # zsh
|
||||||
|
# source scripts/alias.fish # fish
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
VideoTools
|
VideoTools
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /home/stu/Projects/VideoTools
|
cd /home/stu/Projects/VideoTools
|
||||||
source scripts/alias.sh
|
source scripts/alias.sh # bash
|
||||||
|
# source scripts/alias.zsh # zsh
|
||||||
|
# source scripts/alias.fish # fish
|
||||||
VideoTools
|
VideoTools
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -15,7 +17,7 @@ This will:
|
||||||
2. Build the application (if needed)
|
2. Build the application (if needed)
|
||||||
3. Run VideoTools GUI
|
3. Run VideoTools GUI
|
||||||
|
|
||||||
**Available commands after sourcing alias.sh:**
|
**Available commands after sourcing the alias script:**
|
||||||
- `VideoTools` - Run the application
|
- `VideoTools` - Run the application
|
||||||
- `VideoToolsRebuild` - Force a clean rebuild
|
- `VideoToolsRebuild` - Force a clean rebuild
|
||||||
- `VideoToolsClean` - Clean all build artifacts
|
- `VideoToolsClean` - Clean all build artifacts
|
||||||
|
|
@ -67,7 +69,7 @@ source ~/.bashrc
|
||||||
### For Zsh users:
|
### For Zsh users:
|
||||||
Add this line to `~/.zshrc`:
|
Add this line to `~/.zshrc`:
|
||||||
```bash
|
```bash
|
||||||
source /home/stu/Projects/VideoTools/scripts/alias.sh
|
source /home/stu/Projects/VideoTools/scripts/alias.zsh
|
||||||
```
|
```
|
||||||
|
|
||||||
Then reload:
|
Then reload:
|
||||||
|
|
@ -75,6 +77,17 @@ Then reload:
|
||||||
source ~/.zshrc
|
source ~/.zshrc
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### For Fish users:
|
||||||
|
Add this line to `~/.config/fish/config.fish`:
|
||||||
|
```fish
|
||||||
|
source /home/stu/Projects/VideoTools/scripts/alias.fish
|
||||||
|
```
|
||||||
|
|
||||||
|
Reload fish:
|
||||||
|
```fish
|
||||||
|
source ~/.config/fish/config.fish
|
||||||
|
```
|
||||||
|
|
||||||
### After setting up:
|
### After setting up:
|
||||||
From any directory, you can simply type:
|
From any directory, you can simply type:
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -133,10 +146,16 @@ bash scripts/run.sh
|
||||||
- No manual steps needed
|
- No manual steps needed
|
||||||
- Always runs the latest code
|
- Always runs the latest code
|
||||||
|
|
||||||
### alias.sh
|
### alias.sh / alias.zsh / alias.fish
|
||||||
```bash
|
```bash
|
||||||
source scripts/alias.sh
|
source scripts/alias.sh
|
||||||
```
|
```
|
||||||
|
```bash
|
||||||
|
source scripts/alias.zsh
|
||||||
|
```
|
||||||
|
```fish
|
||||||
|
source scripts/alias.fish
|
||||||
|
```
|
||||||
|
|
||||||
**Purpose:** Creates convenient shell commands
|
**Purpose:** Creates convenient shell commands
|
||||||
|
|
||||||
|
|
@ -440,4 +459,3 @@ VideoTools
|
||||||
```
|
```
|
||||||
|
|
||||||
**That's it!** The scripts handle everything else automatically.
|
**That's it!** The scripts handle everything else automatically.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ This single command automates the entire setup process.
|
||||||
4. **Install Binary:** Copies the compiled binary to the selected location and makes it executable.
|
4. **Install Binary:** Copies the compiled binary to the selected location and makes it executable.
|
||||||
5. **Configure Shell:** Detects your shell (`bash` or `zsh`) and updates the corresponding resource file (`~/.bashrc` or `~/.zshrc`) to:
|
5. **Configure Shell:** Detects your shell (`bash` or `zsh`) and updates the corresponding resource file (`~/.bashrc` or `~/.zshrc`) to:
|
||||||
* Add the installation directory to your `PATH`.
|
* Add the installation directory to your `PATH`.
|
||||||
* Source the `alias.sh` script for convenience commands.
|
* Source the matching alias script (`alias.sh` for bash, `alias.zsh` for zsh).
|
||||||
|
|
||||||
### After Installation
|
### After Installation
|
||||||
|
|
||||||
|
|
@ -36,6 +36,9 @@ source ~/.bashrc
|
||||||
|
|
||||||
# For zsh users:
|
# For zsh users:
|
||||||
source ~/.zshrc
|
source ~/.zshrc
|
||||||
|
|
||||||
|
# For fish users (manual setup required):
|
||||||
|
source ~/.config/fish/config.fish
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now run the application from anywhere by simply typing `VideoTools`.
|
You can now run the application from anywhere by simply typing `VideoTools`.
|
||||||
|
|
@ -80,7 +83,9 @@ If you prefer to perform the steps manually:
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
# Source VideoTools aliases
|
# Source VideoTools aliases
|
||||||
source /path/to/VideoTools/scripts/alias.sh
|
source /path/to/VideoTools/scripts/alias.sh # bash
|
||||||
|
# source /path/to/VideoTools/scripts/alias.zsh # zsh
|
||||||
|
# source /path/to/VideoTools/scripts/alias.fish # fish
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Reload Your Shell:**
|
4. **Reload Your Shell:**
|
||||||
|
|
|
||||||
141
main.go
141
main.go
|
|
@ -48,11 +48,20 @@ import (
|
||||||
"git.leaktechnologies.dev/stu/VideoTools/internal/sysinfo"
|
"git.leaktechnologies.dev/stu/VideoTools/internal/sysinfo"
|
||||||
"git.leaktechnologies.dev/stu/VideoTools/internal/ui"
|
"git.leaktechnologies.dev/stu/VideoTools/internal/ui"
|
||||||
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
"git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
||||||
|
guitutils "git.leaktechnologies.dev/stu/VideoTools/internal/utils"
|
||||||
|
|
||||||
"github.com/ebitengine/oto/v3"
|
"github.com/ebitengine/oto/v3"
|
||||||
"github.com/skip2/go-qrcode"
|
"github.com/skip2/go-qrcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// abs returns the absolute value of an int32
|
||||||
|
func abs(x int32) int32 {
|
||||||
|
if x < 0 {
|
||||||
|
return -x
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
// Module describes a high level tool surface that gets a tile on the menu.
|
// Module describes a high level tool surface that gets a tile on the menu.
|
||||||
type Module struct {
|
type Module struct {
|
||||||
ID string
|
ID string
|
||||||
|
|
@ -1156,49 +1165,49 @@ type appState struct {
|
||||||
historyTabIdx int
|
historyTabIdx int
|
||||||
|
|
||||||
// Author module state
|
// Author module state
|
||||||
authorFile *videoSource
|
authorFile *videoSource
|
||||||
authorChapters []authorChapter
|
authorChapters []authorChapter
|
||||||
authorSceneThreshold float64
|
authorSceneThreshold float64
|
||||||
authorDetecting bool
|
authorDetecting bool
|
||||||
authorClips []authorClip // Multiple video clips for compilation
|
authorClips []authorClip // Multiple video clips for compilation
|
||||||
authorOutputType string // "dvd" or "iso"
|
authorOutputType string // "dvd" or "iso"
|
||||||
authorRegion string // "NTSC", "PAL", "AUTO"
|
authorRegion string // "NTSC", "PAL", "AUTO"
|
||||||
authorAspectRatio string // "4:3", "16:9", "AUTO"
|
authorAspectRatio string // "4:3", "16:9", "AUTO"
|
||||||
authorCreateMenu bool // Whether to create DVD menu
|
authorCreateMenu bool // Whether to create DVD menu
|
||||||
authorMenuTemplate string // "Simple", "Dark", "Poster"
|
authorMenuTemplate string // "Simple", "Dark", "Poster"
|
||||||
authorMenuBackgroundImage string // Path to a user-selected background image
|
authorMenuBackgroundImage string // Path to a user-selected background image
|
||||||
authorMenuTheme string // "VideoTools"
|
authorMenuTheme string // "VideoTools"
|
||||||
authorMenuTitleLogoEnabled bool // Enable title logo (main logo above menu)
|
authorMenuTitleLogoEnabled bool // Enable title logo (main logo above menu)
|
||||||
authorMenuTitleLogoPath string // Path to title logo image
|
authorMenuTitleLogoPath string // Path to title logo image
|
||||||
authorMenuTitleLogoPosition string // Position for title logo
|
authorMenuTitleLogoPosition string // Position for title logo
|
||||||
authorMenuTitleLogoScale float64 // Scale for title logo
|
authorMenuTitleLogoScale float64 // Scale for title logo
|
||||||
authorMenuTitleLogoMargin int // Margin for title logo
|
authorMenuTitleLogoMargin int // Margin for title logo
|
||||||
authorMenuStudioLogoEnabled bool // Enable studio logo (corner logo)
|
authorMenuStudioLogoEnabled bool // Enable studio logo (corner logo)
|
||||||
authorMenuStudioLogoPath string // Path to studio logo image
|
authorMenuStudioLogoPath string // Path to studio logo image
|
||||||
authorMenuStudioLogoPosition string // "Top Left", "Top Right", "Bottom Left", "Bottom Right"
|
authorMenuStudioLogoPosition string // "Top Left", "Top Right", "Bottom Left", "Bottom Right"
|
||||||
authorMenuStudioLogoScale float64 // Scale for studio logo
|
authorMenuStudioLogoScale float64 // Scale for studio logo
|
||||||
authorMenuStudioLogoMargin int // Margin for studio logo
|
authorMenuStudioLogoMargin int // Margin for studio logo
|
||||||
authorMenuStructure string // Feature only, Chapters, Extras
|
authorMenuStructure string // Feature only, Chapters, Extras
|
||||||
authorMenuExtrasEnabled bool // Show extras menu
|
authorMenuExtrasEnabled bool // Show extras menu
|
||||||
authorMenuChapterThumbSrc string // Auto, First Frame, Midpoint, Custom
|
authorMenuChapterThumbSrc string // Auto, First Frame, Midpoint, Custom
|
||||||
authorTitle string // DVD title
|
authorTitle string // DVD title
|
||||||
authorSubtitles []string // Subtitle file paths
|
authorSubtitles []string // Subtitle file paths
|
||||||
authorAudioTracks []string // Additional audio tracks
|
authorAudioTracks []string // Additional audio tracks
|
||||||
authorSummaryLabel *widget.Label
|
authorSummaryLabel *widget.Label
|
||||||
authorTreatAsChapters bool // Treat multiple clips as chapters
|
authorTreatAsChapters bool // Treat multiple clips as chapters
|
||||||
authorChapterSource string // embedded, scenes, clips, manual
|
authorChapterSource string // embedded, scenes, clips, manual
|
||||||
authorChaptersRefresh func() // Refresh hook for chapter list UI
|
authorChaptersRefresh func() // Refresh hook for chapter list UI
|
||||||
authorDiscSize string // "DVD5" or "DVD9"
|
authorDiscSize string // "DVD5" or "DVD9"
|
||||||
authorLogText string
|
authorLogText string
|
||||||
authorLogLines []string // Circular buffer for last N lines
|
authorLogLines []string // Circular buffer for last N lines
|
||||||
authorLogFilePath string // Path to log file for full viewing
|
authorLogFilePath string // Path to log file for full viewing
|
||||||
authorLogEntry *widget.Entry
|
authorLogEntry *widget.Entry
|
||||||
authorLogScroll *container.Scroll
|
authorLogScroll *container.Scroll
|
||||||
authorProgress float64
|
authorProgress float64
|
||||||
authorProgressBar *widget.ProgressBar
|
authorProgressBar *widget.ProgressBar
|
||||||
authorStatusLabel *widget.Label
|
authorStatusLabel *widget.Label
|
||||||
authorCancelBtn *widget.Button
|
authorCancelBtn *widget.Button
|
||||||
authorVideoTSPath string
|
authorVideoTSPath string
|
||||||
|
|
||||||
// Rip module state
|
// Rip module state
|
||||||
ripSourcePath string
|
ripSourcePath string
|
||||||
|
|
@ -1851,25 +1860,21 @@ func (r *mouseButtonRenderer) BackgroundColor() color.Color {
|
||||||
|
|
||||||
func (s *appState) setContent(body fyne.CanvasObject) {
|
func (s *appState) setContent(body fyne.CanvasObject) {
|
||||||
update := func() {
|
update := func() {
|
||||||
// Preserve current window size to prevent auto-resizing when content changes
|
|
||||||
// This ensures the window maintains the size the user set, even when content
|
|
||||||
// like progress bars or queue items change dynamically
|
|
||||||
currentSize := s.window.Canvas().Size()
|
currentSize := s.window.Canvas().Size()
|
||||||
|
|
||||||
bg := canvas.NewRectangle(backgroundColor)
|
bg := canvas.NewRectangle(backgroundColor)
|
||||||
sizeGuard := canvas.NewRectangle(color.Transparent)
|
sizeGuard := canvas.NewRectangle(color.Transparent)
|
||||||
sizeGuard.SetMinSize(fyne.NewSize(800, 600))
|
if currentSize.Width > 0 && currentSize.Height > 0 {
|
||||||
|
sizeGuard.SetMinSize(currentSize)
|
||||||
|
} else {
|
||||||
|
sizeGuard.SetMinSize(fyne.NewSize(800, 600))
|
||||||
|
}
|
||||||
if body == nil {
|
if body == nil {
|
||||||
s.window.SetContent(container.NewMax(bg, sizeGuard))
|
s.window.SetContent(container.NewMax(bg, sizeGuard))
|
||||||
// Restore window size after setting content
|
|
||||||
s.window.Resize(currentSize)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Wrap content with mouse button handler
|
// Wrap content with mouse button handler
|
||||||
wrapped := newMouseButtonHandler(container.NewMax(bg, sizeGuard, body), s)
|
wrapped := newMouseButtonHandler(container.NewMax(bg, sizeGuard, body), s)
|
||||||
s.window.SetContent(wrapped)
|
s.window.SetContent(wrapped)
|
||||||
// Restore window size after setting content
|
|
||||||
s.window.Resize(currentSize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use async Do() instead of DoAndWait() to avoid deadlock when called from main goroutine
|
// Use async Do() instead of DoAndWait() to avoid deadlock when called from main goroutine
|
||||||
|
|
@ -1905,6 +1910,7 @@ func (s *appState) showErrorWithCopy(title string, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appState) showMainMenu() {
|
func (s *appState) showMainMenu() {
|
||||||
|
_ = guitutils.DetectGUIEnvironment() // Use GUI utilities to ensure import is used
|
||||||
s.stopPreview()
|
s.stopPreview()
|
||||||
s.stopPlayer()
|
s.stopPlayer()
|
||||||
s.stopQueueAutoRefresh()
|
s.stopQueueAutoRefresh()
|
||||||
|
|
@ -6673,15 +6679,26 @@ func runGUI() {
|
||||||
} else {
|
} else {
|
||||||
logging.Debug(logging.CatUI, "app icon not found; continuing without custom icon")
|
logging.Debug(logging.CatUI, "app icon not found; continuing without custom icon")
|
||||||
}
|
}
|
||||||
|
// Enhanced cross-platform GUI detection and window sizing
|
||||||
|
guiEnv := guitutils.DetectGUIEnvironment()
|
||||||
|
logging.Debug(logging.CatUI, "detected GUI environment: %s", guiEnv.String())
|
||||||
|
|
||||||
// Adaptive window sizing for professional cross-resolution support
|
// Adaptive window sizing for professional cross-resolution support
|
||||||
w.SetFixedSize(false) // Allow manual resizing and maximizing
|
w.SetFixedSize(false) // Allow manual resizing and maximizing
|
||||||
|
|
||||||
// Use compact default size (800x600) that fits on any screen
|
// Use GUI environment to determine optimal window size
|
||||||
// Window can be resized or maximized by user using window manager controls
|
optimalSize := guiEnv.GetOptimalWindowSize(800, 600)
|
||||||
w.Resize(fyne.NewSize(800, 600))
|
w.Resize(optimalSize)
|
||||||
w.CenterOnScreen()
|
w.CenterOnScreen()
|
||||||
|
|
||||||
logging.Debug(logging.CatUI, "window initialized at 800x600 (compact default), manual resizing enabled")
|
// Log GPU acceleration support
|
||||||
|
if guiEnv.SupportsGPUAcceleration() {
|
||||||
|
logging.Debug(logging.CatUI, "GPU acceleration should be available on %s %s", guiEnv.GPUInfo.Vendor, guiEnv.GPUInfo.Model)
|
||||||
|
} else {
|
||||||
|
logging.Debug(logging.CatUI, "GPU acceleration may not be available - using software rendering")
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.Debug(logging.CatUI, "window initialized at %v (auto-detected environment), manual resizing enabled", optimalSize)
|
||||||
|
|
||||||
// Initialize audio module - load persisted config or use defaults
|
// Initialize audio module - load persisted config or use defaults
|
||||||
audioDefaults, err := loadAudioConfig()
|
audioDefaults, err := loadAudioConfig()
|
||||||
|
|
@ -11299,12 +11316,12 @@ func newPlaySession(path string, w, h int, fps, duration float64, targetW, targe
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := &playSession{
|
sess := &playSession{
|
||||||
path: path,
|
path: path,
|
||||||
fps: fps,
|
fps: fps,
|
||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
targetW: targetW,
|
targetW: targetW,
|
||||||
targetH: targetH,
|
targetH: targetH,
|
||||||
volume: 100,
|
volume: 100,
|
||||||
duration: duration,
|
duration: duration,
|
||||||
stop: make(chan struct{}),
|
stop: make(chan struct{}),
|
||||||
|
|
|
||||||
26
scripts/alias.fish
Normal file
26
scripts/alias.fish
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env fish
|
||||||
|
# VideoTools Convenience Script (fish)
|
||||||
|
# Source this file in fish to add the 'VideoTools' command
|
||||||
|
|
||||||
|
if not set -q FISH_VERSION
|
||||||
|
echo "This script is for fish. Use scripts/alias.sh or scripts/alias.zsh instead."
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l script_path (status -f)
|
||||||
|
set -l project_root (dirname (dirname $script_path))
|
||||||
|
|
||||||
|
alias VideoTools="bash $project_root/scripts/run.sh"
|
||||||
|
|
||||||
|
function VideoToolsRebuild
|
||||||
|
echo "Rebuilding VideoTools..."
|
||||||
|
bash "$project_root/scripts/build.sh"
|
||||||
|
end
|
||||||
|
|
||||||
|
function VideoToolsClean
|
||||||
|
echo "Cleaning VideoTools build artifacts..."
|
||||||
|
cd "$project_root"
|
||||||
|
go clean -cache -modcache -testcache
|
||||||
|
rm -f "$project_root/VideoTools"
|
||||||
|
echo "Clean complete"
|
||||||
|
end
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# VideoTools Convenience Script
|
# VideoTools Convenience Script (bash)
|
||||||
# Source this file in your shell to add the 'VideoTools' command
|
# Source this file in bash to add the 'VideoTools' command
|
||||||
|
|
||||||
|
if [ -z "$BASH_VERSION" ]; then
|
||||||
|
echo "This script is for bash. Use scripts/alias.zsh or scripts/alias.fish instead."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
|
|
||||||
26
scripts/alias.zsh
Normal file
26
scripts/alias.zsh
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env zsh
|
||||||
|
# VideoTools Convenience Script (zsh)
|
||||||
|
# Source this file in zsh to add the 'VideoTools' command
|
||||||
|
|
||||||
|
if [[ -z "$ZSH_VERSION" ]]; then
|
||||||
|
echo "This script is for zsh. Use scripts/alias.sh or scripts/alias.fish instead."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_PATH="${(%):-%N}"
|
||||||
|
PROJECT_ROOT="$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)"
|
||||||
|
|
||||||
|
alias VideoTools="bash $PROJECT_ROOT/scripts/run.sh"
|
||||||
|
|
||||||
|
VideoToolsRebuild() {
|
||||||
|
echo "Rebuilding VideoTools..."
|
||||||
|
bash "$PROJECT_ROOT/scripts/build.sh"
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoToolsClean() {
|
||||||
|
echo "Cleaning VideoTools build artifacts..."
|
||||||
|
cd "$PROJECT_ROOT" || return 1
|
||||||
|
go clean -cache -modcache -testcache
|
||||||
|
rm -f "$PROJECT_ROOT/VideoTools"
|
||||||
|
echo "Clean complete"
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user