Add darktable ghost publish plugin
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -2,7 +2,10 @@
|
||||
user ? throw "user argument is required",
|
||||
home ? throw "home argument is required",
|
||||
}:
|
||||
{ pkgs, ... }:
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
in
|
||||
{
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
@@ -13,12 +16,22 @@
|
||||
];
|
||||
|
||||
environment.persistence = {
|
||||
"/persist"."${home}/.config/darktable" = { };
|
||||
"/persist" = {
|
||||
"${home}/.config/darktable/data.db" = { };
|
||||
"${home}/.config/darktable/library.db" = { };
|
||||
};
|
||||
"/cache"."${home}/.cache/darktable" = { };
|
||||
};
|
||||
|
||||
home-manager.users.${user} =
|
||||
let
|
||||
lua-scripts = pkgs.fetchFromGitHub {
|
||||
owner = "darktable-org";
|
||||
repo = "lua-scripts";
|
||||
rev = "daa0877b4c25b91e4b71afc1ef8ffcba6018f7b2";
|
||||
sha256 = "sha256-NNGAq1zgKqWLhKBPgm7kFZq4xwvescxnCAwovSF9r4k=";
|
||||
};
|
||||
|
||||
hald-clut = pkgs.fetchFromGitHub {
|
||||
owner = "cedeber";
|
||||
repo = "hald-clut";
|
||||
@@ -27,7 +40,18 @@
|
||||
};
|
||||
in
|
||||
{
|
||||
home.packages = with pkgs; [ darktable ];
|
||||
home = {
|
||||
packages = with pkgs; [
|
||||
darktable
|
||||
exiftool
|
||||
(pkgs.callPackage ./publish { })
|
||||
];
|
||||
|
||||
sessionVariables = {
|
||||
GHOST_URL = "https://photos.karaolidis.com";
|
||||
GHOST_ADMIN_API_KEY_PATH = hmConfig.sops.secrets."jupiter/photos.karaolidis.com/admin".path;
|
||||
};
|
||||
};
|
||||
|
||||
xdg.configFile = {
|
||||
"darktable/darktablerc".source = (pkgs.formats.keyValue { }).generate "darktablerc" {
|
||||
@@ -48,9 +72,22 @@
|
||||
"$(EXIF.YEAR)-$(EXIF.MONTH)-$(EXIF.DAY)_$(EXIF.HOUR)-$(EXIF.MINUTE)-$(EXIF.SECOND)_$(CONFLICT_PADDING).$(FILE_EXTENSION)";
|
||||
"session/sub_directory_pattern" = "";
|
||||
"setup_import_directory" = true;
|
||||
"lua/script_manager/check_update" = false;
|
||||
};
|
||||
|
||||
"darktable/luarc".text = ''
|
||||
require "tools/script_manager"
|
||||
require "tools/publish"
|
||||
'';
|
||||
|
||||
"darktable/lua/lib".source = "${lua-scripts}/lib";
|
||||
"darktable/lua/tools/script_manager.lua".source = "${lua-scripts}/tools/script_manager.lua";
|
||||
"darktable/lua/tools/publish.lua".source = ./publish/publish.lua;
|
||||
|
||||
"darktable/luts".source = "${hald-clut}/HaldCLUT";
|
||||
};
|
||||
|
||||
sops.secrets."jupiter/photos.karaolidis.com/admin".sopsFile =
|
||||
../../../../../../secrets/personal/secrets.yaml;
|
||||
};
|
||||
}
|
||||
|
2
hosts/common/configs/user/gui/darktable/publish/.gitignore
vendored
Normal file
2
hosts/common/configs/user/gui/darktable/publish/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
build/
|
BIN
hosts/common/configs/user/gui/darktable/publish/bun.lockb
Executable file
BIN
hosts/common/configs/user/gui/darktable/publish/bun.lockb
Executable file
Binary file not shown.
29
hosts/common/configs/user/gui/darktable/publish/default.nix
Normal file
29
hosts/common/configs/user/gui/darktable/publish/default.nix
Normal file
@@ -0,0 +1,29 @@
|
||||
{ pkgs, lib, ... }:
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "darktable-publish";
|
||||
version = "1.0.0";
|
||||
src = ./.;
|
||||
|
||||
npmSrc = pkgs.buildNpmPackage ({
|
||||
inherit src pname version;
|
||||
npmDepsHash = "sha256-vBJIIuryC/zRvp9oKBVuCDTycPOpzgsLebU55CiIb7I=";
|
||||
dontNpmBuild = true;
|
||||
installPhase = ''
|
||||
cp -r . $out
|
||||
'';
|
||||
});
|
||||
|
||||
# FIXME: https://github.com/NixOS/nixpkgs/issues/255890
|
||||
wrapper = pkgs.writeShellApplication {
|
||||
name = pname;
|
||||
runtimeInputs = with pkgs; [ bun ];
|
||||
text = ''
|
||||
bun ${npmSrc}/src/index.ts "$@"
|
||||
'';
|
||||
};
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp ${lib.meta.getExe wrapper} $out/bin/
|
||||
'';
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{ files: ["**/*.{js,mjs,cjs,ts}"] },
|
||||
{ languageOptions: { globals: globals.browser } },
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
];
|
1962
hosts/common/configs/user/gui/darktable/publish/package-lock.json
generated
Normal file
1962
hosts/common/configs/user/gui/darktable/publish/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
hosts/common/configs/user/gui/darktable/publish/package.json
Normal file
24
hosts/common/configs/user/gui/darktable/publish/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "publish",
|
||||
"module": "src/index.ts",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/bun": "latest",
|
||||
"@types/jsonwebtoken": "^9.0.7",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"globals": "^15.14.0",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript-eslint": "^8.18.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^12.1.0",
|
||||
"exiftool-vendored": "^29.0.0",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
}
|
||||
}
|
133
hosts/common/configs/user/gui/darktable/publish/publish.lua
Normal file
133
hosts/common/configs/user/gui/darktable/publish/publish.lua
Normal file
@@ -0,0 +1,133 @@
|
||||
local dt = require "darktable"
|
||||
local df = require "lib/dtutils.file"
|
||||
local os = require "os"
|
||||
|
||||
-- Some fucking bullshit happening right here.
|
||||
function os.capture(command, raw)
|
||||
local f = assert(io.popen(command, 'r'))
|
||||
local s = assert(f:read('*a'))
|
||||
f:close()
|
||||
if raw then return s end
|
||||
s = string.gsub(s, '^%s+', '')
|
||||
s = string.gsub(s, '%s+$', '')
|
||||
s = string.gsub(s, '[\n\r]+', ' ')
|
||||
return s
|
||||
end
|
||||
|
||||
local publish_title = dt.new_widget("entry") {
|
||||
placeholder = "Post Title",
|
||||
tooltip = "enter title for the post"
|
||||
}
|
||||
|
||||
local publish_slug = dt.new_widget("entry") {
|
||||
placeholder = "post-slug",
|
||||
tooltip = "enter slug for the post (URL-friendly)"
|
||||
}
|
||||
|
||||
local publish_keywords = dt.new_widget("entry") {
|
||||
placeholder = "keywords (space-separated)",
|
||||
tooltip = "enter keywords (tags) for the post"
|
||||
}
|
||||
|
||||
local strip_gps_checkbox = dt.new_widget("check_button") {
|
||||
label = "Strip GPS data",
|
||||
value = false,
|
||||
tooltip = "remove GPS metadata from files before uploading"
|
||||
}
|
||||
|
||||
local widget = dt.new_widget("box") {
|
||||
orientation = "vertical",
|
||||
publish_title,
|
||||
publish_slug,
|
||||
publish_keywords,
|
||||
strip_gps_checkbox
|
||||
}
|
||||
|
||||
local function initialize(storage, format, images, high_quality, extra_data)
|
||||
extra_data.exported_files = {}
|
||||
extra_data.cleanup_files = {}
|
||||
|
||||
if publish_title.text == "" then
|
||||
extra_data.title = df.get_basename(images[1].filename)
|
||||
else
|
||||
extra_data.title = publish_title.text
|
||||
end
|
||||
|
||||
if publish_slug.text == "" then
|
||||
extra_data.slug = df.get_basename(images[1].filename)
|
||||
else
|
||||
extra_data.slug = publish_slug.text
|
||||
end
|
||||
|
||||
extra_data.keywords = publish_keywords.text
|
||||
extra_data.strip_gps = strip_gps_checkbox.value
|
||||
|
||||
return images
|
||||
end
|
||||
|
||||
local function store(storage, image, format, filename, number, total, high_quality, extra_data)
|
||||
if extra_data.strip_gps then
|
||||
local command = string.format("exiftool -gps:all= -overwrite_original '%s'", filename)
|
||||
os.execute(command)
|
||||
end
|
||||
|
||||
if image.is_raw then
|
||||
local original_path = image.path .. "/" .. image.filename
|
||||
local raw_filename = original_path
|
||||
|
||||
if extra_data.strip_gps then
|
||||
local tmpfile = os.tmpname()
|
||||
local command = string.format("exiftool -gps:all= -o '%s' '%s'", tmpfile, original_path)
|
||||
os.execute(command)
|
||||
table.insert(extra_data.cleanup_files, tmpfile)
|
||||
raw_filename = tmpfile
|
||||
end
|
||||
|
||||
table.insert(extra_data.exported_files, filename .. ":" .. raw_filename)
|
||||
else
|
||||
table.insert(extra_data.exported_files, filename)
|
||||
end
|
||||
end
|
||||
|
||||
local function finalize(storage, image_table, extra_data)
|
||||
local files_arg = table.concat(extra_data.exported_files, " ")
|
||||
|
||||
local command = string.format(
|
||||
"darktable-publish --title '%s' --slug '%s' %s",
|
||||
extra_data.title, extra_data.slug, files_arg
|
||||
)
|
||||
if extra_data.keywords ~= "" then
|
||||
command = command .. string.format(" --keywords %s", extra_data.keywords)
|
||||
end
|
||||
|
||||
-- Ignore that I use an external tool (written in JavaScript god forbid)
|
||||
-- I am _not_ doing JSON generation and web requests in Lua
|
||||
local result = os.capture(command)
|
||||
|
||||
if result and result:match("^http") then
|
||||
dt.print("Post published: " .. result)
|
||||
else
|
||||
dt.print("Failed to publish post.")
|
||||
end
|
||||
|
||||
local command = string.format("xdg-open %s", result)
|
||||
os.execute(command)
|
||||
|
||||
for _, tmpfile in ipairs(extra_data.cleanup_files) do
|
||||
os.remove(tmpfile)
|
||||
end
|
||||
end
|
||||
|
||||
local function supported(storage, format)
|
||||
return true
|
||||
end
|
||||
|
||||
dt.register_storage(
|
||||
"ghost_publish",
|
||||
"publish to Ghost CMS",
|
||||
store,
|
||||
finalize,
|
||||
supported,
|
||||
initialize,
|
||||
widget
|
||||
)
|
110
hosts/common/configs/user/gui/darktable/publish/src/api.ts
Normal file
110
hosts/common/configs/user/gui/darktable/publish/src/api.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { sign } from "jsonwebtoken";
|
||||
import { file } from "bun";
|
||||
|
||||
const getAdminApiKey = async () => {
|
||||
const keyPath = process.env.GHOST_ADMIN_API_KEY_PATH;
|
||||
if (!keyPath) {
|
||||
throw new Error(
|
||||
"Environment variable GHOST_ADMIN_API_KEY_PATH is not set.",
|
||||
);
|
||||
}
|
||||
|
||||
const keyFile = file(keyPath);
|
||||
if (!(await keyFile.exists())) {
|
||||
throw new Error(`Key file not found at path: ${keyPath}`);
|
||||
}
|
||||
|
||||
return await keyFile.text();
|
||||
};
|
||||
|
||||
const getEndpoint = () => {
|
||||
const endpoint = process.env.GHOST_URL;
|
||||
if (!endpoint) {
|
||||
throw new Error("Environment variable GHOST_URL is not set.");
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
};
|
||||
|
||||
const createJwt = (key: string) => {
|
||||
const [id, secret] = key.split(":");
|
||||
if (!id || !secret) {
|
||||
throw new Error("Invalid API key format. Expected format: {id}:{secret}");
|
||||
}
|
||||
|
||||
return sign({}, Buffer.from(secret, "hex"), {
|
||||
keyid: id,
|
||||
algorithm: "HS256",
|
||||
expiresIn: "5m",
|
||||
audience: `/admin/`,
|
||||
});
|
||||
};
|
||||
|
||||
const upload = async (
|
||||
slug: string,
|
||||
path: string,
|
||||
type: string | undefined,
|
||||
): Promise<any> => {
|
||||
const endpoint = getEndpoint();
|
||||
const fullEndpoint = `${endpoint}${slug}`;
|
||||
|
||||
const key = await getAdminApiKey();
|
||||
const token = createJwt(key);
|
||||
|
||||
const f = Bun.file(path, { type });
|
||||
const formData = new FormData();
|
||||
formData.append("file", f);
|
||||
|
||||
const response = await fetch(fullEndpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Ghost ${token}`,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to upload to ${fullEndpoint}: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
export const uploadImage = async (imagePath: string): Promise<string> => {
|
||||
const slug = `/ghost/api/admin/images/upload`;
|
||||
return (await upload(slug, imagePath, "image/jpeg")).images[0].url;
|
||||
};
|
||||
|
||||
export const uploadFile = async (filePath: string): Promise<string> => {
|
||||
const slug = `/ghost/api/admin/files/upload`;
|
||||
return (await upload(slug, filePath, undefined)).files[0].url;
|
||||
};
|
||||
|
||||
export const uploadPost = async (post: any): Promise<string> => {
|
||||
const endpoint = getEndpoint();
|
||||
const fullEndpoint = `${endpoint}/ghost/api/admin/posts`;
|
||||
|
||||
const key = await getAdminApiKey();
|
||||
const token = createJwt(key);
|
||||
|
||||
const response = await fetch(fullEndpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Ghost ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
posts: [post],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to upload to ${fullEndpoint}: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (await response.json()).posts[0].url;
|
||||
};
|
48
hosts/common/configs/user/gui/darktable/publish/src/exif.ts
Normal file
48
hosts/common/configs/user/gui/darktable/publish/src/exif.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { exiftool } from "exiftool-vendored";
|
||||
import type { FileInfo } from "./files";
|
||||
|
||||
export interface ShootingConditions {
|
||||
make: string;
|
||||
model: string;
|
||||
lensMake: string;
|
||||
lensModel: string;
|
||||
focalLength: string;
|
||||
focalLength35: string;
|
||||
shutterSpeed: string;
|
||||
fStop: string;
|
||||
iso: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export const extractShootingConditions = async (
|
||||
fileInfo: FileInfo,
|
||||
): Promise<ShootingConditions> => {
|
||||
const path = fileInfo.rawPath ?? fileInfo.jpegPath;
|
||||
|
||||
try {
|
||||
const exifData = await exiftool.read(path);
|
||||
|
||||
return {
|
||||
make: exifData.Make ?? "Unknown",
|
||||
model: exifData.Model ?? "Unknown",
|
||||
lensMake: exifData.LensMake ?? "Unknown",
|
||||
lensModel: exifData.LensModel ?? "Unknown",
|
||||
focalLength: exifData.FocalLength ?? "Unknown",
|
||||
focalLength35: exifData.FocalLengthIn35mmFormat ?? "Unknown",
|
||||
shutterSpeed: exifData.ExposureTime ?? "Unknown",
|
||||
fStop: exifData.FNumber?.toString() ?? "Unknown",
|
||||
iso: exifData.ISO?.toString() ?? "Unknown",
|
||||
timestamp: new Date(
|
||||
(exifData.CreateDate?.toString() as string).replace(/\./g, ":"),
|
||||
).toISOString(),
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(
|
||||
`Failed to extract EXIF data from ${path}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const createImageCaption = (exif: ShootingConditions) => {
|
||||
return `${exif.make} ${exif.model}, ${exif.lensMake} ${exif.lensModel} @ ${exif.focalLength} (${exif.focalLength35}), ${exif.shutterSpeed} s, f/${exif.fStop}, ISO ${exif.iso}`;
|
||||
};
|
66
hosts/common/configs/user/gui/darktable/publish/src/files.ts
Normal file
66
hosts/common/configs/user/gui/darktable/publish/src/files.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { basename, extname } from "path";
|
||||
|
||||
export interface FileInfo {
|
||||
jpegPath: string;
|
||||
jpegSize: number;
|
||||
rawPath?: string;
|
||||
rawSize?: number;
|
||||
}
|
||||
|
||||
export const getBasenameWithoutExtension = (path: string): string => {
|
||||
const base = basename(path);
|
||||
const extension = extname(path);
|
||||
return base.slice(0, -extension.length);
|
||||
};
|
||||
|
||||
export const getBasenameWithExtension = (path: string): string => {
|
||||
return basename(path);
|
||||
};
|
||||
|
||||
export const prepareFiles = async (files: string[]): Promise<FileInfo[]> => {
|
||||
if (files.length > 10) {
|
||||
throw new Error("Up to 10 files are allowed at a time.");
|
||||
}
|
||||
|
||||
const parsedFiles: FileInfo[] = [];
|
||||
|
||||
for (const pair of files) {
|
||||
const parts = pair.split(/(?<!\\):/);
|
||||
const jpegPath = parts[0].replace(/\\:/g, ":");
|
||||
const rawPath = parts[1]?.replace(/\\:/g, ":");
|
||||
|
||||
const jpegFile = Bun.file(jpegPath);
|
||||
if (!(await jpegFile.exists())) {
|
||||
throw new Error(`JPEG file not found: ${jpegPath}`);
|
||||
}
|
||||
|
||||
const jpegSize = jpegFile.size;
|
||||
|
||||
if (!rawPath) {
|
||||
parsedFiles.push({
|
||||
jpegPath,
|
||||
jpegSize,
|
||||
rawPath: undefined,
|
||||
rawSize: undefined,
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const rawFile = Bun.file(rawPath);
|
||||
if (!(await rawFile.exists())) {
|
||||
throw new Error(`RAW file not found: ${rawPath}`);
|
||||
}
|
||||
|
||||
const rawSize = rawFile.size;
|
||||
|
||||
parsedFiles.push({
|
||||
jpegPath,
|
||||
jpegSize,
|
||||
rawPath: rawPath,
|
||||
rawSize: rawSize,
|
||||
});
|
||||
}
|
||||
|
||||
return parsedFiles;
|
||||
};
|
115
hosts/common/configs/user/gui/darktable/publish/src/index.ts
Normal file
115
hosts/common/configs/user/gui/darktable/publish/src/index.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Command } from "commander";
|
||||
import { createFileNode, createImageNode, createHeadingNode } from "./lexical";
|
||||
import {
|
||||
extractShootingConditions,
|
||||
createImageCaption,
|
||||
} from "./exif";
|
||||
import { uploadFile, uploadImage, uploadPost } from "./api";
|
||||
import { getBasenameWithExtension, prepareFiles } from "./files";
|
||||
|
||||
new Command()
|
||||
.name("darktable-publish")
|
||||
.description("Publish files to GHOST CMS with optional metadata.")
|
||||
.option("-t, --title [string]", "Specify the title")
|
||||
.option("-s, --slug [string]", "Specify the slug")
|
||||
.option("-k, --keywords [string...]", "Specify blog post keywords (tags)")
|
||||
.argument("<files...>", "Files to process")
|
||||
.action(async (files, options) => {
|
||||
if (!options.title) {
|
||||
throw new Error("Please specify a title.");
|
||||
}
|
||||
|
||||
if (!options.slug) {
|
||||
throw new Error("Please specify a slug.");
|
||||
}
|
||||
|
||||
const parsedFiles = await prepareFiles(files);
|
||||
|
||||
const [
|
||||
shootingConditions,
|
||||
uploadedJpegImages,
|
||||
uploadedJpegFiles,
|
||||
uploadedRawFiles,
|
||||
] = await Promise.all([
|
||||
Promise.all(parsedFiles.map(extractShootingConditions)),
|
||||
Promise.all(parsedFiles.map((f) => uploadImage(f.jpegPath))),
|
||||
Promise.all(parsedFiles.map((f) => uploadFile(f.jpegPath))),
|
||||
Promise.all(
|
||||
parsedFiles.map((f) =>
|
||||
f.rawPath ? uploadFile(f.rawPath) : Promise.resolve(undefined),
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
const aggregatedFiles = parsedFiles.map((file, index) => ({
|
||||
...file,
|
||||
shootingConditions: shootingConditions[index],
|
||||
uploadedJpegImage: uploadedJpegImages[index],
|
||||
uploadedJpegFile: uploadedJpegFiles[index],
|
||||
uploadedRawFile: uploadedRawFiles[index],
|
||||
}));
|
||||
|
||||
const result: any = {
|
||||
root: {
|
||||
children: [],
|
||||
direction: "ltr",
|
||||
format: "",
|
||||
indent: 0,
|
||||
type: "root",
|
||||
version: 1,
|
||||
},
|
||||
};
|
||||
|
||||
if (aggregatedFiles.length > 1) {
|
||||
aggregatedFiles.slice(1).forEach((file) =>
|
||||
result.root.children.push(
|
||||
createImageNode({
|
||||
src: file.uploadedJpegImage,
|
||||
caption: createImageCaption(file.shootingConditions),
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
result.root.children.push(createHeadingNode("Downloads", "h2"));
|
||||
|
||||
aggregatedFiles.forEach((file) => {
|
||||
result.root.children.push(
|
||||
createFileNode({
|
||||
src: file.uploadedJpegFile,
|
||||
name: getBasenameWithExtension(file.jpegPath),
|
||||
size: file.jpegSize,
|
||||
}),
|
||||
);
|
||||
|
||||
if (file.uploadedRawFile && file.rawPath && file.rawSize) {
|
||||
result.root.children.push(
|
||||
createFileNode({
|
||||
src: file.uploadedRawFile,
|
||||
name: getBasenameWithExtension(file.rawPath),
|
||||
size: file.rawSize,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const post = {
|
||||
title: options.title,
|
||||
slug: options.slug,
|
||||
lexical: JSON.stringify(result),
|
||||
feature_image: aggregatedFiles[0].uploadedJpegImage,
|
||||
feature_image_caption: createImageCaption(
|
||||
aggregatedFiles[0].shootingConditions,
|
||||
),
|
||||
status: "published",
|
||||
visibility: "public",
|
||||
tags: options.keywords,
|
||||
published_at: aggregatedFiles[0].shootingConditions.timestamp,
|
||||
};
|
||||
|
||||
const url = await uploadPost(post);
|
||||
console.log(url);
|
||||
|
||||
process.exit(0);
|
||||
})
|
||||
.parse();
|
@@ -0,0 +1,50 @@
|
||||
export const createTextNode = (text: string) => ({
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: "normal",
|
||||
style: "",
|
||||
text,
|
||||
type: "extended-text",
|
||||
version: 1,
|
||||
});
|
||||
|
||||
export const createHeadingNode = (text: string, level: string) => ({
|
||||
children: [createTextNode(text)],
|
||||
direction: "ltr",
|
||||
format: "",
|
||||
indent: 0,
|
||||
type: "extended-heading",
|
||||
version: 1,
|
||||
tag: level,
|
||||
});
|
||||
|
||||
export interface ImageInput {
|
||||
src: string;
|
||||
caption: string;
|
||||
}
|
||||
|
||||
export const createImageNode = (image: ImageInput) => {
|
||||
return {
|
||||
type: "image",
|
||||
version: 1,
|
||||
cardWidth: "regular",
|
||||
...image,
|
||||
};
|
||||
};
|
||||
|
||||
export interface FileInput {
|
||||
src: string;
|
||||
name: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export const createFileNode = (file: FileInput) => {
|
||||
return {
|
||||
type: "file",
|
||||
src: file.src,
|
||||
fileTitle: file.name,
|
||||
fileName: file.name,
|
||||
fileCaption: "",
|
||||
fileSize: file.size,
|
||||
};
|
||||
};
|
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
@@ -12,22 +12,6 @@ let
|
||||
hmConfig = config.home-manager.users.${user};
|
||||
in
|
||||
{
|
||||
# FIXME: https://github.com/lassekongo83/adw-gtk3/issues/267
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
adw-gtk3 = prev.adw-gtk3.overrideAttrs (oldAttrs: rec {
|
||||
pname = "adw-gtk3";
|
||||
version = "5.3";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "lassekongo83";
|
||||
repo = pname;
|
||||
rev = "v${version}";
|
||||
sha256 = "sha256-DpJLX9PJX1Q8dDOx7YOXQzgNECsKp5uGiCVTX6iSlbI=";
|
||||
};
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
home-manager.users.${user} = {
|
||||
gtk = {
|
||||
enable = true;
|
||||
|
@@ -114,6 +114,7 @@ in
|
||||
|
||||
imports = [
|
||||
./langs/c
|
||||
./langs/lua
|
||||
./langs/nix
|
||||
./langs/python
|
||||
./langs/svelte
|
||||
|
@@ -0,0 +1,9 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
programs.vscode.extensions =
|
||||
with pkgs;
|
||||
with vscode-extensions;
|
||||
[
|
||||
sumneko.lua
|
||||
];
|
||||
}
|
Reference in New Issue
Block a user