Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ build:
# Install composer dependencies
.PHONY: composer
composer:
composer install --prefer-dist
composer dump-autoload
composer install --prefer-dist --no-dev
composer dump-autoload --no-dev

# Install npm dependencies
.PHONY: npm
Expand Down Expand Up @@ -60,7 +60,7 @@ fix:

# Build app package for app store
.PHONY: appstore
appstore: clean build
appstore: clean composer build
mkdir -p $(sign_dir)
rsync -a \
--exclude=/.git \
Expand All @@ -71,7 +71,6 @@ appstore: clean build
--exclude=/node_modules \
--exclude=/src \
--exclude=/tests \
--exclude=/vendor \
--exclude=/.gitignore \
--exclude=/.tx \
--exclude=/phpunit.xml \
Expand All @@ -96,10 +95,10 @@ help:
@echo "Available targets:"
@echo " make dev - Build for development with watch mode"
@echo " make build - Build for production"
@echo " make composer - Install PHP dependencies"
@echo " make composer - Install PHP dependencies (production only)"
@echo " make npm - Install Node.js dependencies"
@echo " make clean - Clean build artifacts"
@echo " make test - Run tests"
@echo " make lint - Run linters"
@echo " make fix - Fix code style issues"
@echo " make appstore - Build app package for app store"
@echo " make appstore - Build app package for app store (includes vendor)"
2 changes: 2 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
['name' => 'link#exportLinks', 'url' => '/api/v1/admin/links/export', 'verb' => 'GET'],
['name' => 'link#importLinks', 'url' => '/api/v1/admin/links/import', 'verb' => 'POST'],
['name' => 'link#updateOrder', 'url' => '/api/v1/admin/links/order', 'verb' => 'PUT'],
['name' => 'link#duplicate', 'url' => '/api/v1/admin/duplicate/{id}', 'verb' => 'POST'],
['name' => 'link#copyIcon', 'url' => '/api/v1/admin/copy-icon/{id}', 'verb' => 'POST'],
['name' => 'link#update', 'url' => '/api/v1/admin/links/{id}', 'verb' => 'PUT'],
['name' => 'link#delete', 'url' => '/api/v1/admin/links/{id}', 'verb' => 'DELETE'],
['name' => 'link#uploadIcon', 'url' => '/api/v1/admin/links/{id}/icon', 'verb' => 'POST'],
Expand Down
5 changes: 5 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,10 @@
"test:unit": "phpunit --configuration phpunit.xml --testsuite unit",
"test:integration": "phpunit --configuration phpunit.xml --testsuite integration",
"test": "phpunit --configuration phpunit.xml"
},
"config": {
"audit": {
"block-insecure": false
}
}
}
4 changes: 2 additions & 2 deletions js/dashlink-admin.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/dashlink-admin.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions js/dashlink-dashboard.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/dashlink-dashboard.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions js/dashlink-personal.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/dashlink-personal.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class Application extends App implements IBootstrap {

public function __construct(array $urlParams = []) {
parent::__construct(self::APP_ID, $urlParams);
$vendorAutoload = __DIR__ . "/../../vendor/autoload.php";
if (file_exists($vendorAutoload)) { require_once $vendorAutoload; }
}

public function register(IRegistrationContext $context): void {
Expand Down
49 changes: 46 additions & 3 deletions lib/Controller/LinkController.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,23 @@ public function create(
string $target = '_blank',
array $groups = [],
int $position = 0,
int $enabled = 1
int $enabled = 1,
?string $iconPath = null
): JSONResponse {
try {
$link = $this->linkService->createLink([
$data = [
'title' => $title,
'url' => $url,
'description' => $description,
'target' => $target,
'groups' => $groups,
'position' => $position,
'enabled' => $enabled,
]);
];
if ($iconPath !== null) {
$data['iconPath'] = $iconPath;
}
$link = $this->linkService->createLink($data);

return new JSONResponse($link->jsonSerialize(), Http::STATUS_CREATED);
} catch (\Exception $e) {
Expand Down Expand Up @@ -215,6 +220,44 @@ public function uploadIcon(int $id): JSONResponse {
}
}

/**
* Duplicate a link (including icon)
*
* @AdminRequired
*/
public function duplicate(int $id): JSONResponse {
try {
$link = $this->linkService->duplicateLink($id, $this->iconService);
$data = $link->jsonSerialize();
if (!empty($data['iconPath'])) {
$data['iconUrl'] = $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->linkToRoute('dashlink.link.getIcon', ['id' => $link->getId()])
);
}
return new JSONResponse($data, Http::STATUS_CREATED);
} catch (DoesNotExistException $e) {
return new JSONResponse(['error' => 'Link not found'], Http::STATUS_NOT_FOUND);
} catch (\Exception $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}

/**
* Copy icon from one link to another
*
* @AdminRequired
*/
public function copyIcon(int $id, int $sourceId): JSONResponse {
try {
$link = $this->iconService->copyIconToLink($sourceId, $id);
return new JSONResponse($link->jsonSerialize());
} catch (DoesNotExistException $e) {
return new JSONResponse(['error' => 'Link not found'], Http::STATUS_NOT_FOUND);
} catch (\Exception $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}

/**
* Delete icon for link
*
Expand Down
24 changes: 23 additions & 1 deletion lib/Dashboard/DashLinkWidget.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,34 @@
use OCP\IInitialStateService;
use OCA\DashLink\Service\LinkService;
use OCA\DashLink\Service\SettingsService;
use OCP\IGroupManager;
use OCP\IUserSession;

class DashLinkWidget implements IWidget {
private IL10N $l10n;
private IURLGenerator $urlGenerator;
private IInitialStateService $initialStateService;
private LinkService $linkService;
private SettingsService $settingsService;
private IGroupManager $groupManager;
private IUserSession $userSession;

public function __construct(
IL10N $l10n,
IURLGenerator $urlGenerator,
IInitialStateService $initialStateService,
LinkService $linkService,
SettingsService $settingsService
SettingsService $settingsService,
IGroupManager $groupManager,
IUserSession $userSession
) {
$this->l10n = $l10n;
$this->urlGenerator = $urlGenerator;
$this->initialStateService = $initialStateService;
$this->linkService = $linkService;
$this->settingsService = $settingsService;
$this->groupManager = $groupManager;
$this->userSession = $userSession;
}

public function getId(): string {
Expand Down Expand Up @@ -93,6 +101,20 @@ public function load(): void {
$this->settingsService->getHoverEffect()
);

// Provide isAdmin and userLinksEnabled for gear menu
$user = $this->userSession->getUser();
$isAdmin = $user !== null && $this->groupManager->isAdmin($user->getUID());
$this->initialStateService->provideInitialState(
'dashlink',
'isAdmin',
$isAdmin
);
$this->initialStateService->provideInitialState(
'dashlink',
'userLinksEnabled',
$this->settingsService->isUserLinksEnabled()
);

\OCP\Util::addScript('dashlink', 'dashlink-dashboard');
\OCP\Util::addStyle('dashlink', 'icons');
}
Expand Down
29 changes: 29 additions & 0 deletions lib/Service/IconService.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,35 @@ public function getIconFile(string $filename): ISimpleFile {
return $folder->getFile($filename);
}

/**
* Copy icon from one link to another
*/
public function copyIconToLink(int $sourceLinkId, int $targetLinkId): Link {
$sourceLink = $this->mapper->findById($sourceLinkId);
$targetLink = $this->mapper->findById($targetLinkId);

$sourceIconPath = $sourceLink->getIconPath();
if ($sourceIconPath === null) {
return $targetLink;
}

$folder = $this->getIconsFolder();
$sourceFile = $folder->getFile($sourceIconPath);

// Generate new filename for the copy
$extension = pathinfo($sourceIconPath, PATHINFO_EXTENSION);
$filename = 'icon_' . $targetLinkId . '_' . time() . '.' . $extension;

$newFile = $folder->newFile($filename);
$newFile->putContent($sourceFile->getContent());

$targetLink->setIconPath($filename);
$targetLink->setIconMimeType($sourceLink->getIconMimeType());
$targetLink->setUpdatedAt(new \DateTime());

return $this->mapper->update($targetLink);
}

/**
* Delete icon for link
*
Expand Down
30 changes: 30 additions & 0 deletions lib/Service/LinkService.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,30 @@ public function getLinksForCurrentUser(): array {
*
* @return array
*/
public function duplicateLink(int $id, IconService $iconService): Link {
$source = $this->mapper->findById($id);

$link = new Link();
$link->setTitle($source->getTitle() . ' (copy)');
$link->setUrl($source->getUrl());
$link->setDescription($source->getDescription());
$link->setTarget($source->getTarget());
$link->setGroups($source->getGroups());
$link->setPosition($source->getPosition() + 1);
$link->setEnabled($source->getEnabled());
$link->setCreatedAt(new \DateTime());
$link->setUpdatedAt(new \DateTime());

$newLink = $this->mapper->insert($link);

// Copy icon file if exists
if ($source->getIconPath() !== null) {
$newLink = $iconService->copyIconToLink($id, $newLink->getId());
}

return $newLink;
}

public function getAllLinks(): array {
$links = $this->mapper->findAll();
return array_map(fn(Link $link) => $link->jsonSerialize(), $links);
Expand Down Expand Up @@ -109,6 +133,9 @@ public function createLink(array $data): Link {
$link->setEnabled($data['enabled'] ?? 1);
$link->setCreatedAt(new \DateTime());
$link->setUpdatedAt(new \DateTime());
if (!empty($data["iconPath"])) {
$link->setIconPath($data["iconPath"]);
}

return $this->mapper->insert($link);
}
Expand Down Expand Up @@ -149,6 +176,9 @@ public function updateLink(int $id, array $data): Link {
}

$link->setUpdatedAt(new \DateTime());
if (!empty($data["iconPath"])) {
$link->setIconPath($data["iconPath"]);
}

return $this->mapper->update($link);
}
Expand Down
10 changes: 10 additions & 0 deletions lib/Service/SettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ class SettingsService {
'name' => 'Slide Panel',
'description' => 'Description panel slides up from the bottom'
],
'slide_bottom' => [
'id' => 'slide_bottom',
'name' => 'Slide Bottom',
'description' => 'Compact description bar slides up from the bottom edge'
],
'tooltip' => [
'id' => 'tooltip',
'name' => 'Tooltip',
'description' => 'Description tooltip pops up above the link'
],
];

private const DEFAULT_EFFECT = 'blur';
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading